You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

starwels-cli.cpp 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. // Copyright (c) 2009-2010 Satoshi Nakamoto
  2. // Copyright (c) 2009-2016 The Starwels developers
  3. // Distributed under the MIT software license, see the accompanying
  4. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  5. #if defined(HAVE_CONFIG_H)
  6. #include "config/starwels-config.h"
  7. #endif
  8. #include "chainparamsbase.h"
  9. #include "clientversion.h"
  10. #include "fs.h"
  11. #include "rpc/client.h"
  12. #include "rpc/protocol.h"
  13. #include "util.h"
  14. #include "utilstrencodings.h"
  15. #include <stdio.h>
  16. #include <event2/buffer.h>
  17. #include <event2/keyvalq_struct.h>
  18. #include "support/events.h"
  19. #include <univalue.h>
  20. static const char DEFAULT_RPCCONNECT[] = "127.0.0.1";
  21. static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900;
  22. static const bool DEFAULT_NAMED=false;
  23. static const int CONTINUE_EXECUTION=-1;
  24. std::string HelpMessageCli()
  25. {
  26. const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN);
  27. const auto aiBaseParams = CreateBaseChainParams(CBaseChainParams::AI);
  28. std::string strUsage;
  29. strUsage += HelpMessageGroup(_("Options:"));
  30. strUsage += HelpMessageOpt("-?", _("This help message"));
  31. strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), STARWELS_CONF_FILENAME));
  32. strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory"));
  33. AppendParamsHelpMessages(strUsage);
  34. strUsage += HelpMessageOpt("-named", strprintf(_("Pass named instead of positional arguments (default: %s)"), DEFAULT_NAMED));
  35. strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf(_("Send commands to node running on <ip> (default: %s)"), DEFAULT_RPCCONNECT));
  36. strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Connect to JSON-RPC on <port> (default: %u or ai: %u)"), defaultBaseParams->RPCPort(), aiBaseParams->RPCPort()));
  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"));
  40. strUsage += HelpMessageOpt("-rpcclienttimeout=<n>", strprintf(_("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)"), DEFAULT_HTTP_CLIENT_TIMEOUT));
  41. strUsage += HelpMessageOpt("-stdin", _("Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases)"));
  42. strUsage += HelpMessageOpt("-rpcwallet=<walletname>", _("Send RPC for non-default wallet on RPC server (argument is wallet filename in starwelsd directory, required if starwelsd/-Qt runs with multiple wallets)"));
  43. return strUsage;
  44. }
  45. //////////////////////////////////////////////////////////////////////////////
  46. //
  47. // Start
  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. explicit inline CConnectionFailed(const std::string& msg) :
  57. std::runtime_error(msg)
  58. {}
  59. };
  60. //
  61. // This function returns either one of EXIT_ codes when it's expected to stop the process or
  62. // CONTINUE_EXECUTION when it's expected to continue further.
  63. //
  64. static int AppInitRPC(int argc, char* argv[])
  65. {
  66. //
  67. // Parameters
  68. //
  69. gArgs.ParseParameters(argc, argv);
  70. if (argc<2 || gArgs.IsArgSet("-?") || gArgs.IsArgSet("-h") || gArgs.IsArgSet("-help") || gArgs.IsArgSet("-version")) {
  71. std::string strUsage = strprintf(_("%s RPC client version"), _(PACKAGE_NAME)) + " " + FormatFullVersion() + "\n";
  72. if (!gArgs.IsArgSet("-version")) {
  73. strUsage += "\n" + _("Usage:") + "\n" +
  74. " starwels-cli [options] <command> [params] " + strprintf(_("Send command to %s"), _(PACKAGE_NAME)) + "\n" +
  75. " starwels-cli [options] -named <command> [name=value] ... " + strprintf(_("Send command to %s (with named arguments)"), _(PACKAGE_NAME)) + "\n" +
  76. " starwels-cli [options] help " + _("List commands") + "\n" +
  77. " starwels-cli [options] help <command> " + _("Get help for a command") + "\n";
  78. strUsage += "\n" + HelpMessageCli();
  79. }
  80. fprintf(stdout, "%s", strUsage.c_str());
  81. if (argc < 2) {
  82. fprintf(stderr, "Error: too few parameters\n");
  83. return EXIT_FAILURE;
  84. }
  85. return EXIT_SUCCESS;
  86. }
  87. if (!fs::is_directory(GetDataDir(false))) {
  88. fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str());
  89. return EXIT_FAILURE;
  90. }
  91. try {
  92. gArgs.ReadConfigFile(gArgs.GetArg("-conf", STARWELS_CONF_FILENAME));
  93. } catch (const std::exception& e) {
  94. fprintf(stderr,"Error reading configuration file: %s\n", e.what());
  95. return EXIT_FAILURE;
  96. }
  97. // Check for -ai or -regtest parameter (BaseParams() calls are only valid after this clause)
  98. try {
  99. SelectBaseParams(ChainNameFromCommandLine());
  100. } catch (const std::exception& e) {
  101. fprintf(stderr, "Error: %s\n", e.what());
  102. return EXIT_FAILURE;
  103. }
  104. if (gArgs.GetBoolArg("-rpcssl", false))
  105. {
  106. fprintf(stderr, "Error: SSL mode for RPC (-rpcssl) is no longer supported.\n");
  107. return EXIT_FAILURE;
  108. }
  109. return CONTINUE_EXECUTION;
  110. }
  111. /** Reply structure for request_done to fill in */
  112. struct HTTPReply
  113. {
  114. HTTPReply(): status(0), error(-1) {}
  115. int status;
  116. int error;
  117. std::string body;
  118. };
  119. const char *http_errorstring(int code)
  120. {
  121. switch(code) {
  122. #if LIBEVENT_VERSION_NUMBER >= 0x02010300
  123. case EVREQ_HTTP_TIMEOUT:
  124. return "timeout reached";
  125. case EVREQ_HTTP_EOF:
  126. return "EOF reached";
  127. case EVREQ_HTTP_INVALID_HEADER:
  128. return "error while reading header, or invalid header";
  129. case EVREQ_HTTP_BUFFER_ERROR:
  130. return "error encountered while reading or writing";
  131. case EVREQ_HTTP_REQUEST_CANCEL:
  132. return "request was canceled";
  133. case EVREQ_HTTP_DATA_TOO_LONG:
  134. return "response body is larger than allowed";
  135. #endif
  136. default:
  137. return "unknown";
  138. }
  139. }
  140. static void http_request_done(struct evhttp_request *req, void *ctx)
  141. {
  142. HTTPReply *reply = static_cast<HTTPReply*>(ctx);
  143. if (req == nullptr) {
  144. /* If req is nullptr, it means an error occurred while connecting: the
  145. * error code will have been passed to http_error_cb.
  146. */
  147. reply->status = 0;
  148. return;
  149. }
  150. reply->status = evhttp_request_get_response_code(req);
  151. struct evbuffer *buf = evhttp_request_get_input_buffer(req);
  152. if (buf)
  153. {
  154. size_t size = evbuffer_get_length(buf);
  155. const char *data = (const char*)evbuffer_pullup(buf, size);
  156. if (data)
  157. reply->body = std::string(data, size);
  158. evbuffer_drain(buf, size);
  159. }
  160. }
  161. #if LIBEVENT_VERSION_NUMBER >= 0x02010300
  162. static void http_error_cb(enum evhttp_request_error err, void *ctx)
  163. {
  164. HTTPReply *reply = static_cast<HTTPReply*>(ctx);
  165. reply->error = err;
  166. }
  167. #endif
  168. UniValue CallRPC(const std::string& strMethod, const UniValue& params)
  169. {
  170. std::string host;
  171. // In preference order, we choose the following for the port:
  172. // 1. -rpcport
  173. // 2. port in -rpcconnect (ie following : in ipv4 or ]: in ipv6)
  174. // 3. default port for chain
  175. int port = BaseParams().RPCPort();
  176. SplitHostPort(gArgs.GetArg("-rpcconnect", DEFAULT_RPCCONNECT), port, host);
  177. port = gArgs.GetArg("-rpcport", port);
  178. // Obtain event base
  179. raii_event_base base = obtain_event_base();
  180. // Synchronously look up hostname
  181. raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port);
  182. evhttp_connection_set_timeout(evcon.get(), gArgs.GetArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT));
  183. HTTPReply response;
  184. raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response);
  185. if (req == nullptr)
  186. throw std::runtime_error("create http request failed");
  187. #if LIBEVENT_VERSION_NUMBER >= 0x02010300
  188. evhttp_request_set_error_cb(req.get(), http_error_cb);
  189. #endif
  190. // Get credentials
  191. std::string strRPCUserColonPass;
  192. if (gArgs.GetArg("-rpcpassword", "") == "") {
  193. // Try fall back to cookie-based authentication if no password is provided
  194. if (!GetAuthCookie(&strRPCUserColonPass)) {
  195. throw std::runtime_error(strprintf(
  196. _("Could not locate RPC credentials. No authentication cookie could be found, and no rpcpassword is set in the configuration file (%s)"),
  197. GetConfigFile(gArgs.GetArg("-conf", STARWELS_CONF_FILENAME)).string().c_str()));
  198. }
  199. } else {
  200. strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
  201. }
  202. struct evkeyvalq* output_headers = evhttp_request_get_output_headers(req.get());
  203. assert(output_headers);
  204. evhttp_add_header(output_headers, "Host", host.c_str());
  205. evhttp_add_header(output_headers, "Connection", "close");
  206. evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());
  207. // Attach request data
  208. std::string strRequest = JSONRPCRequestObj(strMethod, params, 1).write() + "\n";
  209. struct evbuffer* output_buffer = evhttp_request_get_output_buffer(req.get());
  210. assert(output_buffer);
  211. evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
  212. // check if we should use a special wallet endpoint
  213. std::string endpoint = "/";
  214. std::string walletName = gArgs.GetArg("-rpcwallet", "");
  215. if (!walletName.empty()) {
  216. char *encodedURI = evhttp_uriencode(walletName.c_str(), walletName.size(), false);
  217. if (encodedURI) {
  218. endpoint = "/wallet/"+ std::string(encodedURI);
  219. free(encodedURI);
  220. }
  221. else {
  222. throw CConnectionFailed("uri-encode failed");
  223. }
  224. }
  225. int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, endpoint.c_str());
  226. req.release(); // ownership moved to evcon in above call
  227. if (r != 0) {
  228. throw CConnectionFailed("send http request failed");
  229. }
  230. event_base_dispatch(base.get());
  231. if (response.status == 0)
  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));
  233. else if (response.status == HTTP_UNAUTHORIZED)
  234. throw std::runtime_error("incorrect rpcuser or rpcpassword (authorization failed)");
  235. else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR)
  236. throw std::runtime_error(strprintf("server returned HTTP error %d", response.status));
  237. else if (response.body.empty())
  238. throw std::runtime_error("no response from server");
  239. // Parse reply
  240. UniValue valReply(UniValue::VSTR);
  241. if (!valReply.read(response.body))
  242. throw std::runtime_error("couldn't parse reply from server");
  243. const UniValue& reply = valReply.get_obj();
  244. if (reply.empty())
  245. throw std::runtime_error("expected reply to have result, error and id properties");
  246. return reply;
  247. }
  248. int CommandLineRPC(int argc, char *argv[])
  249. {
  250. std::string strPrint;
  251. int nRet = 0;
  252. try {
  253. // Skip switches
  254. while (argc > 1 && IsSwitchChar(argv[1][0])) {
  255. argc--;
  256. argv++;
  257. }
  258. std::vector<std::string> args = std::vector<std::string>(&argv[1], &argv[argc]);
  259. if (gArgs.GetBoolArg("-stdin", false)) {
  260. // Read one arg per line from stdin and append
  261. std::string line;
  262. while (std::getline(std::cin,line))
  263. args.push_back(line);
  264. }
  265. if (args.size() < 1)
  266. throw std::runtime_error("too few parameters (need at least command)");
  267. std::string strMethod = args[0];
  268. args.erase(args.begin()); // Remove trailing method name from arguments vector
  269. UniValue params;
  270. if(gArgs.GetBoolArg("-named", DEFAULT_NAMED)) {
  271. params = RPCConvertNamedValues(strMethod, args);
  272. } else {
  273. params = RPCConvertValues(strMethod, args);
  274. }
  275. // Execute and handle connection failures with -rpcwait
  276. const bool fWait = gArgs.GetBoolArg("-rpcwait", false);
  277. do {
  278. try {
  279. const UniValue reply = CallRPC(strMethod, params);
  280. // Parse reply
  281. const UniValue& result = find_value(reply, "result");
  282. const UniValue& error = find_value(reply, "error");
  283. if (!error.isNull()) {
  284. // Error
  285. int code = error["code"].get_int();
  286. if (fWait && code == RPC_IN_WARMUP)
  287. throw CConnectionFailed("server in warmup");
  288. strPrint = "error: " + error.write();
  289. nRet = abs(code);
  290. if (error.isObject())
  291. {
  292. UniValue errCode = find_value(error, "code");
  293. UniValue errMsg = find_value(error, "message");
  294. strPrint = errCode.isNull() ? "" : "error code: "+errCode.getValStr()+"\n";
  295. if (errMsg.isStr())
  296. strPrint += "error message:\n"+errMsg.get_str();
  297. if (errCode.isNum() && errCode.get_int() == RPC_WALLET_NOT_SPECIFIED) {
  298. strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to starwels-cli command line.";
  299. }
  300. }
  301. } else {
  302. // Result
  303. if (result.isNull())
  304. strPrint = "";
  305. else if (result.isStr())
  306. strPrint = result.get_str();
  307. else
  308. strPrint = result.write(2);
  309. }
  310. // Connection succeeded, no need to retry.
  311. break;
  312. }
  313. catch (const CConnectionFailed&) {
  314. if (fWait)
  315. MilliSleep(1000);
  316. else
  317. throw;
  318. }
  319. } while (fWait);
  320. }
  321. catch (const boost::thread_interrupted&) {
  322. throw;
  323. }
  324. catch (const std::exception& e) {
  325. strPrint = std::string("error: ") + e.what();
  326. nRet = EXIT_FAILURE;
  327. }
  328. catch (...) {
  329. PrintExceptionContinue(nullptr, "CommandLineRPC()");
  330. throw;
  331. }
  332. if (strPrint != "") {
  333. fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str());
  334. }
  335. return nRet;
  336. }
  337. int main(int argc, char* argv[])
  338. {
  339. SetupEnvironment();
  340. if (!SetupNetworking()) {
  341. fprintf(stderr, "Error: Initializing networking failed\n");
  342. return EXIT_FAILURE;
  343. }
  344. try {
  345. int ret = AppInitRPC(argc, argv);
  346. if (ret != CONTINUE_EXECUTION)
  347. return ret;
  348. }
  349. catch (const std::exception& e) {
  350. PrintExceptionContinue(&e, "AppInitRPC()");
  351. return EXIT_FAILURE;
  352. } catch (...) {
  353. PrintExceptionContinue(nullptr, "AppInitRPC()");
  354. return EXIT_FAILURE;
  355. }
  356. int ret = EXIT_FAILURE;
  357. try {
  358. ret = CommandLineRPC(argc, argv);
  359. }
  360. catch (const std::exception& e) {
  361. PrintExceptionContinue(&e, "CommandLineRPC()");
  362. } catch (...) {
  363. PrintExceptionContinue(nullptr, "CommandLineRPC()");
  364. }
  365. return ret;
  366. }