]>
Commit | Line | Data |
---|---|---|
69d605f4 | 1 | // Copyright (c) 2010 Satoshi Nakamoto |
f914f1a7 | 2 | // Copyright (c) 2009-2014 The Bitcoin Core developers |
77920402 | 3 | // Distributed under the MIT software license, see the accompanying |
3a25a2b9 | 4 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
69d605f4 | 5 | |
fb78cc23 | 6 | #include "rpcserver.h" |
51ed9ec9 BD |
7 | |
8 | #include "base58.h" | |
0eeb4f5d | 9 | #include "init.h" |
4401b2d7 EL |
10 | #include "random.h" |
11 | #include "sync.h" | |
48ba56cd | 12 | #include "ui_interface.h" |
c037531d | 13 | #include "util.h" |
4401b2d7 | 14 | #include "utilstrencodings.h" |
48ba56cd | 15 | #ifdef ENABLE_WALLET |
50c72f23 | 16 | #include "wallet/wallet.h" |
48ba56cd | 17 | #endif |
fc72c078 S |
18 | #include "asyncrpcqueue.h" |
19 | ||
20 | #include <memory> | |
51ed9ec9 | 21 | |
92f2c1fe | 22 | #include <boost/algorithm/string.hpp> |
69d605f4 | 23 | #include <boost/asio.hpp> |
92f2c1fe | 24 | #include <boost/asio/ssl.hpp> |
914dc012 | 25 | #include <boost/bind.hpp> |
95d888a6 | 26 | #include <boost/filesystem.hpp> |
a0780ba0 | 27 | #include <boost/foreach.hpp> |
69d605f4 WL |
28 | #include <boost/iostreams/concepts.hpp> |
29 | #include <boost/iostreams/stream.hpp> | |
914dc012 | 30 | #include <boost/shared_ptr.hpp> |
4401b2d7 | 31 | #include <boost/signals2/signal.hpp> |
ad49c256 | 32 | #include <boost/thread.hpp> |
ed21d5bd | 33 | |
a10a6e2a | 34 | #include <univalue.h> |
5ce4c2a2 | 35 | |
69d605f4 | 36 | using namespace boost::asio; |
4401b2d7 | 37 | using namespace RPCServer; |
40a158e1 | 38 | using namespace std; |
69d605f4 | 39 | |
f81ce5bd GA |
40 | static std::string strRPCUserColonPass; |
41 | ||
ff6a7af1 | 42 | static bool fRPCRunning = false; |
af82884a DK |
43 | static bool fRPCInWarmup = true; |
44 | static std::string rpcWarmupStatus("RPC server started"); | |
45 | static CCriticalSection cs_rpcWarmup; | |
46 | ||
77920402 | 47 | //! These are created by StartRPCThreads, destroyed in StopRPCThreads |
a3241998 | 48 | static boost::asio::io_service* rpc_io_service = NULL; |
92f2c1fe | 49 | static map<string, boost::shared_ptr<deadline_timer> > deadlineTimers; |
21eb5ada GA |
50 | static ssl::context* rpc_ssl_context = NULL; |
51 | static boost::thread_group* rpc_worker_group = NULL; | |
a8db31c8 | 52 | static boost::asio::io_service::work *rpc_dummy_work = NULL; |
ee219125 | 53 | static std::vector<CSubNet> rpc_allow_subnets; //!< List of subnets to allow RPC connections from |
cef44941 | 54 | static std::vector< boost::shared_ptr<ip::tcp::acceptor> > rpc_acceptors; |
e9205293 | 55 | |
4401b2d7 EL |
56 | static struct CRPCSignals |
57 | { | |
58 | boost::signals2::signal<void ()> Started; | |
59 | boost::signals2::signal<void ()> Stopped; | |
60 | boost::signals2::signal<void (const CRPCCommand&)> PreCommand; | |
61 | boost::signals2::signal<void (const CRPCCommand&)> PostCommand; | |
62 | } g_rpcSignals; | |
63 | ||
64 | void RPCServer::OnStarted(boost::function<void ()> slot) | |
65 | { | |
66 | g_rpcSignals.Started.connect(slot); | |
67 | } | |
68 | ||
69 | void RPCServer::OnStopped(boost::function<void ()> slot) | |
70 | { | |
71 | g_rpcSignals.Stopped.connect(slot); | |
72 | } | |
73 | ||
74 | void RPCServer::OnPreCommand(boost::function<void (const CRPCCommand&)> slot) | |
75 | { | |
76 | g_rpcSignals.PreCommand.connect(boost::bind(slot, _1)); | |
77 | } | |
78 | ||
79 | void RPCServer::OnPostCommand(boost::function<void (const CRPCCommand&)> slot) | |
80 | { | |
81 | g_rpcSignals.PostCommand.connect(boost::bind(slot, _1)); | |
82 | } | |
83 | ||
d014114d JS |
84 | void RPCTypeCheck(const UniValue& params, |
85 | const list<UniValue::VType>& typesExpected, | |
cc6dfd1f | 86 | bool fAllowNull) |
899d373b | 87 | { |
cc71666a | 88 | size_t i = 0; |
d014114d | 89 | BOOST_FOREACH(UniValue::VType t, typesExpected) |
899d373b GA |
90 | { |
91 | if (params.size() <= i) | |
92 | break; | |
93 | ||
d014114d | 94 | const UniValue& v = params[i]; |
ed21d5bd | 95 | if (!((v.type() == t) || (fAllowNull && (v.isNull())))) |
899d373b GA |
96 | { |
97 | string err = strprintf("Expected type %s, got %s", | |
ed21d5bd | 98 | uvTypeName(t), uvTypeName(v.type())); |
738835d7 | 99 | throw JSONRPCError(RPC_TYPE_ERROR, err); |
899d373b GA |
100 | } |
101 | i++; | |
102 | } | |
103 | } | |
104 | ||
ed21d5bd JG |
105 | void RPCTypeCheckObj(const UniValue& o, |
106 | const map<string, UniValue::VType>& typesExpected, | |
cc6dfd1f | 107 | bool fAllowNull) |
899d373b | 108 | { |
d014114d | 109 | BOOST_FOREACH(const PAIRTYPE(string, UniValue::VType)& t, typesExpected) |
899d373b | 110 | { |
d014114d | 111 | const UniValue& v = find_value(o, t.first); |
ed21d5bd | 112 | if (!fAllowNull && v.isNull()) |
7d9d134b | 113 | throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing %s", t.first)); |
cc6dfd1f | 114 | |
ed21d5bd | 115 | if (!((v.type() == t.second) || (fAllowNull && (v.isNull())))) |
899d373b GA |
116 | { |
117 | string err = strprintf("Expected type %s for %s, got %s", | |
ed21d5bd | 118 | uvTypeName(t.second), t.first, uvTypeName(v.type())); |
738835d7 | 119 | throw JSONRPCError(RPC_TYPE_ERROR, err); |
899d373b GA |
120 | } |
121 | } | |
122 | } | |
123 | ||
d014114d | 124 | CAmount AmountFromValue(const UniValue& value) |
69d605f4 | 125 | { |
84d1d5fd WL |
126 | if (!value.isNum() && !value.isStr()) |
127 | throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number or string"); | |
c66dff3d | 128 | CAmount amount; |
fed500e2 | 129 | if (!ParseFixedPoint(value.getValStr(), 8, &amount)) |
738835d7 | 130 | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); |
c66dff3d WL |
131 | if (!MoneyRange(amount)) |
132 | throw JSONRPCError(RPC_TYPE_ERROR, "Amount out of range"); | |
133 | return amount; | |
69d605f4 WL |
134 | } |
135 | ||
851f58f9 | 136 | UniValue ValueFromAmount(const CAmount& amount) |
69d605f4 | 137 | { |
d5bf1afa WL |
138 | bool sign = amount < 0; |
139 | int64_t n_abs = (sign ? -amount : amount); | |
140 | int64_t quotient = n_abs / COIN; | |
141 | int64_t remainder = n_abs % COIN; | |
142 | return UniValue(UniValue::VNUM, | |
143 | strprintf("%s%d.%08d", sign ? "-" : "", quotient, remainder)); | |
69d605f4 WL |
144 | } |
145 | ||
d014114d | 146 | uint256 ParseHashV(const UniValue& v, string strName) |
463c9710 PT |
147 | { |
148 | string strHex; | |
ed21d5bd | 149 | if (v.isStr()) |
463c9710 PT |
150 | strHex = v.get_str(); |
151 | if (!IsHex(strHex)) // Note: IsHex("") is false | |
152 | throw JSONRPCError(RPC_INVALID_PARAMETER, strName+" must be hexadecimal string (not '"+strHex+"')"); | |
153 | uint256 result; | |
154 | result.SetHex(strHex); | |
155 | return result; | |
156 | } | |
d014114d | 157 | uint256 ParseHashO(const UniValue& o, string strKey) |
463c9710 PT |
158 | { |
159 | return ParseHashV(find_value(o, strKey), strKey); | |
160 | } | |
d014114d | 161 | vector<unsigned char> ParseHexV(const UniValue& v, string strName) |
463c9710 PT |
162 | { |
163 | string strHex; | |
ed21d5bd | 164 | if (v.isStr()) |
463c9710 PT |
165 | strHex = v.get_str(); |
166 | if (!IsHex(strHex)) | |
167 | throw JSONRPCError(RPC_INVALID_PARAMETER, strName+" must be hexadecimal string (not '"+strHex+"')"); | |
168 | return ParseHex(strHex); | |
169 | } | |
d014114d | 170 | vector<unsigned char> ParseHexO(const UniValue& o, string strKey) |
463c9710 PT |
171 | { |
172 | return ParseHexV(find_value(o, strKey), strKey); | |
173 | } | |
69d605f4 | 174 | |
74335bd3 | 175 | |
77920402 MF |
176 | /** |
177 | * Note: This interface may still be subject to change. | |
178 | */ | |
69d605f4 | 179 | |
db954a65 | 180 | std::string CRPCTable::help(const std::string& strCommand) const |
69d605f4 | 181 | { |
69d605f4 | 182 | string strRet; |
6b5b7cbf | 183 | string category; |
69d605f4 | 184 | set<rpcfn_type> setDone; |
6b5b7cbf CL |
185 | vector<pair<string, const CRPCCommand*> > vCommands; |
186 | ||
9862229d | 187 | for (map<string, const CRPCCommand*>::const_iterator mi = mapCommands.begin(); mi != mapCommands.end(); ++mi) |
6b5b7cbf CL |
188 | vCommands.push_back(make_pair(mi->second->category + mi->first, mi->second)); |
189 | sort(vCommands.begin(), vCommands.end()); | |
190 | ||
191 | BOOST_FOREACH(const PAIRTYPE(string, const CRPCCommand*)& command, vCommands) | |
69d605f4 | 192 | { |
6b5b7cbf CL |
193 | const CRPCCommand *pcmd = command.second; |
194 | string strMethod = pcmd->name; | |
69d605f4 | 195 | // We already filter duplicates, but these deprecated screw up the sort order |
47f48a65 | 196 | if (strMethod.find("label") != string::npos) |
69d605f4 | 197 | continue; |
bd9aebf1 | 198 | if ((strCommand != "" || pcmd->category == "hidden") && strMethod != strCommand) |
69d605f4 WL |
199 | continue; |
200 | try | |
201 | { | |
851f58f9 | 202 | UniValue params; |
dc42bf52 | 203 | rpcfn_type pfn = pcmd->actor; |
69d605f4 WL |
204 | if (setDone.insert(pfn).second) |
205 | (*pfn)(params, true); | |
206 | } | |
27df4123 | 207 | catch (const std::exception& e) |
69d605f4 WL |
208 | { |
209 | // Help text is returned in an exception | |
210 | string strHelp = string(e.what()); | |
211 | if (strCommand == "") | |
6b5b7cbf | 212 | { |
ab9dc75a | 213 | if (strHelp.find('\n') != string::npos) |
69d605f4 | 214 | strHelp = strHelp.substr(0, strHelp.find('\n')); |
6b5b7cbf CL |
215 | |
216 | if (category != pcmd->category) | |
217 | { | |
218 | if (!category.empty()) | |
219 | strRet += "\n"; | |
220 | category = pcmd->category; | |
221 | string firstLetter = category.substr(0,1); | |
222 | boost::to_upper(firstLetter); | |
223 | strRet += "== " + firstLetter + category.substr(1) + " ==\n"; | |
224 | } | |
225 | } | |
69d605f4 WL |
226 | strRet += strHelp + "\n"; |
227 | } | |
228 | } | |
229 | if (strRet == "") | |
7d9d134b | 230 | strRet = strprintf("help: unknown command: %s\n", strCommand); |
69d605f4 WL |
231 | strRet = strRet.substr(0,strRet.size()-1); |
232 | return strRet; | |
233 | } | |
234 | ||
d014114d | 235 | UniValue help(const UniValue& params, bool fHelp) |
9862229d PW |
236 | { |
237 | if (fHelp || params.size() > 1) | |
238 | throw runtime_error( | |
a6099ef3 | 239 | "help ( \"command\" )\n" |
240 | "\nList all commands, or get help for a specified command.\n" | |
241 | "\nArguments:\n" | |
242 | "1. \"command\" (string, optional) The command to get help on\n" | |
243 | "\nResult:\n" | |
244 | "\"text\" (string) The help text\n" | |
245 | ); | |
9862229d PW |
246 | |
247 | string strCommand; | |
248 | if (params.size() > 0) | |
249 | strCommand = params[0].get_str(); | |
250 | ||
251 | return tableRPC.help(strCommand); | |
252 | } | |
253 | ||
69d605f4 | 254 | |
d014114d | 255 | UniValue stop(const UniValue& params, bool fHelp) |
69d605f4 | 256 | { |
6e65420b | 257 | // Accept the deprecated and ignored 'detach' boolean argument |
3731f578 | 258 | if (fHelp || params.size() > 1) |
69d605f4 | 259 | throw runtime_error( |
92467073 | 260 | "stop\n" |
6c7cc8eb | 261 | "\nStop Zcash server."); |
69d605f4 | 262 | // Shutdown will take long enough that the response should get back |
9247134e | 263 | StartShutdown(); |
6c7cc8eb | 264 | return "Zcash server stopping"; |
69d605f4 WL |
265 | } |
266 | ||
267 | ||
69d605f4 | 268 | |
77920402 MF |
269 | /** |
270 | * Call Table | |
271 | */ | |
e46704dd | 272 | static const CRPCCommand vRPCCommands[] = |
b9fb692d JS |
273 | { // category name actor (function) okSafeMode |
274 | // --------------------- ------------------------ ----------------------- ---------- | |
ab88ed93 | 275 | /* Overall control/query calls */ |
b9fb692d JS |
276 | { "control", "getinfo", &getinfo, true }, /* uses wallet if enabled */ |
277 | { "control", "help", &help, true }, | |
278 | { "control", "stop", &stop, true }, | |
ab88ed93 WL |
279 | |
280 | /* P2P networking */ | |
b9fb692d JS |
281 | { "network", "getnetworkinfo", &getnetworkinfo, true }, |
282 | { "network", "addnode", &addnode, true }, | |
94ee48c4 | 283 | { "network", "disconnectnode", &disconnectnode, true }, |
b9fb692d JS |
284 | { "network", "getaddednodeinfo", &getaddednodeinfo, true }, |
285 | { "network", "getconnectioncount", &getconnectioncount, true }, | |
286 | { "network", "getnettotals", &getnettotals, true }, | |
287 | { "network", "getpeerinfo", &getpeerinfo, true }, | |
288 | { "network", "ping", &ping, true }, | |
ed3f13a0 JS |
289 | { "network", "setban", &setban, true }, |
290 | { "network", "listbanned", &listbanned, true }, | |
291 | { "network", "clearbanned", &clearbanned, true }, | |
ab88ed93 WL |
292 | |
293 | /* Block chain and UTXO */ | |
b9fb692d JS |
294 | { "blockchain", "getblockchaininfo", &getblockchaininfo, true }, |
295 | { "blockchain", "getbestblockhash", &getbestblockhash, true }, | |
296 | { "blockchain", "getblockcount", &getblockcount, true }, | |
297 | { "blockchain", "getblock", &getblock, true }, | |
298 | { "blockchain", "getblockhash", &getblockhash, true }, | |
d3d5483e | 299 | { "blockchain", "getblockheader", &getblockheader, true }, |
b9fb692d JS |
300 | { "blockchain", "getchaintips", &getchaintips, true }, |
301 | { "blockchain", "getdifficulty", &getdifficulty, true }, | |
302 | { "blockchain", "getmempoolinfo", &getmempoolinfo, true }, | |
303 | { "blockchain", "getrawmempool", &getrawmempool, true }, | |
304 | { "blockchain", "gettxout", &gettxout, true }, | |
59ed61b3 MC |
305 | { "blockchain", "gettxoutproof", &gettxoutproof, true }, |
306 | { "blockchain", "verifytxoutproof", &verifytxoutproof, true }, | |
b9fb692d JS |
307 | { "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true }, |
308 | { "blockchain", "verifychain", &verifychain, true }, | |
48ba56cd | 309 | |
4a85e067 | 310 | /* Mining */ |
f3e49686 | 311 | #ifdef ENABLE_WALLET |
b9fb692d | 312 | { "mining", "getblocktemplate", &getblocktemplate, true }, |
f3e49686 | 313 | #endif |
b9fb692d | 314 | { "mining", "getmininginfo", &getmininginfo, true }, |
000499ae JG |
315 | { "mining", "getlocalsolps", &getlocalsolps, true }, |
316 | { "mining", "getnetworksolps", &getnetworksolps, true }, | |
b9fb692d JS |
317 | { "mining", "getnetworkhashps", &getnetworkhashps, true }, |
318 | { "mining", "prioritisetransaction", &prioritisetransaction, true }, | |
319 | { "mining", "submitblock", &submitblock, true }, | |
1b114e54 | 320 | { "mining", "getblocksubsidy", &getblocksubsidy, true }, |
6b5b7cbf | 321 | |
8e8b6d70 | 322 | #ifdef ENABLE_MINING |
6b5b7cbf | 323 | /* Coin generation */ |
b9fb692d JS |
324 | { "generating", "getgenerate", &getgenerate, true }, |
325 | { "generating", "setgenerate", &setgenerate, true }, | |
326 | { "generating", "generate", &generate, true }, | |
6b5b7cbf | 327 | #endif |
ab88ed93 WL |
328 | |
329 | /* Raw transactions */ | |
b9fb692d JS |
330 | { "rawtransactions", "createrawtransaction", &createrawtransaction, true }, |
331 | { "rawtransactions", "decoderawtransaction", &decoderawtransaction, true }, | |
332 | { "rawtransactions", "decodescript", &decodescript, true }, | |
333 | { "rawtransactions", "getrawtransaction", &getrawtransaction, true }, | |
334 | { "rawtransactions", "sendrawtransaction", &sendrawtransaction, false }, | |
335 | { "rawtransactions", "signrawtransaction", &signrawtransaction, false }, /* uses wallet if enabled */ | |
3d8013a0 MC |
336 | #ifdef ENABLE_WALLET |
337 | { "rawtransactions", "fundrawtransaction", &fundrawtransaction, false }, | |
338 | #endif | |
ab88ed93 WL |
339 | |
340 | /* Utility functions */ | |
b9fb692d JS |
341 | { "util", "createmultisig", &createmultisig, true }, |
342 | { "util", "validateaddress", &validateaddress, true }, /* uses wallet if enabled */ | |
343 | { "util", "verifymessage", &verifymessage, true }, | |
344 | { "util", "estimatefee", &estimatefee, true }, | |
345 | { "util", "estimatepriority", &estimatepriority, true }, | |
4e16a724 | 346 | { "util", "z_validateaddress", &z_validateaddress, true }, /* uses wallet if enabled */ |
4a85e067 | 347 | |
bd9aebf1 | 348 | /* Not shown in help */ |
b9fb692d JS |
349 | { "hidden", "invalidateblock", &invalidateblock, true }, |
350 | { "hidden", "reconsiderblock", &reconsiderblock, true }, | |
351 | { "hidden", "setmocktime", &setmocktime, true }, | |
0f5954c4 | 352 | #ifdef ENABLE_WALLET |
b9fb692d | 353 | { "hidden", "resendwallettransactions", &resendwallettransactions, true}, |
0f5954c4 | 354 | #endif |
bd9aebf1 | 355 | |
4a85e067 WL |
356 | #ifdef ENABLE_WALLET |
357 | /* Wallet */ | |
b9fb692d JS |
358 | { "wallet", "addmultisigaddress", &addmultisigaddress, true }, |
359 | { "wallet", "backupwallet", &backupwallet, true }, | |
360 | { "wallet", "dumpprivkey", &dumpprivkey, true }, | |
361 | { "wallet", "dumpwallet", &dumpwallet, true }, | |
362 | { "wallet", "encryptwallet", &encryptwallet, true }, | |
363 | { "wallet", "getaccountaddress", &getaccountaddress, true }, | |
364 | { "wallet", "getaccount", &getaccount, true }, | |
365 | { "wallet", "getaddressesbyaccount", &getaddressesbyaccount, true }, | |
366 | { "wallet", "getbalance", &getbalance, false }, | |
367 | { "wallet", "getnewaddress", &getnewaddress, true }, | |
368 | { "wallet", "getrawchangeaddress", &getrawchangeaddress, true }, | |
369 | { "wallet", "getreceivedbyaccount", &getreceivedbyaccount, false }, | |
370 | { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, false }, | |
371 | { "wallet", "gettransaction", &gettransaction, false }, | |
372 | { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, false }, | |
373 | { "wallet", "getwalletinfo", &getwalletinfo, false }, | |
374 | { "wallet", "importprivkey", &importprivkey, true }, | |
375 | { "wallet", "importwallet", &importwallet, true }, | |
376 | { "wallet", "importaddress", &importaddress, true }, | |
377 | { "wallet", "keypoolrefill", &keypoolrefill, true }, | |
378 | { "wallet", "listaccounts", &listaccounts, false }, | |
379 | { "wallet", "listaddressgroupings", &listaddressgroupings, false }, | |
380 | { "wallet", "listlockunspent", &listlockunspent, false }, | |
381 | { "wallet", "listreceivedbyaccount", &listreceivedbyaccount, false }, | |
382 | { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, false }, | |
383 | { "wallet", "listsinceblock", &listsinceblock, false }, | |
384 | { "wallet", "listtransactions", &listtransactions, false }, | |
385 | { "wallet", "listunspent", &listunspent, false }, | |
386 | { "wallet", "lockunspent", &lockunspent, true }, | |
387 | { "wallet", "move", &movecmd, false }, | |
388 | { "wallet", "sendfrom", &sendfrom, false }, | |
389 | { "wallet", "sendmany", &sendmany, false }, | |
390 | { "wallet", "sendtoaddress", &sendtoaddress, false }, | |
391 | { "wallet", "setaccount", &setaccount, true }, | |
392 | { "wallet", "settxfee", &settxfee, true }, | |
393 | { "wallet", "signmessage", &signmessage, true }, | |
394 | { "wallet", "walletlock", &walletlock, true }, | |
395 | { "wallet", "walletpassphrasechange", &walletpassphrasechange, true }, | |
396 | { "wallet", "walletpassphrase", &walletpassphrase, true }, | |
6962bb3d | 397 | { "wallet", "zcbenchmark", &zc_benchmark, true }, |
730790f7 | 398 | { "wallet", "zcrawkeygen", &zc_raw_keygen, true }, |
b7e4abd6 | 399 | { "wallet", "zcrawjoinsplit", &zc_raw_joinsplit, true }, |
1737627c | 400 | { "wallet", "zcrawreceive", &zc_raw_receive, true }, |
c1c45943 | 401 | { "wallet", "zcsamplejoinsplit", &zc_sample_joinsplit, true }, |
6c41028f | 402 | { "wallet", "z_listreceivedbyaddress",&z_listreceivedbyaddress,false }, |
a0a3334c S |
403 | { "wallet", "z_getbalance", &z_getbalance, false }, |
404 | { "wallet", "z_gettotalbalance", &z_gettotalbalance, false }, | |
6d2d045c | 405 | { "wallet", "z_sendmany", &z_sendmany, false }, |
fc72c078 | 406 | { "wallet", "z_getoperationstatus", &z_getoperationstatus, true }, |
c1eae280 | 407 | { "wallet", "z_getoperationresult", &z_getoperationresult, true }, |
34f0001c | 408 | { "wallet", "z_listoperationids", &z_listoperationids, true }, |
c1c45943 | 409 | { "wallet", "z_getnewaddress", &z_getnewaddress, true }, |
e709997f | 410 | { "wallet", "z_listaddresses", &z_listaddresses, true }, |
c1c45943 | 411 | { "wallet", "z_exportkey", &z_exportkey, true }, |
92444edc S |
412 | { "wallet", "z_importkey", &z_importkey, true }, |
413 | { "wallet", "z_exportwallet", &z_exportwallet, true }, | |
414 | { "wallet", "z_importwallet", &z_importwallet, true } | |
48ba56cd | 415 | #endif // ENABLE_WALLET |
69d605f4 | 416 | }; |
69d605f4 | 417 | |
9862229d | 418 | CRPCTable::CRPCTable() |
dc42bf52 | 419 | { |
dc42bf52 JG |
420 | unsigned int vcidx; |
421 | for (vcidx = 0; vcidx < (sizeof(vRPCCommands) / sizeof(vRPCCommands[0])); vcidx++) | |
422 | { | |
e46704dd | 423 | const CRPCCommand *pcmd; |
69d605f4 | 424 | |
dc42bf52 JG |
425 | pcmd = &vRPCCommands[vcidx]; |
426 | mapCommands[pcmd->name] = pcmd; | |
427 | } | |
428 | } | |
69d605f4 | 429 | |
db954a65 | 430 | const CRPCCommand *CRPCTable::operator[](const std::string& name) const |
9862229d PW |
431 | { |
432 | map<string, const CRPCCommand*>::const_iterator it = mapCommands.find(name); | |
433 | if (it == mapCommands.end()) | |
434 | return NULL; | |
435 | return (*it).second; | |
436 | } | |
69d605f4 | 437 | |
69d605f4 | 438 | |
69d605f4 WL |
439 | bool HTTPAuthorized(map<string, string>& mapHeaders) |
440 | { | |
441 | string strAuth = mapHeaders["authorization"]; | |
442 | if (strAuth.substr(0,6) != "Basic ") | |
443 | return false; | |
444 | string strUserPass64 = strAuth.substr(6); boost::trim(strUserPass64); | |
445 | string strUserPass = DecodeBase64(strUserPass64); | |
42656ea2 | 446 | return TimingResistantEqual(strUserPass, strRPCUserColonPass); |
69d605f4 WL |
447 | } |
448 | ||
d014114d | 449 | void ErrorReply(std::ostream& stream, const UniValue& objError, const UniValue& id) |
69d605f4 WL |
450 | { |
451 | // Send error reply from json-rpc error object | |
285746d3 | 452 | int nStatus = HTTP_INTERNAL_SERVER_ERROR; |
69d605f4 | 453 | int code = find_value(objError, "code").get_int(); |
285746d3 WL |
454 | if (code == RPC_INVALID_REQUEST) nStatus = HTTP_BAD_REQUEST; |
455 | else if (code == RPC_METHOD_NOT_FOUND) nStatus = HTTP_NOT_FOUND; | |
ed21d5bd | 456 | string strReply = JSONRPCReply(NullUniValue, objError, id); |
96c52695 | 457 | stream << HTTPReply(nStatus, strReply, false) << std::flush; |
69d605f4 WL |
458 | } |
459 | ||
21bf3d25 | 460 | CNetAddr BoostAsioToCNetAddr(boost::asio::ip::address address) |
69d605f4 | 461 | { |
ee219125 | 462 | CNetAddr netaddr; |
43b6dafa GS |
463 | // Make sure that IPv4-compatible and IPv4-mapped IPv6 addresses are treated as IPv4 addresses |
464 | if (address.is_v6() | |
465 | && (address.to_v6().is_v4_compatible() | |
466 | || address.to_v6().is_v4_mapped())) | |
ee219125 WL |
467 | address = address.to_v6().to_v4(); |
468 | ||
469 | if(address.is_v4()) | |
470 | { | |
471 | boost::asio::ip::address_v4::bytes_type bytes = address.to_v4().to_bytes(); | |
472 | netaddr.SetRaw(NET_IPV4, &bytes[0]); | |
473 | } | |
474 | else | |
475 | { | |
476 | boost::asio::ip::address_v6::bytes_type bytes = address.to_v6().to_bytes(); | |
477 | netaddr.SetRaw(NET_IPV6, &bytes[0]); | |
478 | } | |
479 | return netaddr; | |
480 | } | |
481 | ||
482 | bool ClientAllowed(const boost::asio::ip::address& address) | |
483 | { | |
484 | CNetAddr netaddr = BoostAsioToCNetAddr(address); | |
485 | BOOST_FOREACH(const CSubNet &subnet, rpc_allow_subnets) | |
486 | if (subnet.Match(netaddr)) | |
69d605f4 WL |
487 | return true; |
488 | return false; | |
489 | } | |
490 | ||
a0780ba0 GS |
491 | template <typename Protocol> |
492 | class AcceptedConnectionImpl : public AcceptedConnection | |
493 | { | |
494 | public: | |
495 | AcceptedConnectionImpl( | |
a3241998 | 496 | boost::asio::io_service& io_service, |
a0780ba0 GS |
497 | ssl::context &context, |
498 | bool fUseSSL) : | |
499 | sslStream(io_service, context), | |
500 | _d(sslStream, fUseSSL), | |
501 | _stream(_d) | |
502 | { | |
503 | } | |
504 | ||
505 | virtual std::iostream& stream() | |
506 | { | |
507 | return _stream; | |
508 | } | |
509 | ||
510 | virtual std::string peer_address_to_string() const | |
511 | { | |
512 | return peer.address().to_string(); | |
513 | } | |
e9205293 | 514 | |
a0780ba0 GS |
515 | virtual void close() |
516 | { | |
517 | _stream.close(); | |
518 | } | |
519 | ||
520 | typename Protocol::endpoint peer; | |
a3241998 | 521 | boost::asio::ssl::stream<typename Protocol::socket> sslStream; |
a0780ba0 GS |
522 | |
523 | private: | |
524 | SSLIOStreamDevice<Protocol> _d; | |
a3241998 | 525 | boost::iostreams::stream< SSLIOStreamDevice<Protocol> > _stream; |
e9205293 DJS |
526 | }; |
527 | ||
21eb5ada | 528 | void ServiceConnection(AcceptedConnection *conn); |
69d605f4 | 529 | |
77920402 | 530 | //! Forward declaration required for RPCListen |
a0780ba0 GS |
531 | template <typename Protocol, typename SocketAcceptorService> |
532 | static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, | |
914dc012 GS |
533 | ssl::context& context, |
534 | bool fUseSSL, | |
1a445225 | 535 | boost::shared_ptr< AcceptedConnection > conn, |
914dc012 GS |
536 | const boost::system::error_code& error); |
537 | ||
538 | /** | |
539 | * Sets up I/O resources to accept and handle a new connection. | |
540 | */ | |
a0780ba0 GS |
541 | template <typename Protocol, typename SocketAcceptorService> |
542 | static void RPCListen(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, | |
914dc012 GS |
543 | ssl::context& context, |
544 | const bool fUseSSL) | |
545 | { | |
914dc012 | 546 | // Accept connection |
1a445225 | 547 | boost::shared_ptr< AcceptedConnectionImpl<Protocol> > conn(new AcceptedConnectionImpl<Protocol>(acceptor->get_io_service(), context, fUseSSL)); |
914dc012 GS |
548 | |
549 | acceptor->async_accept( | |
550 | conn->sslStream.lowest_layer(), | |
551 | conn->peer, | |
a0780ba0 | 552 | boost::bind(&RPCAcceptHandler<Protocol, SocketAcceptorService>, |
914dc012 GS |
553 | acceptor, |
554 | boost::ref(context), | |
555 | fUseSSL, | |
556 | conn, | |
0a0cd345 | 557 | _1)); |
914dc012 GS |
558 | } |
559 | ||
fb78cc23 | 560 | |
914dc012 GS |
561 | /** |
562 | * Accept and handle incoming connection. | |
563 | */ | |
a0780ba0 GS |
564 | template <typename Protocol, typename SocketAcceptorService> |
565 | static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, | |
914dc012 GS |
566 | ssl::context& context, |
567 | const bool fUseSSL, | |
1a445225 | 568 | boost::shared_ptr< AcceptedConnection > conn, |
914dc012 GS |
569 | const boost::system::error_code& error) |
570 | { | |
814efd6f | 571 | // Immediately start accepting new connections, except when we're cancelled or our socket is closed. |
a3241998 | 572 | if (error != boost::asio::error::operation_aborted && acceptor->is_open()) |
ad25804f | 573 | RPCListen(acceptor, context, fUseSSL); |
914dc012 | 574 | |
1a445225 | 575 | AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn.get()); |
a0780ba0 | 576 | |
914dc012 GS |
577 | if (error) |
578 | { | |
0a0cd345 WL |
579 | // TODO: Actually handle errors |
580 | LogPrintf("%s: Error: %s\n", __func__, error.message()); | |
914dc012 | 581 | } |
914dc012 GS |
582 | // Restrict callers by IP. It is important to |
583 | // do this before starting client thread, to filter out | |
584 | // certain DoS and misbehaving clients. | |
21eb5ada | 585 | else if (tcp_conn && !ClientAllowed(tcp_conn->peer.address())) |
914dc012 GS |
586 | { |
587 | // Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake. | |
588 | if (!fUseSSL) | |
16f33f16 | 589 | conn->stream() << HTTPError(HTTP_FORBIDDEN, false) << std::flush; |
1a445225 | 590 | conn->close(); |
914dc012 | 591 | } |
21eb5ada | 592 | else { |
1a445225 | 593 | ServiceConnection(conn.get()); |
21eb5ada | 594 | conn->close(); |
914dc012 | 595 | } |
914dc012 GS |
596 | } |
597 | ||
deb3572a WL |
598 | static ip::tcp::endpoint ParseEndpoint(const std::string &strEndpoint, int defaultPort) |
599 | { | |
600 | std::string addr; | |
601 | int port = defaultPort; | |
602 | SplitHostPort(strEndpoint, port, addr); | |
a3241998 | 603 | return ip::tcp::endpoint(boost::asio::ip::address::from_string(addr), port); |
deb3572a WL |
604 | } |
605 | ||
21eb5ada | 606 | void StartRPCThreads() |
69d605f4 | 607 | { |
ee219125 WL |
608 | rpc_allow_subnets.clear(); |
609 | rpc_allow_subnets.push_back(CSubNet("127.0.0.0/8")); // always allow IPv4 local subnet | |
610 | rpc_allow_subnets.push_back(CSubNet("::1")); // always allow IPv6 localhost | |
611 | if (mapMultiArgs.count("-rpcallowip")) | |
612 | { | |
613 | const vector<string>& vAllow = mapMultiArgs["-rpcallowip"]; | |
614 | BOOST_FOREACH(string strAllow, vAllow) | |
615 | { | |
616 | CSubNet subnet(strAllow); | |
617 | if(!subnet.IsValid()) | |
618 | { | |
619 | uiInterface.ThreadSafeMessageBox( | |
4278b1df | 620 | strprintf("Invalid -rpcallowip subnet specification: %s. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24).", strAllow), |
ee219125 WL |
621 | "", CClientUIInterface::MSG_ERROR); |
622 | StartShutdown(); | |
623 | return; | |
624 | } | |
625 | rpc_allow_subnets.push_back(subnet); | |
626 | } | |
627 | } | |
628 | std::string strAllowed; | |
629 | BOOST_FOREACH(const CSubNet &subnet, rpc_allow_subnets) | |
630 | strAllowed += subnet.ToString() + " "; | |
631 | LogPrint("rpc", "Allowing RPC connections from: %s\n", strAllowed); | |
632 | ||
e957192c | 633 | if (mapArgs["-rpcpassword"] == "") |
69d605f4 | 634 | { |
e957192c WL |
635 | LogPrintf("No rpcpassword set - using random cookie authentication\n"); |
636 | if (!GenerateAuthCookie(&strRPCUserColonPass)) { | |
637 | uiInterface.ThreadSafeMessageBox( | |
638 | _("Error: A fatal internal error occured, see debug.log for details"), // Same message as AbortNode | |
639 | "", CClientUIInterface::MSG_ERROR); | |
640 | StartShutdown(); | |
641 | return; | |
642 | } | |
643 | } else { | |
644 | strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; | |
69d605f4 WL |
645 | } |
646 | ||
21eb5ada | 647 | assert(rpc_io_service == NULL); |
a3241998 | 648 | rpc_io_service = new boost::asio::io_service(); |
21eb5ada | 649 | rpc_ssl_context = new ssl::context(*rpc_io_service, ssl::context::sslv23); |
69d605f4 | 650 | |
3260b4c0 | 651 | const bool fUseSSL = GetBoolArg("-rpcssl", false); |
fbf9df2e | 652 | |
69d605f4 WL |
653 | if (fUseSSL) |
654 | { | |
683dc400 | 655 | rpc_ssl_context->set_options(ssl::context::no_sslv2 | ssl::context::no_sslv3); |
93fb7489 | 656 | |
a3241998 CF |
657 | boost::filesystem::path pathCertFile(GetArg("-rpcsslcertificatechainfile", "server.cert")); |
658 | if (!pathCertFile.is_complete()) pathCertFile = boost::filesystem::path(GetDataDir()) / pathCertFile; | |
659 | if (boost::filesystem::exists(pathCertFile)) rpc_ssl_context->use_certificate_chain_file(pathCertFile.string()); | |
7d9d134b | 660 | else LogPrintf("ThreadRPCServer ERROR: missing server certificate file %s\n", pathCertFile.string()); |
93fb7489 | 661 | |
a3241998 CF |
662 | boost::filesystem::path pathPKFile(GetArg("-rpcsslprivatekeyfile", "server.pem")); |
663 | if (!pathPKFile.is_complete()) pathPKFile = boost::filesystem::path(GetDataDir()) / pathPKFile; | |
664 | if (boost::filesystem::exists(pathPKFile)) rpc_ssl_context->use_private_key_file(pathPKFile.string(), ssl::context::pem); | |
7d9d134b | 665 | else LogPrintf("ThreadRPCServer ERROR: missing server private key file %s\n", pathPKFile.string()); |
93fb7489 | 666 | |
1728bf08 | 667 | string strCiphers = GetArg("-rpcsslciphers", "TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH"); |
21eb5ada | 668 | SSL_CTX_set_cipher_list(rpc_ssl_context->impl(), strCiphers.c_str()); |
69d605f4 | 669 | } |
69d605f4 | 670 | |
deb3572a WL |
671 | std::vector<ip::tcp::endpoint> vEndpoints; |
672 | bool bBindAny = false; | |
84ce18ca | 673 | int defaultPort = GetArg("-rpcport", BaseParams().RPCPort()); |
deb3572a WL |
674 | if (!mapArgs.count("-rpcallowip")) // Default to loopback if not allowing external IPs |
675 | { | |
a3241998 CF |
676 | vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v6::loopback(), defaultPort)); |
677 | vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v4::loopback(), defaultPort)); | |
deb3572a WL |
678 | if (mapArgs.count("-rpcbind")) |
679 | { | |
680 | LogPrintf("WARNING: option -rpcbind was ignored because -rpcallowip was not specified, refusing to allow everyone to connect\n"); | |
681 | } | |
682 | } else if (mapArgs.count("-rpcbind")) // Specific bind address | |
683 | { | |
684 | BOOST_FOREACH(const std::string &addr, mapMultiArgs["-rpcbind"]) | |
685 | { | |
686 | try { | |
687 | vEndpoints.push_back(ParseEndpoint(addr, defaultPort)); | |
688 | } | |
27df4123 | 689 | catch (const boost::system::system_error&) |
deb3572a WL |
690 | { |
691 | uiInterface.ThreadSafeMessageBox( | |
692 | strprintf(_("Could not parse -rpcbind value %s as network address"), addr), | |
693 | "", CClientUIInterface::MSG_ERROR); | |
694 | StartShutdown(); | |
695 | return; | |
696 | } | |
697 | } | |
698 | } else { // No specific bind address specified, bind to any | |
a3241998 CF |
699 | vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v6::any(), defaultPort)); |
700 | vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v4::any(), defaultPort)); | |
deb3572a | 701 | // Prefer making the socket dual IPv6/IPv4 instead of binding |
45bfa137 | 702 | // to both addresses separately. |
deb3572a WL |
703 | bBindAny = true; |
704 | } | |
7cf3d2cc | 705 | |
c1d79812 PW |
706 | bool fListening = false; |
707 | std::string strerr; | |
8db17607 | 708 | std::string straddress; |
deb3572a | 709 | BOOST_FOREACH(const ip::tcp::endpoint &endpoint, vEndpoints) |
c1ecab81 | 710 | { |
deb3572a | 711 | try { |
a3241998 | 712 | boost::asio::ip::address bindAddress = endpoint.address(); |
8db17607 CF |
713 | straddress = bindAddress.to_string(); |
714 | LogPrintf("Binding RPC on address %s port %i (IPv4+IPv6 bind any: %i)\n", straddress, endpoint.port(), bBindAny); | |
715 | boost::system::error_code v6_only_error; | |
716 | boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(*rpc_io_service)); | |
717 | ||
ad25804f GS |
718 | acceptor->open(endpoint.protocol()); |
719 | acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); | |
deb3572a WL |
720 | |
721 | // Try making the socket dual IPv6/IPv4 when listening on the IPv6 "any" address | |
722 | acceptor->set_option(boost::asio::ip::v6_only( | |
a3241998 | 723 | !bBindAny || bindAddress != boost::asio::ip::address_v6::any()), v6_only_error); |
deb3572a | 724 | |
ad25804f GS |
725 | acceptor->bind(endpoint); |
726 | acceptor->listen(socket_base::max_connections); | |
727 | ||
21eb5ada | 728 | RPCListen(acceptor, *rpc_ssl_context, fUseSSL); |
c1d79812 PW |
729 | |
730 | fListening = true; | |
6afa4932 | 731 | rpc_acceptors.push_back(acceptor); |
77920402 | 732 | // If dual IPv6/IPv4 bind successful, skip binding to IPv4 separately |
a3241998 | 733 | if(bBindAny && bindAddress == boost::asio::ip::address_v6::any() && !v6_only_error) |
deb3572a WL |
734 | break; |
735 | } | |
27df4123 | 736 | catch (const boost::system::system_error& e) |
deb3572a | 737 | { |
8db17607 CF |
738 | LogPrintf("ERROR: Binding RPC on address %s port %i failed: %s\n", straddress, endpoint.port(), e.what()); |
739 | strerr = strprintf(_("An error occurred while setting up the RPC address %s port %u for listening: %s"), straddress, endpoint.port(), e.what()); | |
c1ecab81 | 740 | } |
c1d79812 PW |
741 | } |
742 | ||
743 | if (!fListening) { | |
5350ea41 | 744 | uiInterface.ThreadSafeMessageBox(strerr, "", CClientUIInterface::MSG_ERROR); |
07368a9e | 745 | StartShutdown(); |
c1ecab81 GS |
746 | return; |
747 | } | |
69d605f4 | 748 | |
21eb5ada GA |
749 | rpc_worker_group = new boost::thread_group(); |
750 | for (int i = 0; i < GetArg("-rpcthreads", 4); i++) | |
a3241998 | 751 | rpc_worker_group->create_thread(boost::bind(&boost::asio::io_service::run, rpc_io_service)); |
ff6a7af1 | 752 | fRPCRunning = true; |
4401b2d7 | 753 | g_rpcSignals.Started(); |
fc72c078 | 754 | |
008fccfa | 755 | // Launch one async rpc worker. The ability to launch multiple workers is not recommended at present and thus the option is disabled. |
f86f625d | 756 | getAsyncRPCQueue()->addWorker(); |
008fccfa | 757 | /* |
8d08172d S |
758 | int n = GetArg("-rpcasyncthreads", 1); |
759 | if (n<1) { | |
760 | LogPrintf("ERROR: Invalid value %d for -rpcasyncthreads. Must be at least 1.\n", n); | |
761 | strerr = strprintf(_("An error occurred while setting up the Async RPC threads, invalid parameter value of %d (must be at least 1)."), n); | |
762 | uiInterface.ThreadSafeMessageBox(strerr, "", CClientUIInterface::MSG_ERROR); | |
763 | StartShutdown(); | |
764 | return; | |
765 | } | |
766 | for (int i = 0; i < n; i++) | |
f86f625d | 767 | getAsyncRPCQueue()->addWorker(); |
008fccfa | 768 | */ |
21eb5ada GA |
769 | } |
770 | ||
a8db31c8 WL |
771 | void StartDummyRPCThread() |
772 | { | |
773 | if(rpc_io_service == NULL) | |
774 | { | |
a3241998 | 775 | rpc_io_service = new boost::asio::io_service(); |
a8db31c8 WL |
776 | /* Create dummy "work" to keep the thread from exiting when no timeouts active, |
777 | * see http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/reference/io_service.html#boost_asio.reference.io_service.stopping_the_io_service_from_running_out_of_work */ | |
a3241998 | 778 | rpc_dummy_work = new boost::asio::io_service::work(*rpc_io_service); |
a8db31c8 | 779 | rpc_worker_group = new boost::thread_group(); |
a3241998 | 780 | rpc_worker_group->create_thread(boost::bind(&boost::asio::io_service::run, rpc_io_service)); |
ff6a7af1 | 781 | fRPCRunning = true; |
a8db31c8 WL |
782 | } |
783 | } | |
784 | ||
21eb5ada GA |
785 | void StopRPCThreads() |
786 | { | |
787 | if (rpc_io_service == NULL) return; | |
ff6a7af1 LD |
788 | // Set this to false first, so that longpolling loops will exit when woken up |
789 | fRPCRunning = false; | |
21eb5ada | 790 | |
cef44941 WL |
791 | // First, cancel all timers and acceptors |
792 | // This is not done automatically by ->stop(), and in some cases the destructor of | |
a3241998 | 793 | // boost::asio::io_service can hang if this is skipped. |
33e5b429 | 794 | boost::system::error_code ec; |
cef44941 | 795 | BOOST_FOREACH(const boost::shared_ptr<ip::tcp::acceptor> &acceptor, rpc_acceptors) |
33e5b429 WL |
796 | { |
797 | acceptor->cancel(ec); | |
798 | if (ec) | |
7ff9d122 | 799 | LogPrintf("%s: Warning: %s when cancelling acceptor\n", __func__, ec.message()); |
33e5b429 | 800 | } |
cef44941 WL |
801 | rpc_acceptors.clear(); |
802 | BOOST_FOREACH(const PAIRTYPE(std::string, boost::shared_ptr<deadline_timer>) &timer, deadlineTimers) | |
33e5b429 WL |
803 | { |
804 | timer.second->cancel(ec); | |
805 | if (ec) | |
7ff9d122 | 806 | LogPrintf("%s: Warning: %s when cancelling timer\n", __func__, ec.message()); |
33e5b429 | 807 | } |
92f2c1fe | 808 | deadlineTimers.clear(); |
cef44941 | 809 | |
e957192c WL |
810 | DeleteAuthCookie(); |
811 | ||
21eb5ada | 812 | rpc_io_service->stop(); |
4401b2d7 | 813 | g_rpcSignals.Stopped(); |
b2ba55c4 GA |
814 | if (rpc_worker_group != NULL) |
815 | rpc_worker_group->join_all(); | |
a8db31c8 | 816 | delete rpc_dummy_work; rpc_dummy_work = NULL; |
21eb5ada GA |
817 | delete rpc_worker_group; rpc_worker_group = NULL; |
818 | delete rpc_ssl_context; rpc_ssl_context = NULL; | |
819 | delete rpc_io_service; rpc_io_service = NULL; | |
fc72c078 S |
820 | |
821 | // Tells async queue to cancel all operations and shutdown. | |
3b54bf58 | 822 | LogPrintf("%s: waiting for async rpc workers to stop\n", __func__); |
f86f625d | 823 | getAsyncRPCQueue()->closeAndWait(); |
e9205293 DJS |
824 | } |
825 | ||
ff6a7af1 LD |
826 | bool IsRPCRunning() |
827 | { | |
828 | return fRPCRunning; | |
829 | } | |
830 | ||
af82884a DK |
831 | void SetRPCWarmupStatus(const std::string& newStatus) |
832 | { | |
833 | LOCK(cs_rpcWarmup); | |
834 | rpcWarmupStatus = newStatus; | |
835 | } | |
836 | ||
837 | void SetRPCWarmupFinished() | |
838 | { | |
839 | LOCK(cs_rpcWarmup); | |
840 | assert(fRPCInWarmup); | |
841 | fRPCInWarmup = false; | |
842 | } | |
843 | ||
78bdc810 JS |
844 | bool RPCIsInWarmup(std::string *outStatus) |
845 | { | |
846 | LOCK(cs_rpcWarmup); | |
847 | if (outStatus) | |
848 | *outStatus = rpcWarmupStatus; | |
849 | return fRPCInWarmup; | |
850 | } | |
851 | ||
92f2c1fe GA |
852 | void RPCRunHandler(const boost::system::error_code& err, boost::function<void(void)> func) |
853 | { | |
854 | if (!err) | |
855 | func(); | |
856 | } | |
857 | ||
51ed9ec9 | 858 | void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds) |
92f2c1fe GA |
859 | { |
860 | assert(rpc_io_service != NULL); | |
861 | ||
862 | if (deadlineTimers.count(name) == 0) | |
863 | { | |
864 | deadlineTimers.insert(make_pair(name, | |
865 | boost::shared_ptr<deadline_timer>(new deadline_timer(*rpc_io_service)))); | |
866 | } | |
a3241998 | 867 | deadlineTimers[name]->expires_from_now(boost::posix_time::seconds(nSeconds)); |
92f2c1fe GA |
868 | deadlineTimers[name]->async_wait(boost::bind(RPCRunHandler, _1, func)); |
869 | } | |
870 | ||
c6494d82 JG |
871 | class JSONRequest |
872 | { | |
873 | public: | |
851f58f9 | 874 | UniValue id; |
c6494d82 | 875 | string strMethod; |
851f58f9 | 876 | UniValue params; |
c6494d82 | 877 | |
ed21d5bd | 878 | JSONRequest() { id = NullUniValue; } |
d014114d | 879 | void parse(const UniValue& valRequest); |
c6494d82 JG |
880 | }; |
881 | ||
d014114d | 882 | void JSONRequest::parse(const UniValue& valRequest) |
c6494d82 JG |
883 | { |
884 | // Parse request | |
ed21d5bd | 885 | if (!valRequest.isObject()) |
738835d7 | 886 | throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object"); |
d014114d | 887 | const UniValue& request = valRequest.get_obj(); |
c6494d82 JG |
888 | |
889 | // Parse id now so errors from here on will have the id | |
890 | id = find_value(request, "id"); | |
891 | ||
892 | // Parse method | |
851f58f9 | 893 | UniValue valMethod = find_value(request, "method"); |
ed21d5bd | 894 | if (valMethod.isNull()) |
738835d7 | 895 | throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method"); |
ed21d5bd | 896 | if (!valMethod.isStr()) |
738835d7 | 897 | throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string"); |
c6494d82 | 898 | strMethod = valMethod.get_str(); |
cf0c47b2 | 899 | if (strMethod != "getblocktemplate") |
28d4cff0 | 900 | LogPrint("rpc", "ThreadRPCServer method=%s\n", SanitizeString(strMethod)); |
c6494d82 JG |
901 | |
902 | // Parse params | |
851f58f9 | 903 | UniValue valParams = find_value(request, "params"); |
ed21d5bd | 904 | if (valParams.isArray()) |
c6494d82 | 905 | params = valParams.get_array(); |
ed21d5bd | 906 | else if (valParams.isNull()) |
d014114d | 907 | params = UniValue(UniValue::VARR); |
c6494d82 | 908 | else |
738835d7 | 909 | throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array"); |
c6494d82 JG |
910 | } |
911 | ||
fb78cc23 | 912 | |
d014114d | 913 | static UniValue JSONRPCExecOne(const UniValue& req) |
61338901 | 914 | { |
38fc4b70 | 915 | UniValue rpc_result(UniValue::VOBJ); |
61338901 JG |
916 | |
917 | JSONRequest jreq; | |
918 | try { | |
919 | jreq.parse(req); | |
920 | ||
851f58f9 | 921 | UniValue result = tableRPC.execute(jreq.strMethod, jreq.params); |
ed21d5bd | 922 | rpc_result = JSONRPCReplyObj(result, NullUniValue, jreq.id); |
61338901 | 923 | } |
d014114d | 924 | catch (const UniValue& objError) |
61338901 | 925 | { |
ed21d5bd | 926 | rpc_result = JSONRPCReplyObj(NullUniValue, objError, jreq.id); |
61338901 | 927 | } |
27df4123 | 928 | catch (const std::exception& e) |
61338901 | 929 | { |
ed21d5bd | 930 | rpc_result = JSONRPCReplyObj(NullUniValue, |
738835d7 | 931 | JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); |
61338901 JG |
932 | } |
933 | ||
934 | return rpc_result; | |
935 | } | |
936 | ||
d014114d | 937 | static string JSONRPCExecBatch(const UniValue& vReq) |
61338901 | 938 | { |
bf3f5602 | 939 | UniValue ret(UniValue::VARR); |
cc71666a | 940 | for (size_t reqIdx = 0; reqIdx < vReq.size(); reqIdx++) |
61338901 JG |
941 | ret.push_back(JSONRPCExecOne(vReq[reqIdx])); |
942 | ||
ed21d5bd | 943 | return ret.write() + "\n"; |
61338901 JG |
944 | } |
945 | ||
854d0130 JG |
946 | static bool HTTPReq_JSONRPC(AcceptedConnection *conn, |
947 | string& strRequest, | |
948 | map<string, string>& mapHeaders, | |
949 | bool fRun) | |
950 | { | |
951 | // Check authorization | |
952 | if (mapHeaders.count("authorization") == 0) | |
953 | { | |
16f33f16 | 954 | conn->stream() << HTTPError(HTTP_UNAUTHORIZED, false) << std::flush; |
854d0130 JG |
955 | return false; |
956 | } | |
957 | ||
958 | if (!HTTPAuthorized(mapHeaders)) | |
959 | { | |
960 | LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string()); | |
01094bd0 | 961 | /* Deter brute-forcing |
b05a89b2 LD |
962 | We don't support exposing the RPC port, so this shouldn't result |
963 | in a DoS. */ | |
01094bd0 | 964 | MilliSleep(250); |
854d0130 | 965 | |
16f33f16 | 966 | conn->stream() << HTTPError(HTTP_UNAUTHORIZED, false) << std::flush; |
854d0130 JG |
967 | return false; |
968 | } | |
969 | ||
970 | JSONRequest jreq; | |
971 | try | |
972 | { | |
973 | // Parse request | |
851f58f9 | 974 | UniValue valRequest; |
ed21d5bd | 975 | if (!valRequest.read(strRequest)) |
854d0130 JG |
976 | throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); |
977 | ||
978 | string strReply; | |
979 | ||
980 | // singleton request | |
ed21d5bd | 981 | if (valRequest.isObject()) { |
854d0130 JG |
982 | jreq.parse(valRequest); |
983 | ||
851f58f9 | 984 | UniValue result = tableRPC.execute(jreq.strMethod, jreq.params); |
854d0130 JG |
985 | |
986 | // Send reply | |
ed21d5bd | 987 | strReply = JSONRPCReply(result, NullUniValue, jreq.id); |
854d0130 JG |
988 | |
989 | // array of requests | |
ed21d5bd | 990 | } else if (valRequest.isArray()) |
854d0130 JG |
991 | strReply = JSONRPCExecBatch(valRequest.get_array()); |
992 | else | |
993 | throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); | |
994 | ||
e17151ad | 995 | conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, strReply.size()) << strReply << std::flush; |
854d0130 | 996 | } |
d014114d | 997 | catch (const UniValue& objError) |
854d0130 JG |
998 | { |
999 | ErrorReply(conn->stream(), objError, jreq.id); | |
1000 | return false; | |
1001 | } | |
27df4123 | 1002 | catch (const std::exception& e) |
854d0130 JG |
1003 | { |
1004 | ErrorReply(conn->stream(), JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); | |
1005 | return false; | |
1006 | } | |
1007 | return true; | |
1008 | } | |
1009 | ||
21eb5ada | 1010 | void ServiceConnection(AcceptedConnection *conn) |
e9205293 | 1011 | { |
96c52695 | 1012 | bool fRun = true; |
d138598f | 1013 | while (fRun && !ShutdownRequested()) |
21eb5ada | 1014 | { |
2306dc4b | 1015 | int nProto = 0; |
69d605f4 | 1016 | map<string, string> mapHeaders; |
fcf234fc JG |
1017 | string strRequest, strMethod, strURI; |
1018 | ||
1019 | // Read HTTP request line | |
1020 | if (!ReadHTTPRequestLine(conn->stream(), nProto, strMethod, strURI)) | |
1021 | break; | |
69d605f4 | 1022 | |
2306dc4b | 1023 | // Read HTTP message headers and body |
733177eb | 1024 | ReadHTTPMessage(conn->stream(), mapHeaders, strRequest, nProto, MAX_SIZE); |
69d605f4 | 1025 | |
854d0130 | 1026 | // HTTP Keep-Alive is false; close connection immediately |
7d2cb485 | 1027 | if ((mapHeaders["connection"] == "close") || (!GetBoolArg("-rpckeepalive", true))) |
96c52695 | 1028 | fRun = false; |
69d605f4 | 1029 | |
e2655e0a | 1030 | // Process via JSON-RPC API |
854d0130 JG |
1031 | if (strURI == "/") { |
1032 | if (!HTTPReq_JSONRPC(conn, strRequest, mapHeaders, fRun)) | |
1033 | break; | |
e2655e0a JG |
1034 | |
1035 | // Process via HTTP REST API | |
5dc713bf | 1036 | } else if (strURI.substr(0, 6) == "/rest/" && GetBoolArg("-rest", false)) { |
97ee8665 | 1037 | if (!HTTPReq_REST(conn, strURI, strRequest, mapHeaders, fRun)) |
e2655e0a JG |
1038 | break; |
1039 | ||
40a158e1 | 1040 | } else { |
7715c847 | 1041 | conn->stream() << HTTPError(HTTP_NOT_FOUND, false) << std::flush; |
e9205293 | 1042 | break; |
69d605f4 WL |
1043 | } |
1044 | } | |
1045 | } | |
1046 | ||
851f58f9 | 1047 | UniValue CRPCTable::execute(const std::string &strMethod, const UniValue ¶ms) const |
460c51fd | 1048 | { |
483672f7 FV |
1049 | // Return immediately if in warmup |
1050 | { | |
1051 | LOCK(cs_rpcWarmup); | |
1052 | if (fRPCInWarmup) | |
1053 | throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); | |
1054 | } | |
1055 | ||
460c51fd WL |
1056 | // Find method |
1057 | const CRPCCommand *pcmd = tableRPC[strMethod]; | |
1058 | if (!pcmd) | |
738835d7 | 1059 | throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); |
69d605f4 | 1060 | |
4401b2d7 | 1061 | g_rpcSignals.PreCommand(*pcmd); |
460c51fd WL |
1062 | |
1063 | try | |
1064 | { | |
1065 | // Execute | |
4401b2d7 | 1066 | return pcmd->actor(params, false); |
460c51fd | 1067 | } |
27df4123 | 1068 | catch (const std::exception& e) |
460c51fd | 1069 | { |
738835d7 | 1070 | throw JSONRPCError(RPC_MISC_ERROR, e.what()); |
460c51fd | 1071 | } |
4401b2d7 EL |
1072 | |
1073 | g_rpcSignals.PostCommand(*pcmd); | |
460c51fd | 1074 | } |
69d605f4 | 1075 | |
db954a65 PK |
1076 | std::string HelpExampleCli(const std::string& methodname, const std::string& args) |
1077 | { | |
58c4c0bb | 1078 | return "> zcash-cli " + methodname + " " + args + "\n"; |
bbb09365 WL |
1079 | } |
1080 | ||
db954a65 PK |
1081 | std::string HelpExampleRpc(const std::string& methodname, const std::string& args) |
1082 | { | |
bbb09365 | 1083 | return "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\":\"curltest\", " |
3985a40d | 1084 | "\"method\": \"" + methodname + "\", \"params\": [" + args + "] }' -H 'content-type: text/plain;' http://127.0.0.1:8232/\n"; |
bbb09365 WL |
1085 | } |
1086 | ||
e46704dd | 1087 | const CRPCTable tableRPC; |
fc72c078 S |
1088 | |
1089 | // Return async rpc queue | |
1090 | std::shared_ptr<AsyncRPCQueue> getAsyncRPCQueue() | |
1091 | { | |
f86f625d | 1092 | return AsyncRPCQueue::sharedInstance(); |
fc72c078 | 1093 | } |