From 858bfcfd6cc7e326546eb14c5ebc797309b66222 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 7 Jul 2017 15:24:02 +0200 Subject: [PATCH 1/8] Expose JSON endpoint registration --- src/httprpc.cpp | 5 +++++ src/httprpc.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/httprpc.cpp b/src/httprpc.cpp index a207d5ece45..c096b7569e3 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -226,6 +226,11 @@ static bool InitRPCAuthentication() return true; } +void RegisterJSONEndpoint(const std::string& endpoint, bool exactMatch) +{ + RegisterHTTPHandler(endpoint, exactMatch, HTTPReq_JSONRPC); +} + bool StartHTTPRPC() { LogPrint(BCLog::RPC, "Starting HTTP RPC server\n"); diff --git a/src/httprpc.h b/src/httprpc.h index a89a8f0fbfe..4ed8fddbec8 100644 --- a/src/httprpc.h +++ b/src/httprpc.h @@ -32,4 +32,6 @@ void InterruptREST(); */ void StopREST(); +void RegisterJSONEndpoint(const std::string& endpoint, bool exactMatch); + #endif From 6e390d1c821e3fd530bd2525a78d04976d0b6e11 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 7 Jul 2017 15:07:20 +0200 Subject: [PATCH 2/8] Split node / wallet RPC calls based on the endpoint --- src/httpserver.cpp | 6 ++- src/qt/test/rpcnestedtests.cpp | 2 +- src/rpc/blockchain.cpp | 54 ++++++++++----------- src/rpc/mining.cpp | 22 ++++----- src/rpc/misc.cpp | 24 +++++----- src/rpc/net.cpp | 28 +++++------ src/rpc/rawtransaction.cpp | 22 ++++----- src/rpc/server.cpp | 17 +++++-- src/rpc/server.h | 1 + src/wallet/rpcwallet.cpp | 106 ++++++++++++++++++++--------------------- 10 files changed, 149 insertions(+), 133 deletions(-) diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 1c53d8d49d2..4673e7d1a6d 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -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()) { + // only add handlers if they do not exists yet + pathHandlers.push_back(pathHandler); + } } void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch) diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp index fbad9e544ae..356478262e4 100644 --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -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() diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index c17ca2fa3a1..cc77c25a9c1 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -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) diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 046c85a76eb..f194ac5f057 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -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) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index fcbbe1ceed0..492c45b0783 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -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) diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index ed452fcb021..80fda27602a 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -622,20 +622,20 @@ UniValue setnetworkactive(const JSONRPCRequest& request) } static const CRPCCommand commands[] = -{ // category name actor (function) okSafeMode - // --------------------- ------------------------ ----------------------- ---------- - { "network", "getconnectioncount", &getconnectioncount, true, {} }, - { "network", "ping", &ping, true, {} }, - { "network", "getpeerinfo", &getpeerinfo, true, {} }, - { "network", "addnode", &addnode, true, {"node","command"} }, - { "network", "disconnectnode", &disconnectnode, true, {"address", "nodeid"} }, - { "network", "getaddednodeinfo", &getaddednodeinfo, true, {"node"} }, - { "network", "getnettotals", &getnettotals, true, {} }, - { "network", "getnetworkinfo", &getnetworkinfo, true, {} }, - { "network", "setban", &setban, true, {"subnet", "command", "bantime", "absolute"} }, - { "network", "listbanned", &listbanned, true, {} }, - { "network", "clearbanned", &clearbanned, true, {} }, - { "network", "setnetworkactive", &setnetworkactive, true, {"state"} }, +{ // category endpoint name actor (function) okSafeMode named args + // ------------------ ------------ ---------------------- ----------------------- ---------- ------------------ + { "network", "/v1/node/", "getconnectioncount", &getconnectioncount, true, {} }, + { "network", "/v1/node/", "ping", &ping, true, {} }, + { "network", "/v1/node/", "getpeerinfo", &getpeerinfo, true, {} }, + { "network", "/v1/node/", "addnode", &addnode, true, {"node","command"} }, + { "network", "/v1/node/", "disconnectnode", &disconnectnode, true, {"address", "nodeid"} }, + { "network", "/v1/node/", "getaddednodeinfo", &getaddednodeinfo, true, {"node"} }, + { "network", "/v1/node/", "getnettotals", &getnettotals, true, {} }, + { "network", "/v1/node/", "getnetworkinfo", &getnetworkinfo, true, {} }, + { "network", "/v1/node/", "setban", &setban, true, {"subnet", "command", "bantime", "absolute"} }, + { "network", "/v1/node/", "listbanned", &listbanned, true, {} }, + { "network", "/v1/node/", "clearbanned", &clearbanned, true, {} }, + { "network", "/v1/node/", "setnetworkactive", &setnetworkactive, true, {"state"} }, }; void RegisterNetRPCCommands(CRPCTable &t) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index b878624df87..ab93002f29f 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -898,17 +898,17 @@ UniValue sendrawtransaction(const JSONRPCRequest& request) } static const CRPCCommand commands[] = -{ // category name actor (function) okSafeMode - // --------------------- ------------------------ ----------------------- ---------- - { "rawtransactions", "getrawtransaction", &getrawtransaction, true, {"txid","verbose"} }, - { "rawtransactions", "createrawtransaction", &createrawtransaction, true, {"inputs","outputs","locktime","replaceable"} }, - { "rawtransactions", "decoderawtransaction", &decoderawtransaction, true, {"hexstring"} }, - { "rawtransactions", "decodescript", &decodescript, true, {"hexstring"} }, - { "rawtransactions", "sendrawtransaction", &sendrawtransaction, false, {"hexstring","allowhighfees"} }, - { "rawtransactions", "signrawtransaction", &signrawtransaction, false, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */ - - { "blockchain", "gettxoutproof", &gettxoutproof, true, {"txids", "blockhash"} }, - { "blockchain", "verifytxoutproof", &verifytxoutproof, true, {"proof"} }, +{ // category endpoint name actor (function) okSafeMode named args + // ------------------ ------------ ---------------------- ----------------------- ---------- ------------------ + { "rawtransactions", "/v1/node/", "getrawtransaction", &getrawtransaction, true, {"txid","verbose"} }, + { "rawtransactions", "/v1/node/", "createrawtransaction", &createrawtransaction, true, {"inputs","outputs","locktime","replaceable"} }, + { "rawtransactions", "/v1/node/", "decoderawtransaction", &decoderawtransaction, true, {"hexstring"} }, + { "rawtransactions", "/v1/node/", "decodescript", &decodescript, true, {"hexstring"} }, + { "rawtransactions", "/v1/node/", "sendrawtransaction", &sendrawtransaction, false, {"hexstring","allowhighfees"} }, + { "rawtransactions", "/v1/*", "signrawtransaction", &signrawtransaction, false, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */ + + { "blockchain", "/v1/node/", "gettxoutproof", &gettxoutproof, true, {"txids", "blockhash"} }, + { "blockchain", "/v1/node/", "verifytxoutproof", &verifytxoutproof, true, {"proof"} }, }; void RegisterRawTransactionRPCCommands(CRPCTable &t) diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 63e4e9c630b..ab996dc6b86 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -8,6 +8,7 @@ #include "base58.h" #include "fs.h" #include "init.h" +#include "httprpc.h" #include "random.h" #include "sync.h" #include "ui_interface.h" @@ -280,9 +281,9 @@ static const CRPCCommand vRPCCommands[] = { // category name actor (function) okSafe argNames // --------------------- ------------------------ ----------------------- ------ ---------- /* Overall control/query calls */ - { "control", "help", &help, true, {"command"} }, - { "control", "stop", &stop, true, {} }, - { "control", "uptime", &uptime, true, {} }, + { "control", "/v1/*" , "help", &help, true, {"command"} }, + { "control", "/v1/node/", "stop", &stop, true, {} }, + { "control", "/v1/node/", "uptime", &uptime, true, {} }, }; CRPCTable::CRPCTable() @@ -310,6 +311,9 @@ bool CRPCTable::appendCommand(const std::string& name, const CRPCCommand* pcmd) if (IsRPCRunning()) return false; + // make sure we register the command endpoint + RegisterJSONEndpoint(pcmd->endpoint, false); + // don't allow overwriting for now std::map::const_iterator it = mapCommands.find(name); if (it != mapCommands.end()) @@ -494,6 +498,13 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const if (!pcmd) throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); + // enfore endpoint only for non / and "" endpoint (the later is used by the GUI) + if (request.URI != "" && request.URI != "/") { + // enforce correct endpoint usage + if (pcmd->endpoint != "/v1/*" && (request.URI.size() < pcmd->endpoint.size() || request.URI.substr(0, pcmd->endpoint.size()) != pcmd->endpoint)) { + throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Incorrect endpoint used (called method has "+pcmd->endpoint+" as endpoint)"); + } + } g_rpcSignals.PreCommand(*pcmd); try diff --git a/src/rpc/server.h b/src/rpc/server.h index b20c8277274..60d0c7c06ce 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -132,6 +132,7 @@ class CRPCCommand { public: std::string category; + std::string endpoint; std::string name; rpcfn_type actor; bool okSafeMode; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 5f72e3b6f59..b94e5bc8d0d 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3051,59 +3051,59 @@ extern UniValue removeprunedfunds(const JSONRPCRequest& request); extern UniValue importmulti(const JSONRPCRequest& request); static const CRPCCommand commands[] = -{ // category name actor (function) okSafeMode - // --------------------- ------------------------ ----------------------- ---------- - { "rawtransactions", "fundrawtransaction", &fundrawtransaction, false, {"hexstring","options"} }, - { "hidden", "resendwallettransactions", &resendwallettransactions, true, {} }, - { "wallet", "abandontransaction", &abandontransaction, false, {"txid"} }, - { "wallet", "abortrescan", &abortrescan, false, {} }, - { "wallet", "addmultisigaddress", &addmultisigaddress, true, {"nrequired","keys","account"} }, - { "wallet", "addwitnessaddress", &addwitnessaddress, true, {"address"} }, - { "wallet", "backupwallet", &backupwallet, true, {"destination"} }, - { "wallet", "bumpfee", &bumpfee, true, {"txid", "options"} }, - { "wallet", "dumpprivkey", &dumpprivkey, true, {"address"} }, - { "wallet", "dumpwallet", &dumpwallet, true, {"filename"} }, - { "wallet", "encryptwallet", &encryptwallet, true, {"passphrase"} }, - { "wallet", "getaccountaddress", &getaccountaddress, true, {"account"} }, - { "wallet", "getaccount", &getaccount, true, {"address"} }, - { "wallet", "getaddressesbyaccount", &getaddressesbyaccount, true, {"account"} }, - { "wallet", "getbalance", &getbalance, false, {"account","minconf","include_watchonly"} }, - { "wallet", "getnewaddress", &getnewaddress, true, {"account"} }, - { "wallet", "getrawchangeaddress", &getrawchangeaddress, true, {} }, - { "wallet", "getreceivedbyaccount", &getreceivedbyaccount, false, {"account","minconf"} }, - { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, false, {"address","minconf"} }, - { "wallet", "gettransaction", &gettransaction, false, {"txid","include_watchonly"} }, - { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, false, {} }, - { "wallet", "getwalletinfo", &getwalletinfo, false, {} }, - { "wallet", "importmulti", &importmulti, true, {"requests","options"} }, - { "wallet", "importprivkey", &importprivkey, true, {"privkey","label","rescan"} }, - { "wallet", "importwallet", &importwallet, true, {"filename"} }, - { "wallet", "importaddress", &importaddress, true, {"address","label","rescan","p2sh"} }, - { "wallet", "importprunedfunds", &importprunedfunds, true, {"rawtransaction","txoutproof"} }, - { "wallet", "importpubkey", &importpubkey, true, {"pubkey","label","rescan"} }, - { "wallet", "keypoolrefill", &keypoolrefill, true, {"newsize"} }, - { "wallet", "listaccounts", &listaccounts, false, {"minconf","include_watchonly"} }, - { "wallet", "listaddressgroupings", &listaddressgroupings, false, {} }, - { "wallet", "listlockunspent", &listlockunspent, false, {} }, - { "wallet", "listreceivedbyaccount", &listreceivedbyaccount, false, {"minconf","include_empty","include_watchonly"} }, - { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, false, {"minconf","include_empty","include_watchonly"} }, - { "wallet", "listsinceblock", &listsinceblock, false, {"blockhash","target_confirmations","include_watchonly"} }, - { "wallet", "listtransactions", &listtransactions, false, {"account","count","skip","include_watchonly"} }, - { "wallet", "listunspent", &listunspent, false, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, - { "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} }, - { "wallet", "move", &movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} }, - { "wallet", "sendfrom", &sendfrom, false, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, - { "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} }, - { "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} }, - { "wallet", "setaccount", &setaccount, true, {"address","account"} }, - { "wallet", "settxfee", &settxfee, true, {"amount"} }, - { "wallet", "signmessage", &signmessage, true, {"address","message"} }, - { "wallet", "walletlock", &walletlock, true, {} }, - { "wallet", "walletpassphrasechange", &walletpassphrasechange, true, {"oldpassphrase","newpassphrase"} }, - { "wallet", "walletpassphrase", &walletpassphrase, true, {"passphrase","timeout"} }, - { "wallet", "removeprunedfunds", &removeprunedfunds, true, {"txid"} }, - - { "generating", "generate", &generate, true, {"nblocks","maxtries"} }, +{ // category endpoint name actor (function) okSafeMode named args + // ------------------ -------------- ------------------------ ----------------------- ---------- ------------------ + { "rawtransactions", "/v1/wallet/", "fundrawtransaction", &fundrawtransaction, false, {"hexstring","options"} }, + { "hidden", "/v1/wallet/", "resendwallettransactions", &resendwallettransactions, true, {} }, + { "wallet", "/v1/wallet/", "abandontransaction", &abandontransaction, false, {"txid"} }, + { "wallet", "/v1/wallet/", "abortrescan", &abortrescan, false, {} }, + { "wallet", "/v1/wallet/", "addmultisigaddress", &addmultisigaddress, true, {"nrequired","keys","account"} }, + { "wallet", "/v1/wallet/", "addwitnessaddress", &addwitnessaddress, true, {"address"} }, + { "wallet", "/v1/wallet/", "backupwallet", &backupwallet, true, {"destination"} }, + { "wallet", "/v1/wallet/", "bumpfee", &bumpfee, true, {"txid", "options"} }, + { "wallet", "/v1/wallet/", "dumpprivkey", &dumpprivkey, true, {"address"} }, + { "wallet", "/v1/wallet/", "dumpwallet", &dumpwallet, true, {"filename"} }, + { "wallet", "/v1/wallet/", "encryptwallet", &encryptwallet, true, {"passphrase"} }, + { "wallet", "/v1/wallet/", "getaccountaddress", &getaccountaddress, true, {"account"} }, + { "wallet", "/v1/wallet/", "getaccount", &getaccount, true, {"address"} }, + { "wallet", "/v1/wallet/", "getaddressesbyaccount", &getaddressesbyaccount, true, {"account"} }, + { "wallet", "/v1/wallet/", "getbalance", &getbalance, false, {"account","minconf","include_watchonly"} }, + { "wallet", "/v1/wallet/", "getnewaddress", &getnewaddress, true, {"account"} }, + { "wallet", "/v1/wallet/", "getrawchangeaddress", &getrawchangeaddress, true, {} }, + { "wallet", "/v1/wallet/", "getreceivedbyaccount", &getreceivedbyaccount, false, {"account","minconf"} }, + { "wallet", "/v1/wallet/", "getreceivedbyaddress", &getreceivedbyaddress, false, {"address","minconf"} }, + { "wallet", "/v1/wallet/", "gettransaction", &gettransaction, false, {"txid","include_watchonly"} }, + { "wallet", "/v1/wallet/", "getunconfirmedbalance", &getunconfirmedbalance, false, {} }, + { "wallet", "/v1/wallet/", "getwalletinfo", &getwalletinfo, false, {} }, + { "wallet", "/v1/wallet/", "importmulti", &importmulti, true, {"requests","options"} }, + { "wallet", "/v1/wallet/", "importprivkey", &importprivkey, true, {"privkey","label","rescan"} }, + { "wallet", "/v1/wallet/", "importwallet", &importwallet, true, {"filename"} }, + { "wallet", "/v1/wallet/", "importaddress", &importaddress, true, {"address","label","rescan","p2sh"} }, + { "wallet", "/v1/wallet/", "importprunedfunds", &importprunedfunds, true, {"rawtransaction","txoutproof"} }, + { "wallet", "/v1/wallet/", "importpubkey", &importpubkey, true, {"pubkey","label","rescan"} }, + { "wallet", "/v1/wallet/", "keypoolrefill", &keypoolrefill, true, {"newsize"} }, + { "wallet", "/v1/wallet/", "listaccounts", &listaccounts, false, {"minconf","include_watchonly"} }, + { "wallet", "/v1/wallet/", "listaddressgroupings", &listaddressgroupings, false, {} }, + { "wallet", "/v1/wallet/", "listlockunspent", &listlockunspent, false, {} }, + { "wallet", "/v1/wallet/", "listreceivedbyaccount", &listreceivedbyaccount, false, {"minconf","include_empty","include_watchonly"} }, + { "wallet", "/v1/wallet/", "listreceivedbyaddress", &listreceivedbyaddress, false, {"minconf","include_empty","include_watchonly"} }, + { "wallet", "/v1/wallet/", "listsinceblock", &listsinceblock, false, {"blockhash","target_confirmations","include_watchonly"} }, + { "wallet", "/v1/wallet/", "listtransactions", &listtransactions, false, {"account","count","skip","include_watchonly"} }, + { "wallet", "/v1/wallet/", "listunspent", &listunspent, false, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, + { "wallet", "/v1/wallet/", "lockunspent", &lockunspent, true, {"unlock","transactions"} }, + { "wallet", "/v1/wallet/", "move", &movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} }, + { "wallet", "/v1/wallet/", "sendfrom", &sendfrom, false, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, + { "wallet", "/v1/wallet/", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} }, + { "wallet", "/v1/wallet/", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} }, + { "wallet", "/v1/wallet/", "setaccount", &setaccount, true, {"address","account"} }, + { "wallet", "/v1/wallet/", "settxfee", &settxfee, true, {"amount"} }, + { "wallet", "/v1/wallet/", "signmessage", &signmessage, true, {"address","message"} }, + { "wallet", "/v1/wallet/", "walletlock", &walletlock, true, {} }, + { "wallet", "/v1/wallet/", "walletpassphrasechange", &walletpassphrasechange, true, {"oldpassphrase","newpassphrase"} }, + { "wallet", "/v1/wallet/", "walletpassphrase", &walletpassphrase, true, {"passphrase","timeout"} }, + { "wallet", "/v1/wallet/", "removeprunedfunds", &removeprunedfunds, true, {"txid"} }, + + { "generating", "/v1/wallet/", "generate", &generate, true, {"nblocks","maxtries"} }, }; void RegisterWalletRPCCommands(CRPCTable &t) From 7506021afa733a55d2d5ef5543c7f59e311658eb Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Thu, 13 Jul 2017 17:06:27 +0200 Subject: [PATCH 3/8] Add wallet endpoint support to bitcoin-cli (-usewallet) --- src/bitcoin-cli.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 885b787b4da..78d2b66815f 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -46,6 +46,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; } @@ -235,7 +236,13 @@ 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 = "/"; + if (GetArg("-usewallet", "") != "") { + // always use v1 endpoint if -usewallet has been provided + endpoint = "/v1/wallet/"+GetArg("-usewallet", ""); + } + 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"); From f232ef53ecc36dc6350d2407c6031ede7920f0a1 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Thu, 13 Jul 2017 17:08:42 +0200 Subject: [PATCH 4/8] Add urlencode/decode via libevent2 --- src/bitcoin-cli.cpp | 12 ++++++++++-- src/httpserver.cpp | 11 +++++++++++ src/httpserver.h | 2 ++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 78d2b66815f..623b3ccb95f 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -238,9 +238,17 @@ UniValue CallRPC(const std::string& strMethod, const UniValue& params) // check if we should use a special wallet endpoint std::string endpoint = "/"; - if (GetArg("-usewallet", "") != "") { + std::string walletName = GetArg("-usewallet", ""); + if (walletName != "") { // always use v1 endpoint if -usewallet has been provided - endpoint = "/v1/wallet/"+GetArg("-usewallet", ""); + 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 diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 4673e7d1a6d..117ed00f312 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -669,3 +669,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 9df56e5fc55..3e434bf0a0b 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -148,4 +148,6 @@ class HTTPEvent struct event* ev; }; +std::string urlDecode(const std::string &urlEncoded); + #endif // BITCOIN_HTTPSERVER_H From 19fe32fc54f05f5e654d88430136e58b23fd2e7b Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Thu, 13 Jul 2017 17:15:45 +0200 Subject: [PATCH 5/8] Select wallet based on the given endpoint --- src/rpc/protocol.h | 1 + src/wallet/rpcwallet.cpp | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index 70f7092cfeb..86056ad49b2 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -82,6 +82,7 @@ enum RPCErrorCode RPC_WALLET_WRONG_ENC_STATE = -15, //!< Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) RPC_WALLET_ENCRYPTION_FAILED = -16, //!< Failed to encrypt the wallet RPC_WALLET_ALREADY_UNLOCKED = -17, //!< Wallet is already unlocked + RPC_WALLET_NOT_FOUND = -18, //!< Wallet not found (multiwallet) }; UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index b94e5bc8d0d..ca7a9358b7b 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -9,6 +9,7 @@ #include "consensus/validation.h" #include "core_io.h" #include "init.h" +#include "httpserver.h" #include "validation.h" #include "net.h" #include "policy/feerate.h" @@ -30,10 +31,29 @@ #include +static const std::string WALLET_ENDPOINT_BASE = "/v1/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; + } + } + // no wallet found with the given endpoint + throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet file does not exist or is not loaded"); + } + else { + if (vpwallets.size() > 1 && !request.fHelp) { + // don't allow to use a default wallet in multiwallet + return nullptr; + } + // default endpoint, use first wallet if possible + return vpwallets.empty() ? nullptr : vpwallets[0]; + } } std::string HelpRequiringPassphrase(CWallet * const pwallet) From 555b51d2e73f1c535b9c60cc6d56ec7530ad3712 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 7 Jul 2017 16:03:24 +0200 Subject: [PATCH 6/8] Fix test_bitcoin circular dependency issue --- src/Makefile.test.include | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 90504ad52df..6415b3d2e33 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -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 From 9710df955dbfc6687600e34263e57d903776bd31 Mon Sep 17 00:00:00 2001 From: John Newbery Date: Thu, 13 Jul 2017 10:53:42 -0400 Subject: [PATCH 7/8] [tests] [wallet] Add wallet endpoint support to authproxy --- test/functional/test_framework/authproxy.py | 3 +++ test/functional/test_framework/coverage.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/test/functional/test_framework/authproxy.py b/test/functional/test_framework/authproxy.py index dfcc524313a..b3671cbdc51 100644 --- a/test/functional/test_framework/authproxy.py +++ b/test/functional/test_framework/authproxy.py @@ -191,3 +191,6 @@ def _get_response(self): 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 3f87ef91f61..227b1a17afb 100644 --- a/test/functional/test_framework/coverage.py +++ b/test/functional/test_framework/coverage.py @@ -56,6 +56,8 @@ def __call__(self, *args, **kwargs): 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): """ From 759dc37f3de53f660c73650c0678f1ee6544490b Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Thu, 13 Jul 2017 17:49:46 +0200 Subject: [PATCH 8/8] [QA] add basic multiwallet test --- test/functional/multiwallet.py | 47 ++++++++++++++++++++++++++++++++++++++++++ test/functional/test_runner.py | 1 + 2 files changed, 48 insertions(+) create mode 100755 test/functional/multiwallet.py diff --git a/test/functional/multiwallet.py b/test/functional/multiwallet.py new file mode 100755 index 00000000000..6fcab4ce89d --- /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] / "v1/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] / "v1/wallet/w2" + walletinfo = w2.getwalletinfo() + assert_equal(walletinfo['immature_balance'], 0) + + w3 = self.nodes[0] / "v1/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_runner.py b/test/functional/test_runner.py index b7bc6e841b0..51577589fe0 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -63,6 +63,7 @@ 'segwit.py', # vv Tests less than 2m vv 'wallet.py', + 'multiwallet.py', 'wallet-accounts.py', 'p2p-segwit.py', 'wallet-dump.py',