]>
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 | ||
16 | #include <event2/event.h> | |
17 | #include <event2/http.h> | |
18 | #include <event2/buffer.h> | |
19 | #include <event2/keyvalq_struct.h> | |
51ed9ec9 | 20 | |
a10a6e2a | 21 | #include <univalue.h> |
d014114d | 22 | |
09eb201b | 23 | using namespace std; |
b750cf1f WL |
24 | |
25 | std::string HelpMessageCli() | |
26 | { | |
27 | string strUsage; | |
1fdb9fa3 LV |
28 | strUsage += HelpMessageGroup(_("Options:")); |
29 | strUsage += HelpMessageOpt("-?", _("This help message")); | |
aaf64959 | 30 | strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), "zcash.conf")); |
1fdb9fa3 LV |
31 | strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory")); |
32 | strUsage += HelpMessageOpt("-testnet", _("Use the test network")); | |
33 | strUsage += HelpMessageOpt("-regtest", _("Enter regression test mode, which uses a special chain in which blocks can be " | |
34 | "solved instantly. This is intended for regression testing tools and app development.")); | |
35 | strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf(_("Send commands to node running on <ip> (default: %s)"), "127.0.0.1")); | |
3985a40d | 36 | strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Connect to JSON-RPC on <port> (default: %u or testnet: %u)"), 8232, 18232)); |
1fdb9fa3 LV |
37 | strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start")); |
38 | strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections")); | |
39 | strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections")); | |
b750cf1f | 40 | |
b750cf1f WL |
41 | return strUsage; |
42 | } | |
43 | ||
2a03a390 WL |
44 | ////////////////////////////////////////////////////////////////////////////// |
45 | // | |
46 | // Start | |
47 | // | |
af82884a DK |
48 | |
49 | // | |
50 | // Exception thrown on connection error. This error is used to determine | |
51 | // when to wait if -rpcwait is given. | |
52 | // | |
53 | class CConnectionFailed : public std::runtime_error | |
54 | { | |
55 | public: | |
56 | ||
57 | explicit inline CConnectionFailed(const std::string& msg) : | |
58 | std::runtime_error(msg) | |
59 | {} | |
60 | ||
61 | }; | |
62 | ||
2a03a390 WL |
63 | static bool AppInitRPC(int argc, char* argv[]) |
64 | { | |
65 | // | |
66 | // Parameters | |
67 | // | |
68 | ParseParameters(argc, argv); | |
af6edac0 | 69 | if (argc<2 || mapArgs.count("-?") || mapArgs.count("-h") || mapArgs.count("-help") || mapArgs.count("-version")) { |
a1de76c7 | 70 | std::string strUsage = _("Zcash RPC client version") + " " + FormatFullVersion() + "\n"; |
3d0a1ce1 PJ |
71 | if (!mapArgs.count("-version")) { |
72 | strUsage += "\n" + _("Usage:") + "\n" + | |
a1de76c7 JG |
73 | " zcash-cli [options] <command> [params] " + _("Send command to Zcash") + "\n" + |
74 | " zcash-cli [options] help " + _("List commands") + "\n" + | |
75 | " zcash-cli [options] help <command> " + _("Get help for a command") + "\n"; | |
3d0a1ce1 PJ |
76 | |
77 | strUsage += "\n" + HelpMessageCli(); | |
af021144 S |
78 | } else { |
79 | strUsage += LicenseInfo(); | |
3d0a1ce1 PJ |
80 | } |
81 | ||
82 | fprintf(stdout, "%s", strUsage.c_str()); | |
83 | return false; | |
84 | } | |
3ce7e669 | 85 | if (!boost::filesystem::is_directory(GetDataDir(false))) { |
2a03a390 WL |
86 | fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", mapArgs["-datadir"].c_str()); |
87 | return false; | |
88 | } | |
4ae5e721 WL |
89 | try { |
90 | ReadConfigFile(mapArgs, mapMultiArgs); | |
27df4123 | 91 | } catch (const std::exception& e) { |
4ae5e721 WL |
92 | fprintf(stderr,"Error reading configuration file: %s\n", e.what()); |
93 | return false; | |
94 | } | |
84ce18ca WL |
95 | // Check for -testnet or -regtest parameter (BaseParams() calls are only valid after this clause) |
96 | if (!SelectBaseParamsFromCommandLine()) { | |
9d2b73d1 WL |
97 | fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n"); |
98 | return false; | |
99 | } | |
afd64f76 WL |
100 | if (GetBoolArg("-rpcssl", false)) |
101 | { | |
102 | fprintf(stderr, "Error: SSL mode for RPC (-rpcssl) is no longer supported.\n"); | |
103 | return false; | |
104 | } | |
2a03a390 WL |
105 | return true; |
106 | } | |
107 | ||
afd64f76 WL |
108 | |
109 | /** Reply structure for request_done to fill in */ | |
110 | struct HTTPReply | |
b750cf1f | 111 | { |
afd64f76 WL |
112 | int status; |
113 | std::string body; | |
114 | }; | |
115 | ||
116 | static void http_request_done(struct evhttp_request *req, void *ctx) | |
117 | { | |
118 | HTTPReply *reply = static_cast<HTTPReply*>(ctx); | |
119 | ||
120 | if (req == NULL) { | |
121 | /* If req is NULL, it means an error occurred while connecting, but | |
122 | * I'm not sure how to find out which one. We also don't really care. | |
123 | */ | |
124 | reply->status = 0; | |
125 | return; | |
126 | } | |
b750cf1f | 127 | |
afd64f76 WL |
128 | reply->status = evhttp_request_get_response_code(req); |
129 | ||
130 | struct evbuffer *buf = evhttp_request_get_input_buffer(req); | |
131 | if (buf) | |
132 | { | |
133 | size_t size = evbuffer_get_length(buf); | |
134 | const char *data = (const char*)evbuffer_pullup(buf, size); | |
135 | if (data) | |
136 | reply->body = std::string(data, size); | |
137 | evbuffer_drain(buf, size); | |
138 | } | |
139 | } | |
140 | ||
141 | UniValue CallRPC(const string& strMethod, const UniValue& params) | |
142 | { | |
143 | std::string host = GetArg("-rpcconnect", "127.0.0.1"); | |
144 | int port = GetArg("-rpcport", BaseParams().RPCPort()); | |
145 | ||
146 | // Create event base | |
147 | struct event_base *base = event_base_new(); // TODO RAII | |
148 | if (!base) | |
149 | throw runtime_error("cannot create event_base"); | |
150 | ||
151 | // Synchronously look up hostname | |
152 | struct evhttp_connection *evcon = evhttp_connection_base_new(base, NULL, host.c_str(), port); // TODO RAII | |
153 | if (evcon == NULL) | |
154 | throw runtime_error("create connection failed"); | |
155 | evhttp_connection_set_timeout(evcon, GetArg("-rpctimeout", 30)); | |
156 | ||
157 | HTTPReply response; | |
158 | struct evhttp_request *req = evhttp_request_new(http_request_done, (void*)&response); // TODO RAII | |
159 | if (req == NULL) | |
160 | throw runtime_error("create http request failed"); | |
161 | ||
162 | // Get credentials | |
e957192c WL |
163 | std::string strRPCUserColonPass; |
164 | if (mapArgs["-rpcpassword"] == "") { | |
165 | // Try fall back to cookie-based authentication if no password is provided | |
166 | if (!GetAuthCookie(&strRPCUserColonPass)) { | |
167 | throw runtime_error(strprintf( | |
afd64f76 | 168 | _("Could not locate RPC credentials. No authentication cookie could be found, and no rpcpassword is set in the configuration file (%s)"), |
e957192c WL |
169 | GetConfigFile().string().c_str())); |
170 | ||
171 | } | |
172 | } else { | |
173 | strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; | |
174 | } | |
175 | ||
afd64f76 WL |
176 | struct evkeyvalq *output_headers = evhttp_request_get_output_headers(req); |
177 | assert(output_headers); | |
178 | evhttp_add_header(output_headers, "Host", host.c_str()); | |
179 | evhttp_add_header(output_headers, "Connection", "close"); | |
180 | evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str()); | |
181 | ||
182 | // Attach request data | |
183 | std::string strRequest = JSONRPCRequest(strMethod, params, 1); | |
184 | struct evbuffer * output_buffer = evhttp_request_get_output_buffer(req); | |
185 | assert(output_buffer); | |
186 | evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); | |
187 | ||
188 | int r = evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/"); | |
189 | if (r != 0) { | |
190 | evhttp_connection_free(evcon); | |
191 | event_base_free(base); | |
192 | throw CConnectionFailed("send http request failed"); | |
193 | } | |
b750cf1f | 194 | |
afd64f76 WL |
195 | event_base_dispatch(base); |
196 | evhttp_connection_free(evcon); | |
197 | event_base_free(base); | |
b750cf1f | 198 | |
afd64f76 WL |
199 | if (response.status == 0) |
200 | throw CConnectionFailed("couldn't connect to server"); | |
201 | else if (response.status == HTTP_UNAUTHORIZED) | |
b750cf1f | 202 | throw runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); |
afd64f76 WL |
203 | else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR) |
204 | throw runtime_error(strprintf("server returned HTTP error %d", response.status)); | |
205 | else if (response.body.empty()) | |
b750cf1f WL |
206 | throw runtime_error("no response from server"); |
207 | ||
208 | // Parse reply | |
851f58f9 | 209 | UniValue valReply(UniValue::VSTR); |
afd64f76 | 210 | if (!valReply.read(response.body)) |
b750cf1f | 211 | throw runtime_error("couldn't parse reply from server"); |
d014114d | 212 | const UniValue& reply = valReply.get_obj(); |
b750cf1f WL |
213 | if (reply.empty()) |
214 | throw runtime_error("expected reply to have result, error and id properties"); | |
215 | ||
216 | return reply; | |
217 | } | |
218 | ||
219 | int CommandLineRPC(int argc, char *argv[]) | |
220 | { | |
221 | string strPrint; | |
222 | int nRet = 0; | |
3ce7e669 | 223 | try { |
b750cf1f | 224 | // Skip switches |
3ce7e669 | 225 | while (argc > 1 && IsSwitchChar(argv[1][0])) { |
b750cf1f WL |
226 | argc--; |
227 | argv++; | |
228 | } | |
229 | ||
230 | // Method | |
231 | if (argc < 2) | |
232 | throw runtime_error("too few parameters"); | |
233 | string strMethod = argv[1]; | |
234 | ||
235 | // Parameters default to strings | |
236 | std::vector<std::string> strParams(&argv[2], &argv[argc]); | |
851f58f9 | 237 | UniValue params = RPCConvertValues(strMethod, strParams); |
b750cf1f | 238 | |
af82884a DK |
239 | // Execute and handle connection failures with -rpcwait |
240 | const bool fWait = GetBoolArg("-rpcwait", false); | |
241 | do { | |
242 | try { | |
851f58f9 | 243 | const UniValue reply = CallRPC(strMethod, params); |
af82884a DK |
244 | |
245 | // Parse reply | |
d014114d JS |
246 | const UniValue& result = find_value(reply, "result"); |
247 | const UniValue& error = find_value(reply, "error"); | |
af82884a | 248 | |
ed21d5bd | 249 | if (!error.isNull()) { |
af82884a | 250 | // Error |
ed21d5bd | 251 | int code = error["code"].get_int(); |
d014114d JS |
252 | if (fWait && code == RPC_IN_WARMUP) |
253 | throw CConnectionFailed("server in warmup"); | |
254 | strPrint = "error: " + error.write(); | |
af82884a | 255 | nRet = abs(code); |
f061578b JS |
256 | if (error.isObject()) |
257 | { | |
258 | UniValue errCode = find_value(error, "code"); | |
259 | UniValue errMsg = find_value(error, "message"); | |
260 | strPrint = errCode.isNull() ? "" : "error code: "+errCode.getValStr()+"\n"; | |
261 | ||
262 | if (errMsg.isStr()) | |
263 | strPrint += "error message:\n"+errMsg.get_str(); | |
264 | } | |
af82884a DK |
265 | } else { |
266 | // Result | |
ed21d5bd | 267 | if (result.isNull()) |
af82884a | 268 | strPrint = ""; |
ed21d5bd | 269 | else if (result.isStr()) |
af82884a DK |
270 | strPrint = result.get_str(); |
271 | else | |
ed21d5bd | 272 | strPrint = result.write(2); |
af82884a | 273 | } |
af82884a DK |
274 | // Connection succeeded, no need to retry. |
275 | break; | |
276 | } | |
27df4123 | 277 | catch (const CConnectionFailed&) { |
af82884a DK |
278 | if (fWait) |
279 | MilliSleep(1000); | |
280 | else | |
281 | throw; | |
282 | } | |
283 | } while (fWait); | |
b750cf1f | 284 | } |
27df4123 | 285 | catch (const boost::thread_interrupted&) { |
b750cf1f WL |
286 | throw; |
287 | } | |
27df4123 | 288 | catch (const std::exception& e) { |
b750cf1f WL |
289 | strPrint = string("error: ") + e.what(); |
290 | nRet = EXIT_FAILURE; | |
291 | } | |
292 | catch (...) { | |
293 | PrintExceptionContinue(NULL, "CommandLineRPC()"); | |
294 | throw; | |
295 | } | |
296 | ||
3ce7e669 | 297 | if (strPrint != "") { |
b750cf1f WL |
298 | fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); |
299 | } | |
300 | return nRet; | |
301 | } | |
302 | ||
2a03a390 WL |
303 | int main(int argc, char* argv[]) |
304 | { | |
5248ff40 SC |
305 | SetupEnvironment(); |
306 | ||
3ce7e669 | 307 | try { |
2a03a390 | 308 | if(!AppInitRPC(argc, argv)) |
0cafb630 | 309 | return EXIT_FAILURE; |
2a03a390 | 310 | } |
27df4123 | 311 | catch (const std::exception& e) { |
2a03a390 | 312 | PrintExceptionContinue(&e, "AppInitRPC()"); |
0cafb630 | 313 | return EXIT_FAILURE; |
2a03a390 WL |
314 | } catch (...) { |
315 | PrintExceptionContinue(NULL, "AppInitRPC()"); | |
0cafb630 | 316 | return EXIT_FAILURE; |
2a03a390 WL |
317 | } |
318 | ||
0cafb630 | 319 | int ret = EXIT_FAILURE; |
3ce7e669 | 320 | try { |
a7199038 | 321 | ret = CommandLineRPC(argc, argv); |
2a03a390 | 322 | } |
27df4123 | 323 | catch (const std::exception& e) { |
2a03a390 WL |
324 | PrintExceptionContinue(&e, "CommandLineRPC()"); |
325 | } catch (...) { | |
326 | PrintExceptionContinue(NULL, "CommandLineRPC()"); | |
327 | } | |
a7199038 | 328 | return ret; |
2a03a390 | 329 | } |