]>
Commit | Line | Data |
---|---|---|
2a03a390 | 1 | // Copyright (c) 2009-2010 Satoshi Nakamoto |
f914f1a7 | 2 | // Copyright (c) 2009-2013 The Bitcoin Core developers |
78253fcb | 3 | // Distributed under the MIT software license, see the accompanying |
2a03a390 WL |
4 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
5 | ||
611116d4 | 6 | #include "chainparamsbase.h" |
71697f97 | 7 | #include "clientversion.h" |
fb78cc23 | 8 | #include "rpcclient.h" |
a7199038 | 9 | #include "rpcprotocol.h" |
611116d4 | 10 | #include "util.h" |
ad49c256 | 11 | #include "utilstrencodings.h" |
2a03a390 | 12 | |
51ed9ec9 | 13 | #include <boost/filesystem/operations.hpp> |
afd64f76 WL |
14 | #include <stdio.h> |
15 | ||
afd64f76 WL |
16 | #include <event2/buffer.h> |
17 | #include <event2/keyvalq_struct.h> | |
68377e18 | 18 | #include "support/events.h" |
51ed9ec9 | 19 | |
a10a6e2a | 20 | #include <univalue.h> |
d014114d | 21 | |
09eb201b | 22 | using namespace std; |
b750cf1f | 23 | |
89bccddc WL |
24 | static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900; |
25 | ||
b750cf1f WL |
26 | std::string HelpMessageCli() |
27 | { | |
28 | string strUsage; | |
1fdb9fa3 LV |
29 | strUsage += HelpMessageGroup(_("Options:")); |
30 | strUsage += HelpMessageOpt("-?", _("This help message")); | |
aaf64959 | 31 | strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), "zcash.conf")); |
1fdb9fa3 LV |
32 | strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory")); |
33 | strUsage += HelpMessageOpt("-testnet", _("Use the test network")); | |
34 | strUsage += HelpMessageOpt("-regtest", _("Enter regression test mode, which uses a special chain in which blocks can be " | |
35 | "solved instantly. This is intended for regression testing tools and app development.")); | |
36 | strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf(_("Send commands to node running on <ip> (default: %s)"), "127.0.0.1")); | |
3985a40d | 37 | strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Connect to JSON-RPC on <port> (default: %u or testnet: %u)"), 8232, 18232)); |
1fdb9fa3 LV |
38 | strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start")); |
39 | strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections")); | |
40 | strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections")); | |
629a8752 | 41 | strUsage += HelpMessageOpt("-rpcclienttimeout=<n>", strprintf(_("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)"), DEFAULT_HTTP_CLIENT_TIMEOUT)); |
b750cf1f | 42 | |
b750cf1f WL |
43 | return strUsage; |
44 | } | |
45 | ||
2a03a390 WL |
46 | ////////////////////////////////////////////////////////////////////////////// |
47 | // | |
48 | // Start | |
49 | // | |
af82884a DK |
50 | |
51 | // | |
52 | // Exception thrown on connection error. This error is used to determine | |
53 | // when to wait if -rpcwait is given. | |
54 | // | |
55 | class CConnectionFailed : public std::runtime_error | |
56 | { | |
57 | public: | |
58 | ||
59 | explicit inline CConnectionFailed(const std::string& msg) : | |
60 | std::runtime_error(msg) | |
61 | {} | |
62 | ||
63 | }; | |
64 | ||
2a03a390 WL |
65 | static bool AppInitRPC(int argc, char* argv[]) |
66 | { | |
67 | // | |
68 | // Parameters | |
69 | // | |
70 | ParseParameters(argc, argv); | |
af6edac0 | 71 | if (argc<2 || mapArgs.count("-?") || mapArgs.count("-h") || mapArgs.count("-help") || mapArgs.count("-version")) { |
f0d1accb | 72 | std::string strUsage = _("Zcash RPC client version") + " " + FormatFullVersion() + "\n" + PrivacyInfo(); |
3d0a1ce1 PJ |
73 | if (!mapArgs.count("-version")) { |
74 | strUsage += "\n" + _("Usage:") + "\n" + | |
a1de76c7 JG |
75 | " zcash-cli [options] <command> [params] " + _("Send command to Zcash") + "\n" + |
76 | " zcash-cli [options] help " + _("List commands") + "\n" + | |
77 | " zcash-cli [options] help <command> " + _("Get help for a command") + "\n"; | |
3d0a1ce1 PJ |
78 | |
79 | strUsage += "\n" + HelpMessageCli(); | |
af021144 S |
80 | } else { |
81 | strUsage += LicenseInfo(); | |
3d0a1ce1 PJ |
82 | } |
83 | ||
84 | fprintf(stdout, "%s", strUsage.c_str()); | |
85 | return false; | |
86 | } | |
3ce7e669 | 87 | if (!boost::filesystem::is_directory(GetDataDir(false))) { |
2a03a390 WL |
88 | fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", mapArgs["-datadir"].c_str()); |
89 | return false; | |
90 | } | |
4ae5e721 WL |
91 | try { |
92 | ReadConfigFile(mapArgs, mapMultiArgs); | |
27df4123 | 93 | } catch (const std::exception& e) { |
4ae5e721 WL |
94 | fprintf(stderr,"Error reading configuration file: %s\n", e.what()); |
95 | return false; | |
96 | } | |
84ce18ca WL |
97 | // Check for -testnet or -regtest parameter (BaseParams() calls are only valid after this clause) |
98 | if (!SelectBaseParamsFromCommandLine()) { | |
9d2b73d1 WL |
99 | fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n"); |
100 | return false; | |
101 | } | |
afd64f76 WL |
102 | if (GetBoolArg("-rpcssl", false)) |
103 | { | |
104 | fprintf(stderr, "Error: SSL mode for RPC (-rpcssl) is no longer supported.\n"); | |
105 | return false; | |
106 | } | |
2a03a390 WL |
107 | return true; |
108 | } | |
109 | ||
afd64f76 WL |
110 | |
111 | /** Reply structure for request_done to fill in */ | |
112 | struct HTTPReply | |
b750cf1f | 113 | { |
6415573a WL |
114 | HTTPReply(): status(0), error(-1) {} |
115 | ||
afd64f76 | 116 | int status; |
6415573a | 117 | int error; |
afd64f76 WL |
118 | std::string body; |
119 | }; | |
120 | ||
6415573a WL |
121 | const char *http_errorstring(int code) |
122 | { | |
123 | switch(code) { | |
124 | #if LIBEVENT_VERSION_NUMBER >= 0x02010300 | |
125 | case EVREQ_HTTP_TIMEOUT: | |
126 | return "timeout reached"; | |
127 | case EVREQ_HTTP_EOF: | |
128 | return "EOF reached"; | |
129 | case EVREQ_HTTP_INVALID_HEADER: | |
130 | return "error while reading header, or invalid header"; | |
131 | case EVREQ_HTTP_BUFFER_ERROR: | |
132 | return "error encountered while reading or writing"; | |
133 | case EVREQ_HTTP_REQUEST_CANCEL: | |
134 | return "request was canceled"; | |
135 | case EVREQ_HTTP_DATA_TOO_LONG: | |
136 | return "response body is larger than allowed"; | |
137 | #endif | |
138 | default: | |
139 | return "unknown"; | |
140 | } | |
141 | } | |
142 | ||
afd64f76 WL |
143 | static void http_request_done(struct evhttp_request *req, void *ctx) |
144 | { | |
145 | HTTPReply *reply = static_cast<HTTPReply*>(ctx); | |
146 | ||
147 | if (req == NULL) { | |
6415573a WL |
148 | /* If req is NULL, it means an error occurred while connecting: the |
149 | * error code will have been passed to http_error_cb. | |
afd64f76 WL |
150 | */ |
151 | reply->status = 0; | |
152 | return; | |
153 | } | |
b750cf1f | 154 | |
afd64f76 WL |
155 | reply->status = evhttp_request_get_response_code(req); |
156 | ||
157 | struct evbuffer *buf = evhttp_request_get_input_buffer(req); | |
158 | if (buf) | |
159 | { | |
160 | size_t size = evbuffer_get_length(buf); | |
161 | const char *data = (const char*)evbuffer_pullup(buf, size); | |
162 | if (data) | |
163 | reply->body = std::string(data, size); | |
164 | evbuffer_drain(buf, size); | |
165 | } | |
166 | } | |
167 | ||
6415573a WL |
168 | #if LIBEVENT_VERSION_NUMBER >= 0x02010300 |
169 | static void http_error_cb(enum evhttp_request_error err, void *ctx) | |
170 | { | |
171 | HTTPReply *reply = static_cast<HTTPReply*>(ctx); | |
172 | reply->error = err; | |
173 | } | |
174 | #endif | |
175 | ||
afd64f76 WL |
176 | UniValue CallRPC(const string& strMethod, const UniValue& params) |
177 | { | |
178 | std::string host = GetArg("-rpcconnect", "127.0.0.1"); | |
179 | int port = GetArg("-rpcport", BaseParams().RPCPort()); | |
180 | ||
68377e18 KJA |
181 | // Obtain event base |
182 | raii_event_base base = obtain_event_base(); | |
afd64f76 WL |
183 | |
184 | // Synchronously look up hostname | |
68377e18 KJA |
185 | raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port); |
186 | evhttp_connection_set_timeout(evcon.get(), GetArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT)); | |
afd64f76 WL |
187 | |
188 | HTTPReply response; | |
68377e18 | 189 | raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response); |
afd64f76 WL |
190 | if (req == NULL) |
191 | throw runtime_error("create http request failed"); | |
6415573a | 192 | #if LIBEVENT_VERSION_NUMBER >= 0x02010300 |
68377e18 | 193 | evhttp_request_set_error_cb(req.get(), http_error_cb); |
6415573a | 194 | #endif |
afd64f76 WL |
195 | |
196 | // Get credentials | |
e957192c WL |
197 | std::string strRPCUserColonPass; |
198 | if (mapArgs["-rpcpassword"] == "") { | |
199 | // Try fall back to cookie-based authentication if no password is provided | |
200 | if (!GetAuthCookie(&strRPCUserColonPass)) { | |
201 | throw runtime_error(strprintf( | |
206e2b97 JG |
202 | _("Could not locate RPC credentials. No authentication cookie could be found,\n" |
203 | "and no rpcpassword is set in the configuration file (%s)."), | |
e957192c WL |
204 | GetConfigFile().string().c_str())); |
205 | ||
206 | } | |
207 | } else { | |
208 | strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; | |
209 | } | |
210 | ||
68377e18 | 211 | struct evkeyvalq* output_headers = evhttp_request_get_output_headers(req.get()); |
afd64f76 WL |
212 | assert(output_headers); |
213 | evhttp_add_header(output_headers, "Host", host.c_str()); | |
214 | evhttp_add_header(output_headers, "Connection", "close"); | |
215 | evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str()); | |
216 | ||
217 | // Attach request data | |
218 | std::string strRequest = JSONRPCRequest(strMethod, params, 1); | |
68377e18 | 219 | struct evbuffer* output_buffer = evhttp_request_get_output_buffer(req.get()); |
afd64f76 WL |
220 | assert(output_buffer); |
221 | evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); | |
222 | ||
68377e18 KJA |
223 | int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/"); |
224 | req.release(); // ownership moved to evcon in above call | |
afd64f76 | 225 | if (r != 0) { |
afd64f76 WL |
226 | throw CConnectionFailed("send http request failed"); |
227 | } | |
b750cf1f | 228 | |
68377e18 | 229 | event_base_dispatch(base.get()); |
b750cf1f | 230 | |
afd64f76 | 231 | if (response.status == 0) |
7c2ab059 | 232 | throw CConnectionFailed(strprintf("couldn't connect to server: %s (code %d)\n(make sure server is running and you are connecting to the correct RPC port)", http_errorstring(response.error), response.error)); |
afd64f76 | 233 | else if (response.status == HTTP_UNAUTHORIZED) |
b750cf1f | 234 | throw runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); |
afd64f76 WL |
235 | else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR) |
236 | throw runtime_error(strprintf("server returned HTTP error %d", response.status)); | |
237 | else if (response.body.empty()) | |
b750cf1f WL |
238 | throw runtime_error("no response from server"); |
239 | ||
240 | // Parse reply | |
851f58f9 | 241 | UniValue valReply(UniValue::VSTR); |
afd64f76 | 242 | if (!valReply.read(response.body)) |
b750cf1f | 243 | throw runtime_error("couldn't parse reply from server"); |
d014114d | 244 | const UniValue& reply = valReply.get_obj(); |
b750cf1f WL |
245 | if (reply.empty()) |
246 | throw runtime_error("expected reply to have result, error and id properties"); | |
247 | ||
248 | return reply; | |
249 | } | |
250 | ||
251 | int CommandLineRPC(int argc, char *argv[]) | |
252 | { | |
253 | string strPrint; | |
254 | int nRet = 0; | |
3ce7e669 | 255 | try { |
b750cf1f | 256 | // Skip switches |
3ce7e669 | 257 | while (argc > 1 && IsSwitchChar(argv[1][0])) { |
b750cf1f WL |
258 | argc--; |
259 | argv++; | |
260 | } | |
261 | ||
262 | // Method | |
263 | if (argc < 2) | |
264 | throw runtime_error("too few parameters"); | |
265 | string strMethod = argv[1]; | |
266 | ||
267 | // Parameters default to strings | |
268 | std::vector<std::string> strParams(&argv[2], &argv[argc]); | |
851f58f9 | 269 | UniValue params = RPCConvertValues(strMethod, strParams); |
b750cf1f | 270 | |
af82884a DK |
271 | // Execute and handle connection failures with -rpcwait |
272 | const bool fWait = GetBoolArg("-rpcwait", false); | |
273 | do { | |
274 | try { | |
851f58f9 | 275 | const UniValue reply = CallRPC(strMethod, params); |
af82884a DK |
276 | |
277 | // Parse reply | |
d014114d JS |
278 | const UniValue& result = find_value(reply, "result"); |
279 | const UniValue& error = find_value(reply, "error"); | |
af82884a | 280 | |
ed21d5bd | 281 | if (!error.isNull()) { |
af82884a | 282 | // Error |
ed21d5bd | 283 | int code = error["code"].get_int(); |
d014114d JS |
284 | if (fWait && code == RPC_IN_WARMUP) |
285 | throw CConnectionFailed("server in warmup"); | |
286 | strPrint = "error: " + error.write(); | |
af82884a | 287 | nRet = abs(code); |
f061578b JS |
288 | if (error.isObject()) |
289 | { | |
290 | UniValue errCode = find_value(error, "code"); | |
291 | UniValue errMsg = find_value(error, "message"); | |
292 | strPrint = errCode.isNull() ? "" : "error code: "+errCode.getValStr()+"\n"; | |
293 | ||
294 | if (errMsg.isStr()) | |
295 | strPrint += "error message:\n"+errMsg.get_str(); | |
296 | } | |
af82884a DK |
297 | } else { |
298 | // Result | |
ed21d5bd | 299 | if (result.isNull()) |
af82884a | 300 | strPrint = ""; |
ed21d5bd | 301 | else if (result.isStr()) |
af82884a DK |
302 | strPrint = result.get_str(); |
303 | else | |
ed21d5bd | 304 | strPrint = result.write(2); |
af82884a | 305 | } |
af82884a DK |
306 | // Connection succeeded, no need to retry. |
307 | break; | |
308 | } | |
27df4123 | 309 | catch (const CConnectionFailed&) { |
af82884a DK |
310 | if (fWait) |
311 | MilliSleep(1000); | |
312 | else | |
313 | throw; | |
314 | } | |
315 | } while (fWait); | |
b750cf1f | 316 | } |
27df4123 | 317 | catch (const boost::thread_interrupted&) { |
b750cf1f WL |
318 | throw; |
319 | } | |
27df4123 | 320 | catch (const std::exception& e) { |
b750cf1f WL |
321 | strPrint = string("error: ") + e.what(); |
322 | nRet = EXIT_FAILURE; | |
323 | } | |
324 | catch (...) { | |
325 | PrintExceptionContinue(NULL, "CommandLineRPC()"); | |
326 | throw; | |
327 | } | |
328 | ||
3ce7e669 | 329 | if (strPrint != "") { |
b750cf1f WL |
330 | fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); |
331 | } | |
332 | return nRet; | |
333 | } | |
334 | ||
2a03a390 WL |
335 | int main(int argc, char* argv[]) |
336 | { | |
5248ff40 | 337 | SetupEnvironment(); |
167b6231 WL |
338 | if (!SetupNetworking()) { |
339 | fprintf(stderr, "Error: Initializing networking failed\n"); | |
340 | exit(1); | |
341 | } | |
5248ff40 | 342 | |
3ce7e669 | 343 | try { |
2a03a390 | 344 | if(!AppInitRPC(argc, argv)) |
0cafb630 | 345 | return EXIT_FAILURE; |
2a03a390 | 346 | } |
27df4123 | 347 | catch (const std::exception& e) { |
2a03a390 | 348 | PrintExceptionContinue(&e, "AppInitRPC()"); |
0cafb630 | 349 | return EXIT_FAILURE; |
2a03a390 WL |
350 | } catch (...) { |
351 | PrintExceptionContinue(NULL, "AppInitRPC()"); | |
0cafb630 | 352 | return EXIT_FAILURE; |
2a03a390 WL |
353 | } |
354 | ||
0cafb630 | 355 | int ret = EXIT_FAILURE; |
3ce7e669 | 356 | try { |
a7199038 | 357 | ret = CommandLineRPC(argc, argv); |
2a03a390 | 358 | } |
27df4123 | 359 | catch (const std::exception& e) { |
2a03a390 WL |
360 | PrintExceptionContinue(&e, "CommandLineRPC()"); |
361 | } catch (...) { | |
362 | PrintExceptionContinue(NULL, "CommandLineRPC()"); | |
363 | } | |
a7199038 | 364 | return ret; |
2a03a390 | 365 | } |