]>
Commit | Line | Data |
---|---|---|
69d605f4 | 1 | // Copyright (c) 2010 Satoshi Nakamoto |
57702541 | 2 | // Copyright (c) 2009-2014 The Bitcoin developers |
69d605f4 | 3 | // Distributed under the MIT/X11 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" |
51ed9ec9 | 10 | #include "main.h" |
48ba56cd | 11 | #include "ui_interface.h" |
c037531d | 12 | #include "util.h" |
48ba56cd | 13 | #ifdef ENABLE_WALLET |
51ed9ec9 | 14 | #include "wallet.h" |
48ba56cd | 15 | #endif |
51ed9ec9 | 16 | |
92f2c1fe | 17 | #include <boost/algorithm/string.hpp> |
69d605f4 | 18 | #include <boost/asio.hpp> |
92f2c1fe | 19 | #include <boost/asio/ssl.hpp> |
914dc012 | 20 | #include <boost/bind.hpp> |
95d888a6 | 21 | #include <boost/filesystem.hpp> |
a0780ba0 | 22 | #include <boost/foreach.hpp> |
69d605f4 WL |
23 | #include <boost/iostreams/concepts.hpp> |
24 | #include <boost/iostreams/stream.hpp> | |
914dc012 | 25 | #include <boost/shared_ptr.hpp> |
51ed9ec9 | 26 | #include "json/json_spirit_writer_template.h" |
5ce4c2a2 | 27 | |
69d605f4 WL |
28 | using namespace std; |
29 | using namespace boost; | |
30 | using namespace boost::asio; | |
31 | using namespace json_spirit; | |
32 | ||
f81ce5bd GA |
33 | static std::string strRPCUserColonPass; |
34 | ||
21eb5ada GA |
35 | // These are created by StartRPCThreads, destroyed in StopRPCThreads |
36 | static asio::io_service* rpc_io_service = NULL; | |
92f2c1fe | 37 | static map<string, boost::shared_ptr<deadline_timer> > deadlineTimers; |
21eb5ada GA |
38 | static ssl::context* rpc_ssl_context = NULL; |
39 | static boost::thread_group* rpc_worker_group = NULL; | |
a8db31c8 | 40 | static boost::asio::io_service::work *rpc_dummy_work = NULL; |
ee219125 | 41 | static std::vector<CSubNet> rpc_allow_subnets; //!< List of subnets to allow RPC connections from |
e9205293 | 42 | |
899d373b | 43 | void RPCTypeCheck(const Array& params, |
cc6dfd1f GA |
44 | const list<Value_type>& typesExpected, |
45 | bool fAllowNull) | |
899d373b | 46 | { |
dab9fa7f | 47 | unsigned int i = 0; |
899d373b GA |
48 | BOOST_FOREACH(Value_type t, typesExpected) |
49 | { | |
50 | if (params.size() <= i) | |
51 | break; | |
52 | ||
cc6dfd1f GA |
53 | const Value& v = params[i]; |
54 | if (!((v.type() == t) || (fAllowNull && (v.type() == null_type)))) | |
899d373b GA |
55 | { |
56 | string err = strprintf("Expected type %s, got %s", | |
57 | Value_type_name[t], Value_type_name[v.type()]); | |
738835d7 | 58 | throw JSONRPCError(RPC_TYPE_ERROR, err); |
899d373b GA |
59 | } |
60 | i++; | |
61 | } | |
62 | } | |
63 | ||
64 | void RPCTypeCheck(const Object& o, | |
cc6dfd1f GA |
65 | const map<string, Value_type>& typesExpected, |
66 | bool fAllowNull) | |
899d373b GA |
67 | { |
68 | BOOST_FOREACH(const PAIRTYPE(string, Value_type)& t, typesExpected) | |
69 | { | |
70 | const Value& v = find_value(o, t.first); | |
cc6dfd1f | 71 | if (!fAllowNull && v.type() == null_type) |
7d9d134b | 72 | throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing %s", t.first)); |
cc6dfd1f GA |
73 | |
74 | if (!((v.type() == t.second) || (fAllowNull && (v.type() == null_type)))) | |
899d373b GA |
75 | { |
76 | string err = strprintf("Expected type %s for %s, got %s", | |
7d9d134b | 77 | Value_type_name[t.second], t.first, Value_type_name[v.type()]); |
738835d7 | 78 | throw JSONRPCError(RPC_TYPE_ERROR, err); |
899d373b GA |
79 | } |
80 | } | |
81 | } | |
82 | ||
51ed9ec9 | 83 | int64_t AmountFromValue(const Value& value) |
69d605f4 WL |
84 | { |
85 | double dAmount = value.get_real(); | |
86 | if (dAmount <= 0.0 || dAmount > 21000000.0) | |
738835d7 | 87 | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); |
51ed9ec9 | 88 | int64_t nAmount = roundint64(dAmount * COIN); |
69d605f4 | 89 | if (!MoneyRange(nAmount)) |
738835d7 | 90 | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); |
69d605f4 WL |
91 | return nAmount; |
92 | } | |
93 | ||
51ed9ec9 | 94 | Value ValueFromAmount(int64_t amount) |
69d605f4 WL |
95 | { |
96 | return (double)amount / (double)COIN; | |
97 | } | |
98 | ||
bdab0cf5 | 99 | std::string HexBits(unsigned int nBits) |
34f87889 LD |
100 | { |
101 | union { | |
102 | int32_t nBits; | |
103 | char cBits[4]; | |
104 | } uBits; | |
105 | uBits.nBits = htonl((int32_t)nBits); | |
106 | return HexStr(BEGIN(uBits.cBits), END(uBits.cBits)); | |
107 | } | |
108 | ||
463c9710 PT |
109 | uint256 ParseHashV(const Value& v, string strName) |
110 | { | |
111 | string strHex; | |
112 | if (v.type() == str_type) | |
113 | strHex = v.get_str(); | |
114 | if (!IsHex(strHex)) // Note: IsHex("") is false | |
115 | throw JSONRPCError(RPC_INVALID_PARAMETER, strName+" must be hexadecimal string (not '"+strHex+"')"); | |
116 | uint256 result; | |
117 | result.SetHex(strHex); | |
118 | return result; | |
119 | } | |
120 | uint256 ParseHashO(const Object& o, string strKey) | |
121 | { | |
122 | return ParseHashV(find_value(o, strKey), strKey); | |
123 | } | |
124 | vector<unsigned char> ParseHexV(const Value& v, string strName) | |
125 | { | |
126 | string strHex; | |
127 | if (v.type() == str_type) | |
128 | strHex = v.get_str(); | |
129 | if (!IsHex(strHex)) | |
130 | throw JSONRPCError(RPC_INVALID_PARAMETER, strName+" must be hexadecimal string (not '"+strHex+"')"); | |
131 | return ParseHex(strHex); | |
132 | } | |
133 | vector<unsigned char> ParseHexO(const Object& o, string strKey) | |
134 | { | |
135 | return ParseHexV(find_value(o, strKey), strKey); | |
136 | } | |
69d605f4 | 137 | |
74335bd3 | 138 | |
69d605f4 WL |
139 | /// |
140 | /// Note: This interface may still be subject to change. | |
141 | /// | |
142 | ||
9862229d | 143 | string CRPCTable::help(string strCommand) const |
69d605f4 | 144 | { |
69d605f4 WL |
145 | string strRet; |
146 | set<rpcfn_type> setDone; | |
9862229d | 147 | for (map<string, const CRPCCommand*>::const_iterator mi = mapCommands.begin(); mi != mapCommands.end(); ++mi) |
69d605f4 | 148 | { |
9862229d | 149 | const CRPCCommand *pcmd = mi->second; |
dc42bf52 | 150 | string strMethod = mi->first; |
69d605f4 | 151 | // We already filter duplicates, but these deprecated screw up the sort order |
47f48a65 | 152 | if (strMethod.find("label") != string::npos) |
69d605f4 WL |
153 | continue; |
154 | if (strCommand != "" && strMethod != strCommand) | |
155 | continue; | |
48ba56cd | 156 | #ifdef ENABLE_WALLET |
b0730874 JG |
157 | if (pcmd->reqWallet && !pwalletMain) |
158 | continue; | |
48ba56cd | 159 | #endif |
b0730874 | 160 | |
69d605f4 WL |
161 | try |
162 | { | |
163 | Array params; | |
dc42bf52 | 164 | rpcfn_type pfn = pcmd->actor; |
69d605f4 WL |
165 | if (setDone.insert(pfn).second) |
166 | (*pfn)(params, true); | |
167 | } | |
168 | catch (std::exception& e) | |
169 | { | |
170 | // Help text is returned in an exception | |
171 | string strHelp = string(e.what()); | |
172 | if (strCommand == "") | |
ab9dc75a | 173 | if (strHelp.find('\n') != string::npos) |
69d605f4 WL |
174 | strHelp = strHelp.substr(0, strHelp.find('\n')); |
175 | strRet += strHelp + "\n"; | |
176 | } | |
177 | } | |
178 | if (strRet == "") | |
7d9d134b | 179 | strRet = strprintf("help: unknown command: %s\n", strCommand); |
69d605f4 WL |
180 | strRet = strRet.substr(0,strRet.size()-1); |
181 | return strRet; | |
182 | } | |
183 | ||
9862229d PW |
184 | Value help(const Array& params, bool fHelp) |
185 | { | |
186 | if (fHelp || params.size() > 1) | |
187 | throw runtime_error( | |
a6099ef3 | 188 | "help ( \"command\" )\n" |
189 | "\nList all commands, or get help for a specified command.\n" | |
190 | "\nArguments:\n" | |
191 | "1. \"command\" (string, optional) The command to get help on\n" | |
192 | "\nResult:\n" | |
193 | "\"text\" (string) The help text\n" | |
194 | ); | |
9862229d PW |
195 | |
196 | string strCommand; | |
197 | if (params.size() > 0) | |
198 | strCommand = params[0].get_str(); | |
199 | ||
200 | return tableRPC.help(strCommand); | |
201 | } | |
202 | ||
69d605f4 WL |
203 | |
204 | Value stop(const Array& params, bool fHelp) | |
205 | { | |
6e65420b | 206 | // Accept the deprecated and ignored 'detach' boolean argument |
3731f578 | 207 | if (fHelp || params.size() > 1) |
69d605f4 | 208 | throw runtime_error( |
92467073 | 209 | "stop\n" |
a6099ef3 | 210 | "\nStop Bitcoin server."); |
69d605f4 | 211 | // Shutdown will take long enough that the response should get back |
9247134e | 212 | StartShutdown(); |
ff0ee876 | 213 | return "Bitcoin server stopping"; |
69d605f4 WL |
214 | } |
215 | ||
216 | ||
69d605f4 WL |
217 | |
218 | // | |
219 | // Call Table | |
220 | // | |
221 | ||
dc42bf52 | 222 | |
e46704dd | 223 | static const CRPCCommand vRPCCommands[] = |
b0730874 JG |
224 | { // name actor (function) okSafeMode threadSafe reqWallet |
225 | // ------------------------ ----------------------- ---------- ---------- --------- | |
ab88ed93 WL |
226 | /* Overall control/query calls */ |
227 | { "getinfo", &getinfo, true, false, false }, /* uses wallet if enabled */ | |
b0730874 JG |
228 | { "help", &help, true, true, false }, |
229 | { "stop", &stop, true, true, false }, | |
ab88ed93 WL |
230 | |
231 | /* P2P networking */ | |
d387b8ec | 232 | { "getnetworkinfo", &getnetworkinfo, true, false, false }, |
b0730874 JG |
233 | { "addnode", &addnode, true, true, false }, |
234 | { "getaddednodeinfo", &getaddednodeinfo, true, true, false }, | |
ab88ed93 | 235 | { "getconnectioncount", &getconnectioncount, true, false, false }, |
ce14345a | 236 | { "getnettotals", &getnettotals, true, true, false }, |
ab88ed93 WL |
237 | { "getpeerinfo", &getpeerinfo, true, false, false }, |
238 | { "ping", &ping, true, false, false }, | |
239 | ||
240 | /* Block chain and UTXO */ | |
d387b8ec | 241 | { "getblockchaininfo", &getblockchaininfo, true, false, false }, |
ab88ed93 WL |
242 | { "getbestblockhash", &getbestblockhash, true, false, false }, |
243 | { "getblockcount", &getblockcount, true, false, false }, | |
48ba56cd WL |
244 | { "getblock", &getblock, false, false, false }, |
245 | { "getblockhash", &getblockhash, false, false, false }, | |
ab88ed93 WL |
246 | { "getdifficulty", &getdifficulty, true, false, false }, |
247 | { "getrawmempool", &getrawmempool, true, false, false }, | |
48ba56cd | 248 | { "gettxout", &gettxout, true, false, false }, |
ab88ed93 | 249 | { "gettxoutsetinfo", &gettxoutsetinfo, true, false, false }, |
48ba56cd WL |
250 | { "verifychain", &verifychain, true, false, false }, |
251 | ||
4a85e067 | 252 | /* Mining */ |
4a85e067 | 253 | { "getblocktemplate", &getblocktemplate, true, false, false }, |
ab88ed93 WL |
254 | { "getmininginfo", &getmininginfo, true, false, false }, |
255 | { "getnetworkhashps", &getnetworkhashps, true, false, false }, | |
4a85e067 | 256 | { "submitblock", &submitblock, false, false, false }, |
ab88ed93 WL |
257 | |
258 | /* Raw transactions */ | |
259 | { "createrawtransaction", &createrawtransaction, false, false, false }, | |
260 | { "decoderawtransaction", &decoderawtransaction, false, false, false }, | |
261 | { "decodescript", &decodescript, false, false, false }, | |
262 | { "getrawtransaction", &getrawtransaction, false, false, false }, | |
263 | { "sendrawtransaction", &sendrawtransaction, false, false, false }, | |
264 | { "signrawtransaction", &signrawtransaction, false, false, false }, /* uses wallet if enabled */ | |
265 | ||
266 | /* Utility functions */ | |
723a03d2 | 267 | { "createmultisig", &createmultisig, true, true , false }, |
ab88ed93 | 268 | { "validateaddress", &validateaddress, true, false, false }, /* uses wallet if enabled */ |
c3a7f516 | 269 | { "verifymessage", &verifymessage, false, false, false }, |
4a85e067 WL |
270 | |
271 | #ifdef ENABLE_WALLET | |
272 | /* Wallet */ | |
ab88ed93 WL |
273 | { "addmultisigaddress", &addmultisigaddress, false, false, true }, |
274 | { "backupwallet", &backupwallet, true, false, true }, | |
275 | { "dumpprivkey", &dumpprivkey, true, false, true }, | |
276 | { "dumpwallet", &dumpwallet, true, false, true }, | |
277 | { "encryptwallet", &encryptwallet, false, false, true }, | |
b0730874 | 278 | { "getaccountaddress", &getaccountaddress, true, false, true }, |
b0730874 JG |
279 | { "getaccount", &getaccount, false, false, true }, |
280 | { "getaddressesbyaccount", &getaddressesbyaccount, true, false, true }, | |
b0730874 | 281 | { "getbalance", &getbalance, false, false, true }, |
ab88ed93 WL |
282 | { "getnewaddress", &getnewaddress, true, false, true }, |
283 | { "getrawchangeaddress", &getrawchangeaddress, true, false, true }, | |
284 | { "getreceivedbyaccount", &getreceivedbyaccount, false, false, true }, | |
285 | { "getreceivedbyaddress", &getreceivedbyaddress, false, false, true }, | |
b0730874 | 286 | { "gettransaction", &gettransaction, false, false, true }, |
ab88ed93 WL |
287 | { "getunconfirmedbalance", &getunconfirmedbalance, false, false, true }, |
288 | { "getwalletinfo", &getwalletinfo, true, false, true }, | |
b0730874 JG |
289 | { "importprivkey", &importprivkey, false, false, true }, |
290 | { "importwallet", &importwallet, false, false, true }, | |
ab88ed93 WL |
291 | { "keypoolrefill", &keypoolrefill, true, false, true }, |
292 | { "listaccounts", &listaccounts, false, false, true }, | |
293 | { "listaddressgroupings", &listaddressgroupings, false, false, true }, | |
294 | { "listlockunspent", &listlockunspent, false, false, true }, | |
295 | { "listreceivedbyaccount", &listreceivedbyaccount, false, false, true }, | |
296 | { "listreceivedbyaddress", &listreceivedbyaddress, false, false, true }, | |
297 | { "listsinceblock", &listsinceblock, false, false, true }, | |
298 | { "listtransactions", &listtransactions, false, false, true }, | |
b0730874 | 299 | { "listunspent", &listunspent, false, false, true }, |
b0730874 | 300 | { "lockunspent", &lockunspent, false, false, true }, |
ab88ed93 WL |
301 | { "move", &movecmd, false, false, true }, |
302 | { "sendfrom", &sendfrom, false, false, true }, | |
303 | { "sendmany", &sendmany, false, false, true }, | |
304 | { "sendtoaddress", &sendtoaddress, false, false, true }, | |
305 | { "setaccount", &setaccount, true, false, true }, | |
a943bde6 | 306 | { "settxfee", &settxfee, false, false, true }, |
ab88ed93 WL |
307 | { "signmessage", &signmessage, false, false, true }, |
308 | { "walletlock", &walletlock, true, false, true }, | |
309 | { "walletpassphrasechange", &walletpassphrasechange, false, false, true }, | |
310 | { "walletpassphrase", &walletpassphrase, true, false, true }, | |
4a85e067 WL |
311 | |
312 | /* Wallet-enabled mining */ | |
313 | { "getgenerate", &getgenerate, true, false, false }, | |
4a85e067 | 314 | { "gethashespersec", &gethashespersec, true, false, false }, |
ab88ed93 WL |
315 | { "getwork", &getwork, true, false, true }, |
316 | { "setgenerate", &setgenerate, true, true, false }, | |
48ba56cd | 317 | #endif // ENABLE_WALLET |
69d605f4 | 318 | }; |
69d605f4 | 319 | |
9862229d | 320 | CRPCTable::CRPCTable() |
dc42bf52 | 321 | { |
dc42bf52 JG |
322 | unsigned int vcidx; |
323 | for (vcidx = 0; vcidx < (sizeof(vRPCCommands) / sizeof(vRPCCommands[0])); vcidx++) | |
324 | { | |
e46704dd | 325 | const CRPCCommand *pcmd; |
69d605f4 | 326 | |
dc42bf52 JG |
327 | pcmd = &vRPCCommands[vcidx]; |
328 | mapCommands[pcmd->name] = pcmd; | |
329 | } | |
330 | } | |
69d605f4 | 331 | |
9862229d PW |
332 | const CRPCCommand *CRPCTable::operator[](string name) const |
333 | { | |
334 | map<string, const CRPCCommand*>::const_iterator it = mapCommands.find(name); | |
335 | if (it == mapCommands.end()) | |
336 | return NULL; | |
337 | return (*it).second; | |
338 | } | |
69d605f4 | 339 | |
69d605f4 | 340 | |
69d605f4 WL |
341 | bool HTTPAuthorized(map<string, string>& mapHeaders) |
342 | { | |
343 | string strAuth = mapHeaders["authorization"]; | |
344 | if (strAuth.substr(0,6) != "Basic ") | |
345 | return false; | |
346 | string strUserPass64 = strAuth.substr(6); boost::trim(strUserPass64); | |
347 | string strUserPass = DecodeBase64(strUserPass64); | |
42656ea2 | 348 | return TimingResistantEqual(strUserPass, strRPCUserColonPass); |
69d605f4 WL |
349 | } |
350 | ||
69d605f4 WL |
351 | void ErrorReply(std::ostream& stream, const Object& objError, const Value& id) |
352 | { | |
353 | // Send error reply from json-rpc error object | |
285746d3 | 354 | int nStatus = HTTP_INTERNAL_SERVER_ERROR; |
69d605f4 | 355 | int code = find_value(objError, "code").get_int(); |
285746d3 WL |
356 | if (code == RPC_INVALID_REQUEST) nStatus = HTTP_BAD_REQUEST; |
357 | else if (code == RPC_METHOD_NOT_FOUND) nStatus = HTTP_NOT_FOUND; | |
69d605f4 | 358 | string strReply = JSONRPCReply(Value::null, objError, id); |
96c52695 | 359 | stream << HTTPReply(nStatus, strReply, false) << std::flush; |
69d605f4 WL |
360 | } |
361 | ||
21bf3d25 | 362 | CNetAddr BoostAsioToCNetAddr(boost::asio::ip::address address) |
69d605f4 | 363 | { |
ee219125 | 364 | CNetAddr netaddr; |
43b6dafa GS |
365 | // Make sure that IPv4-compatible and IPv4-mapped IPv6 addresses are treated as IPv4 addresses |
366 | if (address.is_v6() | |
367 | && (address.to_v6().is_v4_compatible() | |
368 | || address.to_v6().is_v4_mapped())) | |
ee219125 WL |
369 | address = address.to_v6().to_v4(); |
370 | ||
371 | if(address.is_v4()) | |
372 | { | |
373 | boost::asio::ip::address_v4::bytes_type bytes = address.to_v4().to_bytes(); | |
374 | netaddr.SetRaw(NET_IPV4, &bytes[0]); | |
375 | } | |
376 | else | |
377 | { | |
378 | boost::asio::ip::address_v6::bytes_type bytes = address.to_v6().to_bytes(); | |
379 | netaddr.SetRaw(NET_IPV6, &bytes[0]); | |
380 | } | |
381 | return netaddr; | |
382 | } | |
383 | ||
384 | bool ClientAllowed(const boost::asio::ip::address& address) | |
385 | { | |
386 | CNetAddr netaddr = BoostAsioToCNetAddr(address); | |
387 | BOOST_FOREACH(const CSubNet &subnet, rpc_allow_subnets) | |
388 | if (subnet.Match(netaddr)) | |
69d605f4 WL |
389 | return true; |
390 | return false; | |
391 | } | |
392 | ||
e9205293 DJS |
393 | class AcceptedConnection |
394 | { | |
a0780ba0 GS |
395 | public: |
396 | virtual ~AcceptedConnection() {} | |
397 | ||
398 | virtual std::iostream& stream() = 0; | |
399 | virtual std::string peer_address_to_string() const = 0; | |
400 | virtual void close() = 0; | |
401 | }; | |
e9205293 | 402 | |
a0780ba0 GS |
403 | template <typename Protocol> |
404 | class AcceptedConnectionImpl : public AcceptedConnection | |
405 | { | |
406 | public: | |
407 | AcceptedConnectionImpl( | |
408 | asio::io_service& io_service, | |
409 | ssl::context &context, | |
410 | bool fUseSSL) : | |
411 | sslStream(io_service, context), | |
412 | _d(sslStream, fUseSSL), | |
413 | _stream(_d) | |
414 | { | |
415 | } | |
416 | ||
417 | virtual std::iostream& stream() | |
418 | { | |
419 | return _stream; | |
420 | } | |
421 | ||
422 | virtual std::string peer_address_to_string() const | |
423 | { | |
424 | return peer.address().to_string(); | |
425 | } | |
e9205293 | 426 | |
a0780ba0 GS |
427 | virtual void close() |
428 | { | |
429 | _stream.close(); | |
430 | } | |
431 | ||
432 | typename Protocol::endpoint peer; | |
433 | asio::ssl::stream<typename Protocol::socket> sslStream; | |
434 | ||
435 | private: | |
436 | SSLIOStreamDevice<Protocol> _d; | |
437 | iostreams::stream< SSLIOStreamDevice<Protocol> > _stream; | |
e9205293 DJS |
438 | }; |
439 | ||
21eb5ada | 440 | void ServiceConnection(AcceptedConnection *conn); |
69d605f4 | 441 | |
914dc012 | 442 | // Forward declaration required for RPCListen |
a0780ba0 GS |
443 | template <typename Protocol, typename SocketAcceptorService> |
444 | static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, | |
914dc012 GS |
445 | ssl::context& context, |
446 | bool fUseSSL, | |
1a445225 | 447 | boost::shared_ptr< AcceptedConnection > conn, |
914dc012 GS |
448 | const boost::system::error_code& error); |
449 | ||
450 | /** | |
451 | * Sets up I/O resources to accept and handle a new connection. | |
452 | */ | |
a0780ba0 GS |
453 | template <typename Protocol, typename SocketAcceptorService> |
454 | static void RPCListen(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, | |
914dc012 GS |
455 | ssl::context& context, |
456 | const bool fUseSSL) | |
457 | { | |
914dc012 | 458 | // Accept connection |
1a445225 | 459 | boost::shared_ptr< AcceptedConnectionImpl<Protocol> > conn(new AcceptedConnectionImpl<Protocol>(acceptor->get_io_service(), context, fUseSSL)); |
914dc012 GS |
460 | |
461 | acceptor->async_accept( | |
462 | conn->sslStream.lowest_layer(), | |
463 | conn->peer, | |
a0780ba0 | 464 | boost::bind(&RPCAcceptHandler<Protocol, SocketAcceptorService>, |
914dc012 GS |
465 | acceptor, |
466 | boost::ref(context), | |
467 | fUseSSL, | |
468 | conn, | |
0a0cd345 | 469 | _1)); |
914dc012 GS |
470 | } |
471 | ||
fb78cc23 | 472 | |
914dc012 GS |
473 | /** |
474 | * Accept and handle incoming connection. | |
475 | */ | |
a0780ba0 GS |
476 | template <typename Protocol, typename SocketAcceptorService> |
477 | static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, | |
914dc012 GS |
478 | ssl::context& context, |
479 | const bool fUseSSL, | |
1a445225 | 480 | boost::shared_ptr< AcceptedConnection > conn, |
914dc012 GS |
481 | const boost::system::error_code& error) |
482 | { | |
814efd6f | 483 | // Immediately start accepting new connections, except when we're cancelled or our socket is closed. |
21eb5ada | 484 | if (error != asio::error::operation_aborted && acceptor->is_open()) |
ad25804f | 485 | RPCListen(acceptor, context, fUseSSL); |
914dc012 | 486 | |
1a445225 | 487 | AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn.get()); |
a0780ba0 | 488 | |
914dc012 GS |
489 | if (error) |
490 | { | |
0a0cd345 WL |
491 | // TODO: Actually handle errors |
492 | LogPrintf("%s: Error: %s\n", __func__, error.message()); | |
914dc012 | 493 | } |
914dc012 GS |
494 | // Restrict callers by IP. It is important to |
495 | // do this before starting client thread, to filter out | |
496 | // certain DoS and misbehaving clients. | |
21eb5ada | 497 | else if (tcp_conn && !ClientAllowed(tcp_conn->peer.address())) |
914dc012 GS |
498 | { |
499 | // Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake. | |
500 | if (!fUseSSL) | |
285746d3 | 501 | conn->stream() << HTTPReply(HTTP_FORBIDDEN, "", false) << std::flush; |
1a445225 | 502 | conn->close(); |
914dc012 | 503 | } |
21eb5ada | 504 | else { |
1a445225 | 505 | ServiceConnection(conn.get()); |
21eb5ada | 506 | conn->close(); |
914dc012 | 507 | } |
914dc012 GS |
508 | } |
509 | ||
21eb5ada | 510 | void StartRPCThreads() |
69d605f4 | 511 | { |
ee219125 WL |
512 | rpc_allow_subnets.clear(); |
513 | rpc_allow_subnets.push_back(CSubNet("127.0.0.0/8")); // always allow IPv4 local subnet | |
514 | rpc_allow_subnets.push_back(CSubNet("::1")); // always allow IPv6 localhost | |
515 | if (mapMultiArgs.count("-rpcallowip")) | |
516 | { | |
517 | const vector<string>& vAllow = mapMultiArgs["-rpcallowip"]; | |
518 | BOOST_FOREACH(string strAllow, vAllow) | |
519 | { | |
520 | CSubNet subnet(strAllow); | |
521 | if(!subnet.IsValid()) | |
522 | { | |
523 | uiInterface.ThreadSafeMessageBox( | |
524 | strprintf("Invalid -rpcallowip subnet specification: %s", strAllow), | |
525 | "", CClientUIInterface::MSG_ERROR); | |
526 | StartShutdown(); | |
527 | return; | |
528 | } | |
529 | rpc_allow_subnets.push_back(subnet); | |
530 | } | |
531 | } | |
532 | std::string strAllowed; | |
533 | BOOST_FOREACH(const CSubNet &subnet, rpc_allow_subnets) | |
534 | strAllowed += subnet.ToString() + " "; | |
535 | LogPrint("rpc", "Allowing RPC connections from: %s\n", strAllowed); | |
536 | ||
f81ce5bd | 537 | strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; |
0e4b3175 MH |
538 | if (((mapArgs["-rpcpassword"] == "") || |
539 | (mapArgs["-rpcuser"] == mapArgs["-rpcpassword"])) && Params().RequireRPCPassword()) | |
69d605f4 | 540 | { |
b04f301c GM |
541 | unsigned char rand_pwd[32]; |
542 | RAND_bytes(rand_pwd, 32); | |
69d605f4 WL |
543 | string strWhatAmI = "To use bitcoind"; |
544 | if (mapArgs.count("-server")) | |
545 | strWhatAmI = strprintf(_("To use the %s option"), "\"-server\""); | |
546 | else if (mapArgs.count("-daemon")) | |
547 | strWhatAmI = strprintf(_("To use the %s option"), "\"-daemon\""); | |
ab1b288f | 548 | uiInterface.ThreadSafeMessageBox(strprintf( |
90bd933e PK |
549 | _("%s, you must set a rpcpassword in the configuration file:\n" |
550 | "%s\n" | |
5a60b66a WL |
551 | "It is recommended you use the following random password:\n" |
552 | "rpcuser=bitcoinrpc\n" | |
553 | "rpcpassword=%s\n" | |
554 | "(you do not need to remember this password)\n" | |
7e1610d5 | 555 | "The username and password MUST NOT be the same.\n" |
3d9d2d42 GA |
556 | "If the file does not exist, create it with owner-readable-only file permissions.\n" |
557 | "It is also recommended to set alertnotify so you are notified of problems;\n" | |
558 | "for example: alertnotify=echo %%s | mail -s \"Bitcoin Alert\" [email protected]\n"), | |
7d9d134b WL |
559 | strWhatAmI, |
560 | GetConfigFile().string(), | |
561 | EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32)), | |
5350ea41 | 562 | "", CClientUIInterface::MSG_ERROR); |
9247134e | 563 | StartShutdown(); |
69d605f4 WL |
564 | return; |
565 | } | |
566 | ||
21eb5ada GA |
567 | assert(rpc_io_service == NULL); |
568 | rpc_io_service = new asio::io_service(); | |
569 | rpc_ssl_context = new ssl::context(*rpc_io_service, ssl::context::sslv23); | |
69d605f4 | 570 | |
3260b4c0 | 571 | const bool fUseSSL = GetBoolArg("-rpcssl", false); |
fbf9df2e | 572 | |
69d605f4 WL |
573 | if (fUseSSL) |
574 | { | |
21eb5ada | 575 | rpc_ssl_context->set_options(ssl::context::no_sslv2); |
93fb7489 PK |
576 | |
577 | filesystem::path pathCertFile(GetArg("-rpcsslcertificatechainfile", "server.cert")); | |
578 | if (!pathCertFile.is_complete()) pathCertFile = filesystem::path(GetDataDir()) / pathCertFile; | |
21eb5ada | 579 | if (filesystem::exists(pathCertFile)) rpc_ssl_context->use_certificate_chain_file(pathCertFile.string()); |
7d9d134b | 580 | else LogPrintf("ThreadRPCServer ERROR: missing server certificate file %s\n", pathCertFile.string()); |
93fb7489 PK |
581 | |
582 | filesystem::path pathPKFile(GetArg("-rpcsslprivatekeyfile", "server.pem")); | |
583 | if (!pathPKFile.is_complete()) pathPKFile = filesystem::path(GetDataDir()) / pathPKFile; | |
21eb5ada | 584 | if (filesystem::exists(pathPKFile)) rpc_ssl_context->use_private_key_file(pathPKFile.string(), ssl::context::pem); |
7d9d134b | 585 | else LogPrintf("ThreadRPCServer ERROR: missing server private key file %s\n", pathPKFile.string()); |
93fb7489 | 586 | |
1728bf08 | 587 | string strCiphers = GetArg("-rpcsslciphers", "TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH"); |
21eb5ada | 588 | SSL_CTX_set_cipher_list(rpc_ssl_context->impl(), strCiphers.c_str()); |
69d605f4 | 589 | } |
69d605f4 | 590 | |
c1ecab81 GS |
591 | // Try a dual IPv6/IPv4 socket, falling back to separate IPv4 and IPv6 sockets |
592 | const bool loopback = !mapArgs.count("-rpcallowip"); | |
593 | asio::ip::address bindAddress = loopback ? asio::ip::address_v6::loopback() : asio::ip::address_v6::any(); | |
0e4b3175 | 594 | ip::tcp::endpoint endpoint(bindAddress, GetArg("-rpcport", Params().RPCPort())); |
c1d79812 | 595 | boost::system::error_code v6_only_error; |
21eb5ada | 596 | boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(*rpc_io_service)); |
7cf3d2cc | 597 | |
c1d79812 PW |
598 | bool fListening = false; |
599 | std::string strerr; | |
c1ecab81 GS |
600 | try |
601 | { | |
ad25804f GS |
602 | acceptor->open(endpoint.protocol()); |
603 | acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); | |
c1ecab81 GS |
604 | |
605 | // Try making the socket dual IPv6/IPv4 (if listening on the "any" address) | |
ad25804f | 606 | acceptor->set_option(boost::asio::ip::v6_only(loopback), v6_only_error); |
c1ecab81 | 607 | |
ad25804f GS |
608 | acceptor->bind(endpoint); |
609 | acceptor->listen(socket_base::max_connections); | |
c1ecab81 | 610 | |
21eb5ada | 611 | RPCListen(acceptor, *rpc_ssl_context, fUseSSL); |
c1ecab81 | 612 | |
c1d79812 PW |
613 | fListening = true; |
614 | } | |
615 | catch(boost::system::system_error &e) | |
616 | { | |
95e625d2 | 617 | strerr = strprintf(_("An error occurred while setting up the RPC port %u for listening on IPv6, falling back to IPv4: %s"), endpoint.port(), e.what()); |
c1d79812 PW |
618 | } |
619 | ||
620 | try { | |
c1ecab81 | 621 | // If dual IPv6/IPv4 failed (or we're opening loopback interfaces only), open IPv4 separately |
c1d79812 | 622 | if (!fListening || loopback || v6_only_error) |
c1ecab81 GS |
623 | { |
624 | bindAddress = loopback ? asio::ip::address_v4::loopback() : asio::ip::address_v4::any(); | |
625 | endpoint.address(bindAddress); | |
626 | ||
21eb5ada | 627 | acceptor.reset(new ip::tcp::acceptor(*rpc_io_service)); |
ad25804f GS |
628 | acceptor->open(endpoint.protocol()); |
629 | acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); | |
630 | acceptor->bind(endpoint); | |
631 | acceptor->listen(socket_base::max_connections); | |
632 | ||
21eb5ada | 633 | RPCListen(acceptor, *rpc_ssl_context, fUseSSL); |
c1d79812 PW |
634 | |
635 | fListening = true; | |
c1ecab81 GS |
636 | } |
637 | } | |
638 | catch(boost::system::system_error &e) | |
639 | { | |
463a1cab | 640 | strerr = strprintf(_("An error occurred while setting up the RPC port %u for listening on IPv4: %s"), endpoint.port(), e.what()); |
c1d79812 PW |
641 | } |
642 | ||
643 | if (!fListening) { | |
5350ea41 | 644 | uiInterface.ThreadSafeMessageBox(strerr, "", CClientUIInterface::MSG_ERROR); |
07368a9e | 645 | StartShutdown(); |
c1ecab81 GS |
646 | return; |
647 | } | |
69d605f4 | 648 | |
21eb5ada GA |
649 | rpc_worker_group = new boost::thread_group(); |
650 | for (int i = 0; i < GetArg("-rpcthreads", 4); i++) | |
651 | rpc_worker_group->create_thread(boost::bind(&asio::io_service::run, rpc_io_service)); | |
652 | } | |
653 | ||
a8db31c8 WL |
654 | void StartDummyRPCThread() |
655 | { | |
656 | if(rpc_io_service == NULL) | |
657 | { | |
658 | rpc_io_service = new asio::io_service(); | |
659 | /* Create dummy "work" to keep the thread from exiting when no timeouts active, | |
660 | * 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 */ | |
661 | rpc_dummy_work = new asio::io_service::work(*rpc_io_service); | |
662 | rpc_worker_group = new boost::thread_group(); | |
663 | rpc_worker_group->create_thread(boost::bind(&asio::io_service::run, rpc_io_service)); | |
664 | } | |
665 | } | |
666 | ||
21eb5ada GA |
667 | void StopRPCThreads() |
668 | { | |
669 | if (rpc_io_service == NULL) return; | |
670 | ||
92f2c1fe | 671 | deadlineTimers.clear(); |
21eb5ada | 672 | rpc_io_service->stop(); |
b2ba55c4 GA |
673 | if (rpc_worker_group != NULL) |
674 | rpc_worker_group->join_all(); | |
a8db31c8 | 675 | delete rpc_dummy_work; rpc_dummy_work = NULL; |
21eb5ada GA |
676 | delete rpc_worker_group; rpc_worker_group = NULL; |
677 | delete rpc_ssl_context; rpc_ssl_context = NULL; | |
678 | delete rpc_io_service; rpc_io_service = NULL; | |
e9205293 DJS |
679 | } |
680 | ||
92f2c1fe GA |
681 | void RPCRunHandler(const boost::system::error_code& err, boost::function<void(void)> func) |
682 | { | |
683 | if (!err) | |
684 | func(); | |
685 | } | |
686 | ||
51ed9ec9 | 687 | void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds) |
92f2c1fe GA |
688 | { |
689 | assert(rpc_io_service != NULL); | |
690 | ||
691 | if (deadlineTimers.count(name) == 0) | |
692 | { | |
693 | deadlineTimers.insert(make_pair(name, | |
694 | boost::shared_ptr<deadline_timer>(new deadline_timer(*rpc_io_service)))); | |
695 | } | |
696 | deadlineTimers[name]->expires_from_now(posix_time::seconds(nSeconds)); | |
697 | deadlineTimers[name]->async_wait(boost::bind(RPCRunHandler, _1, func)); | |
698 | } | |
699 | ||
c6494d82 JG |
700 | class JSONRequest |
701 | { | |
702 | public: | |
703 | Value id; | |
704 | string strMethod; | |
705 | Array params; | |
706 | ||
707 | JSONRequest() { id = Value::null; } | |
708 | void parse(const Value& valRequest); | |
709 | }; | |
710 | ||
711 | void JSONRequest::parse(const Value& valRequest) | |
712 | { | |
713 | // Parse request | |
714 | if (valRequest.type() != obj_type) | |
738835d7 | 715 | throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object"); |
c6494d82 JG |
716 | const Object& request = valRequest.get_obj(); |
717 | ||
718 | // Parse id now so errors from here on will have the id | |
719 | id = find_value(request, "id"); | |
720 | ||
721 | // Parse method | |
722 | Value valMethod = find_value(request, "method"); | |
723 | if (valMethod.type() == null_type) | |
738835d7 | 724 | throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method"); |
c6494d82 | 725 | if (valMethod.type() != str_type) |
738835d7 | 726 | throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string"); |
c6494d82 | 727 | strMethod = valMethod.get_str(); |
44427fa8 | 728 | if (strMethod != "getwork" && strMethod != "getblocktemplate") |
7d9d134b | 729 | LogPrint("rpc", "ThreadRPCServer method=%s\n", strMethod); |
c6494d82 JG |
730 | |
731 | // Parse params | |
732 | Value valParams = find_value(request, "params"); | |
733 | if (valParams.type() == array_type) | |
734 | params = valParams.get_array(); | |
735 | else if (valParams.type() == null_type) | |
736 | params = Array(); | |
737 | else | |
738835d7 | 738 | throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array"); |
c6494d82 JG |
739 | } |
740 | ||
fb78cc23 | 741 | |
61338901 JG |
742 | static Object JSONRPCExecOne(const Value& req) |
743 | { | |
744 | Object rpc_result; | |
745 | ||
746 | JSONRequest jreq; | |
747 | try { | |
748 | jreq.parse(req); | |
749 | ||
750 | Value result = tableRPC.execute(jreq.strMethod, jreq.params); | |
751 | rpc_result = JSONRPCReplyObj(result, Value::null, jreq.id); | |
752 | } | |
753 | catch (Object& objError) | |
754 | { | |
755 | rpc_result = JSONRPCReplyObj(Value::null, objError, jreq.id); | |
756 | } | |
757 | catch (std::exception& e) | |
758 | { | |
759 | rpc_result = JSONRPCReplyObj(Value::null, | |
738835d7 | 760 | JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); |
61338901 JG |
761 | } |
762 | ||
763 | return rpc_result; | |
764 | } | |
765 | ||
766 | static string JSONRPCExecBatch(const Array& vReq) | |
767 | { | |
768 | Array ret; | |
769 | for (unsigned int reqIdx = 0; reqIdx < vReq.size(); reqIdx++) | |
770 | ret.push_back(JSONRPCExecOne(vReq[reqIdx])); | |
771 | ||
0db9a805 | 772 | return write_string(Value(ret), false) + "\n"; |
61338901 JG |
773 | } |
774 | ||
21eb5ada | 775 | void ServiceConnection(AcceptedConnection *conn) |
e9205293 | 776 | { |
96c52695 | 777 | bool fRun = true; |
d138598f | 778 | while (fRun && !ShutdownRequested()) |
21eb5ada | 779 | { |
2306dc4b | 780 | int nProto = 0; |
69d605f4 | 781 | map<string, string> mapHeaders; |
fcf234fc JG |
782 | string strRequest, strMethod, strURI; |
783 | ||
784 | // Read HTTP request line | |
785 | if (!ReadHTTPRequestLine(conn->stream(), nProto, strMethod, strURI)) | |
786 | break; | |
69d605f4 | 787 | |
2306dc4b JG |
788 | // Read HTTP message headers and body |
789 | ReadHTTPMessage(conn->stream(), mapHeaders, strRequest, nProto); | |
69d605f4 | 790 | |
9f4976af JG |
791 | if (strURI != "/") { |
792 | conn->stream() << HTTPReply(HTTP_NOT_FOUND, "", false) << std::flush; | |
793 | break; | |
794 | } | |
795 | ||
69d605f4 WL |
796 | // Check authorization |
797 | if (mapHeaders.count("authorization") == 0) | |
798 | { | |
285746d3 | 799 | conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush; |
e9205293 | 800 | break; |
69d605f4 WL |
801 | } |
802 | if (!HTTPAuthorized(mapHeaders)) | |
803 | { | |
7d9d134b | 804 | LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string()); |
b04f301c | 805 | /* Deter brute-forcing short passwords. |
f65dddc7 PK |
806 | If this results in a DoS the user really |
807 | shouldn't have their RPC port exposed. */ | |
b04f301c | 808 | if (mapArgs["-rpcpassword"].size() < 20) |
1b43bf0d | 809 | MilliSleep(250); |
69d605f4 | 810 | |
285746d3 | 811 | conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush; |
e9205293 | 812 | break; |
69d605f4 | 813 | } |
96c52695 DJS |
814 | if (mapHeaders["connection"] == "close") |
815 | fRun = false; | |
69d605f4 | 816 | |
c6494d82 | 817 | JSONRequest jreq; |
69d605f4 WL |
818 | try |
819 | { | |
820 | // Parse request | |
821 | Value valRequest; | |
61338901 | 822 | if (!read_string(strRequest, valRequest)) |
738835d7 | 823 | throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); |
69d605f4 | 824 | |
61338901 JG |
825 | string strReply; |
826 | ||
827 | // singleton request | |
828 | if (valRequest.type() == obj_type) { | |
829 | jreq.parse(valRequest); | |
c6494d82 | 830 | |
61338901 | 831 | Value result = tableRPC.execute(jreq.strMethod, jreq.params); |
460c51fd | 832 | |
61338901 JG |
833 | // Send reply |
834 | strReply = JSONRPCReply(result, Value::null, jreq.id); | |
835 | ||
836 | // array of requests | |
837 | } else if (valRequest.type() == array_type) | |
838 | strReply = JSONRPCExecBatch(valRequest.get_array()); | |
839 | else | |
738835d7 | 840 | throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); |
ea0796bd | 841 | |
285746d3 | 842 | conn->stream() << HTTPReply(HTTP_OK, strReply, fRun) << std::flush; |
69d605f4 WL |
843 | } |
844 | catch (Object& objError) | |
845 | { | |
c6494d82 | 846 | ErrorReply(conn->stream(), objError, jreq.id); |
e9205293 | 847 | break; |
69d605f4 WL |
848 | } |
849 | catch (std::exception& e) | |
850 | { | |
738835d7 | 851 | ErrorReply(conn->stream(), JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); |
e9205293 | 852 | break; |
69d605f4 WL |
853 | } |
854 | } | |
855 | } | |
856 | ||
460c51fd WL |
857 | json_spirit::Value CRPCTable::execute(const std::string &strMethod, const json_spirit::Array ¶ms) const |
858 | { | |
859 | // Find method | |
860 | const CRPCCommand *pcmd = tableRPC[strMethod]; | |
861 | if (!pcmd) | |
738835d7 | 862 | throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); |
48ba56cd | 863 | #ifdef ENABLE_WALLET |
b0730874 JG |
864 | if (pcmd->reqWallet && !pwalletMain) |
865 | throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found (disabled)"); | |
48ba56cd | 866 | #endif |
69d605f4 | 867 | |
460c51fd WL |
868 | // Observe safe mode |
869 | string strWarning = GetWarnings("rpc"); | |
3260b4c0 | 870 | if (strWarning != "" && !GetBoolArg("-disablesafemode", false) && |
460c51fd | 871 | !pcmd->okSafeMode) |
738835d7 | 872 | throw JSONRPCError(RPC_FORBIDDEN_BY_SAFE_MODE, string("Safe mode: ") + strWarning); |
460c51fd WL |
873 | |
874 | try | |
875 | { | |
876 | // Execute | |
877 | Value result; | |
878 | { | |
10ef3611 | 879 | if (pcmd->threadSafe) |
0e1d3551 | 880 | result = pcmd->actor(params, false); |
48ba56cd | 881 | #ifdef ENABLE_WALLET |
b0730874 JG |
882 | else if (!pwalletMain) { |
883 | LOCK(cs_main); | |
884 | result = pcmd->actor(params, false); | |
885 | } else { | |
0e1d3551 JG |
886 | LOCK2(cs_main, pwalletMain->cs_wallet); |
887 | result = pcmd->actor(params, false); | |
888 | } | |
48ba56cd WL |
889 | #else // ENABLE_WALLET |
890 | else { | |
891 | LOCK(cs_main); | |
892 | result = pcmd->actor(params, false); | |
893 | } | |
894 | #endif // !ENABLE_WALLET | |
460c51fd WL |
895 | } |
896 | return result; | |
897 | } | |
898 | catch (std::exception& e) | |
899 | { | |
738835d7 | 900 | throw JSONRPCError(RPC_MISC_ERROR, e.what()); |
460c51fd WL |
901 | } |
902 | } | |
69d605f4 | 903 | |
bbb09365 WL |
904 | std::string HelpExampleCli(string methodname, string args){ |
905 | return "> bitcoin-cli " + methodname + " " + args + "\n"; | |
906 | } | |
907 | ||
908 | std::string HelpExampleRpc(string methodname, string args){ | |
909 | return "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\":\"curltest\", " | |
910 | "\"method\": \"" + methodname + "\", \"params\": [" + args + "] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"; | |
911 | } | |
912 | ||
e46704dd | 913 | const CRPCTable tableRPC; |