PIVX Core  5.6.99
P2P Digital Currency
pivx-cli.cpp
Go to the documentation of this file.
1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Copyright (c) 2009-2021 The Bitcoin developers
3 // Copyright (c) 2009-2015 The Dash developers
4 // Copyright (c) 2015-2022 The PIVX Core developers
5 // Distributed under the MIT/X11 software license, see the accompanying
6 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
7 
8 #if defined(HAVE_CONFIG_H)
9 #include "config/pivx-config.h"
10 #endif
11 
12 #include "chainparamsbase.h"
13 #include "clientversion.h"
14 #include "fs.h"
15 #include "rpc/client.h"
16 #include "rpc/protocol.h"
17 #include "util/system.h"
18 #include "utilstrencodings.h"
19 
20 #include <stdio.h>
21 #include <tuple>
22 
23 #include <event2/buffer.h>
24 #include <event2/keyvalq_struct.h>
25 #include "support/events.h"
26 
27 #include <univalue.h>
28 
29 static const char DEFAULT_RPCCONNECT[] = "127.0.0.1";
30 static const bool DEFAULT_NAMED=false;
31 static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900;
32 static const int CONTINUE_EXECUTION=-1;
33 
34 std::string HelpMessageCli()
35 {
36  const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN);
37  const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET);
38  std::string strUsage;
39  strUsage += HelpMessageGroup("Options:");
40  strUsage += HelpMessageOpt("-?", "This help message");
41  strUsage += HelpMessageOpt("-conf=<file>", strprintf("Specify configuration file (default: %s)", PIVX_CONF_FILENAME));
42  strUsage += HelpMessageOpt("-datadir=<dir>", "Specify data directory");
43  AppendParamsHelpMessages(strUsage);
44  strUsage += HelpMessageOpt("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED));
45  strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT));
46  strUsage += HelpMessageOpt("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u or testnet: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort()));
47  strUsage += HelpMessageOpt("-rpcwait", "Wait for RPC server to start");
48  strUsage += HelpMessageOpt("-rpcuser=<user>", "Username for JSON-RPC connections");
49  strUsage += HelpMessageOpt("-rpcpassword=<pw>", "Password for JSON-RPC connections");
50  strUsage += HelpMessageOpt("-rpcclienttimeout=<n>", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT));
51  strUsage += HelpMessageOpt("-rpcwallet=<walletname>", "Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to pivxd)");
52 
53  return strUsage;
54 }
55 
57 static void libevent_log_cb(int severity, const char *msg)
58 {
59 #ifndef EVENT_LOG_ERR // EVENT_LOG_ERR was added in 2.0.19; but before then _EVENT_LOG_ERR existed.
60 # define EVENT_LOG_ERR _EVENT_LOG_ERR
61 #endif
62  // Ignore everything other than errors
63  if (severity >= EVENT_LOG_ERR) {
64  throw std::runtime_error(strprintf("libevent error: %s", msg));
65  }
66 }
67 
69 //
70 // Start
71 //
72 
73 //
74 // Exception thrown on connection error. This error is used to determine
75 // when to wait if -rpcwait is given.
76 //
77 class CConnectionFailed : public std::runtime_error
78 {
79 public:
80  explicit inline CConnectionFailed(const std::string& msg) : std::runtime_error(msg)
81  {
82  }
83 };
84 
85 //
86 // This function returns either one of EXIT_ codes when it's expected to stop the process or
87 // CONTINUE_EXECUTION when it's expected to continue further.
88 //
89 static int AppInitRPC(int argc, char* argv[])
90 {
91  //
92  // Parameters
93  //
94  gArgs.ParseParameters(argc, argv);
95  if (argc < 2 || gArgs.IsArgSet("-?") || gArgs.IsArgSet("-h") || gArgs.IsArgSet("-help") || gArgs.IsArgSet("-version")) {
96  std::string strUsage = PACKAGE_NAME " RPC client version " + FormatFullVersion() + "\n";
97  if (!gArgs.IsArgSet("-version")) {
98  strUsage += "\n"
99  "Usage: pivx-cli [options] <command> [params] Send command to " PACKAGE_NAME "\n"
100  "or: pivx-cli [options] -named <command> [name=value]... Send command to " PACKAGE_NAME " (with named arguments)\n"
101  "or: pivx-cli [options] help List commands\n"
102  "or: pivx-cli [options] help <command> Get help for a command\n";
103  strUsage += "\n" + HelpMessageCli();
104  }
105 
106  fprintf(stdout, "%s", strUsage.c_str());
107  if (argc < 2) {
108  fprintf(stderr, "Error: too few parameters\n");
109  return EXIT_FAILURE;
110  }
111  return EXIT_SUCCESS;
112  }
113  if (!CheckDataDirOption()) {
114  fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str());
115  return EXIT_FAILURE;
116  }
117  try {
119  } catch (const std::exception& e) {
120  fprintf(stderr, "Error reading configuration file: %s\n", e.what());
121  return EXIT_FAILURE;
122  }
123  // Check for -testnet or -regtest parameter (BaseParams() calls are only valid after this clause)
124  try {
126  } catch(const std::exception& e) {
127  fprintf(stderr, "Error: %s\n", e.what());
128  return EXIT_FAILURE;
129  }
130  if (gArgs.GetBoolArg("-rpcssl", false))
131  {
132  fprintf(stderr, "Error: SSL mode for RPC (-rpcssl) is no longer supported.\n");
133  return EXIT_FAILURE;
134  }
135  return CONTINUE_EXECUTION;
136 }
137 
138 
140 struct HTTPReply
141 {
142  HTTPReply(): status(0), error(-1) {}
143 
144  int status;
145  int error;
146  std::string body;
147 };
148 
149 const char *http_errorstring(int code)
150 {
151  switch(code) {
152 #if LIBEVENT_VERSION_NUMBER >= 0x02010300
153  case EVREQ_HTTP_TIMEOUT:
154  return "timeout reached";
155  case EVREQ_HTTP_EOF:
156  return "EOF reached";
157  case EVREQ_HTTP_INVALID_HEADER:
158  return "error while reading header, or invalid header";
159  case EVREQ_HTTP_BUFFER_ERROR:
160  return "error encountered while reading or writing";
161  case EVREQ_HTTP_REQUEST_CANCEL:
162  return "request was canceled";
163  case EVREQ_HTTP_DATA_TOO_LONG:
164  return "response body is larger than allowed";
165 #endif
166  default:
167  return "unknown";
168  }
169 }
170 
171 static void http_request_done(struct evhttp_request *req, void *ctx)
172 {
173  HTTPReply *reply = static_cast<HTTPReply*>(ctx);
174 
175  if (req == nullptr) {
176  /* If req is nullptr, it means an error occurred while connecting: the
177  * error code will have been passed to http_error_cb.
178  */
179  reply->status = 0;
180  return;
181  }
182 
183  reply->status = evhttp_request_get_response_code(req);
184 
185  struct evbuffer *buf = evhttp_request_get_input_buffer(req);
186  if (buf)
187  {
188  size_t size = evbuffer_get_length(buf);
189  const char *data = (const char*)evbuffer_pullup(buf, size);
190  if (data)
191  reply->body = std::string(data, size);
192  evbuffer_drain(buf, size);
193  }
194 }
195 
196 #if LIBEVENT_VERSION_NUMBER >= 0x02010300
197 static void http_error_cb(enum evhttp_request_error err, void *ctx)
198 {
199  HTTPReply *reply = static_cast<HTTPReply*>(ctx);
200  reply->error = err;
201 }
202 #endif
203 
204 UniValue CallRPC(const std::string& strMethod, const UniValue& params)
205 {
206  std::string host = gArgs.GetArg("-rpcconnect", DEFAULT_RPCCONNECT);
207  int port = gArgs.GetArg("-rpcport", BaseParams().RPCPort());
208 
209  // Obtain event base
210  raii_event_base base = obtain_event_base();
211 
212  // Synchronously look up hostname
213  raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port);
214  evhttp_connection_set_timeout(evcon.get(), gArgs.GetArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT));
215 
216  HTTPReply response;
217  raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response);
218  if (req == nullptr)
219  throw std::runtime_error("create http request failed");
220 #if LIBEVENT_VERSION_NUMBER >= 0x02010300
221  evhttp_request_set_error_cb(req.get(), http_error_cb);
222 #endif
223 
224  // Get credentials
225  std::string strRPCUserColonPass;
226  if (gArgs.GetArg("-rpcpassword", "") == "") {
227  // Try fall back to cookie-based authentication if no password is provided
228  if (!GetAuthCookie(&strRPCUserColonPass)) {
229  throw std::runtime_error(strprintf(
230  _("Could not locate RPC credentials. No authentication cookie could be found, and no rpcpassword is set in the configuration file (%s)"),
231  GetConfigFile(gArgs.GetArg("-conf", PIVX_CONF_FILENAME)).string().c_str()));
232 
233  }
234  } else {
235  strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
236  }
237 
238  struct evkeyvalq* output_headers = evhttp_request_get_output_headers(req.get());
239  assert(output_headers);
240  evhttp_add_header(output_headers, "Host", host.c_str());
241  evhttp_add_header(output_headers, "Connection", "close");
242  evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());
243 
244  // Attach request data
245  std::string strRequest = JSONRPCRequestObj(strMethod, params, 1).write() + "\n";
246  struct evbuffer* output_buffer = evhttp_request_get_output_buffer(req.get());
247  assert(output_buffer);
248  evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
249 
250  // check if we should use a special wallet endpoint
251  std::string endpoint = "/";
252  if (!gArgs.GetArgs("-rpcwallet").empty()) {
253  std::string walletName = gArgs.GetArg("-rpcwallet", "");
254  char* encodedURI = evhttp_uriencode(walletName.c_str(), walletName.size(), false);
255  if (encodedURI) {
256  endpoint = "/wallet/"+ std::string(encodedURI);
257  free(encodedURI);
258  } else {
259  throw CConnectionFailed("uri-encode failed");
260  }
261  }
262  int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/");
263  req.release(); // ownership moved to evcon in above call
264  if (r != 0) {
265  throw CConnectionFailed("send http request failed");
266  }
267 
268  event_base_dispatch(base.get());
269 
270  if (response.status == 0)
271  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));
272  else if (response.status == HTTP_UNAUTHORIZED)
273  throw std::runtime_error("incorrect rpcuser or rpcpassword (authorization failed)");
274  else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR)
275  throw std::runtime_error(strprintf("server returned HTTP error %d", response.status));
276  else if (response.body.empty())
277  throw std::runtime_error("no response from server");
278 
279  // Parse reply
280  UniValue valReply(UniValue::VSTR);
281  if (!valReply.read(response.body))
282  throw std::runtime_error("couldn't parse reply from server");
283  const UniValue& reply = valReply.get_obj();
284  if (reply.empty())
285  throw std::runtime_error("expected reply to have result, error and id properties");
286 
287  return reply;
288 }
289 
290 int CommandLineRPC(int argc, char* argv[])
291 {
292  std::string strPrint;
293  int nRet = 0;
294  try {
295  // Skip switches
296  while (argc > 1 && IsSwitchChar(argv[1][0])) {
297  argc--;
298  argv++;
299  }
300 
301  // Method
302  if (argc < 2)
303  throw std::runtime_error("too few parameters");
304  std::string strMethod = argv[1];
305 
306  // Parameters default to strings
307  std::vector<std::string> strParams(&argv[2], &argv[argc]);
308  UniValue params;
309  if(gArgs.GetBoolArg("-named", DEFAULT_NAMED)) {
310  params = RPCConvertNamedValues(strMethod, strParams);
311  } else {
312  params = RPCConvertValues(strMethod, strParams);
313  }
314 
315  // Execute and handle connection failures with -rpcwait
316  const bool fWait = gArgs.GetBoolArg("-rpcwait", false);
317  do {
318  try {
319  const UniValue reply = CallRPC(strMethod, params);
320 
321  // Parse reply
322  const UniValue& result = find_value(reply, "result");
323  const UniValue& error = find_value(reply, "error");
324 
325  if (!error.isNull()) {
326  // Error
327  int code = error["code"].get_int();
328  if (fWait && code == RPC_IN_WARMUP)
329  throw CConnectionFailed("server in warmup");
330  strPrint = "error: " + error.write();
331  nRet = abs(code);
332  if (error.isObject()) {
333  UniValue errCode = find_value(error, "code");
334  UniValue errMsg = find_value(error, "message");
335  strPrint = errCode.isNull() ? "" : "error code: "+errCode.getValStr()+"\n";
336 
337  if (errMsg.isStr())
338  strPrint += "error message:\n"+errMsg.get_str();
339 
340  if (errCode.isNum() && errCode.get_int() == RPC_WALLET_NOT_SPECIFIED) {
341  strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to pivx-cli command line.";
342  }
343  }
344  } else {
345  // Result
346  if (result.isNull())
347  strPrint = "";
348  else if (result.isStr())
349  strPrint = result.get_str();
350  else
351  strPrint = result.write(2);
352  }
353  // Connection succeeded, no need to retry.
354  break;
355  } catch (const CConnectionFailed& e) {
356  if (fWait)
357  MilliSleep(1000);
358  else
359  throw;
360  }
361  } while (fWait);
362  } catch (const boost::thread_interrupted&) {
363  throw;
364  } catch (const std::exception& e) {
365  strPrint = std::string("error: ") + e.what();
366  nRet = EXIT_FAILURE;
367  } catch (...) {
368  PrintExceptionContinue(nullptr, "CommandLineRPC()");
369  throw;
370  }
371 
372  if (strPrint != "") {
373  fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str());
374  }
375  return nRet;
376 }
377 
378 #ifdef WIN32
379 // Export main() and ensure working ASLR on Windows.
380 // Exporting a symbol will prevent the linker from stripping
381 // the .reloc section from the binary, which is a requirement
382 // for ASLR. This is a temporary workaround until a fixed
383 // version of binutils is used for releases.
384 __declspec(dllexport) int main(int argc, char* argv[])
385 {
386  util::WinCmdLineArgs winArgs;
387  std::tie(argc, argv) = winArgs.get();
388 #else
389 int main(int argc, char* argv[])
390 {
391 #endif
393  if (!SetupNetworking()) {
394  fprintf(stderr, "Error: Initializing networking failed\n");
395  return EXIT_FAILURE;
396  }
397  event_set_log_callback(&libevent_log_cb);
398 
399  try {
400  int ret = AppInitRPC(argc, argv);
401  if (ret != CONTINUE_EXECUTION)
402  return ret;
403  } catch (const std::exception& e) {
404  PrintExceptionContinue(&e, "AppInitRPC()");
405  return EXIT_FAILURE;
406  } catch (...) {
407  PrintExceptionContinue(nullptr, "AppInitRPC()");
408  return EXIT_FAILURE;
409  }
410 
411  int ret = EXIT_FAILURE;
412  try {
413  ret = CommandLineRPC(argc, argv);
414  } catch (const std::exception& e) {
415  PrintExceptionContinue(&e, "CommandLineRPC()");
416  } catch (...) {
417  PrintExceptionContinue(nullptr, "CommandLineRPC()");
418  }
419  return ret;
420 }
const CBaseChainParams & BaseParams()
Return the currently selected parameters.
void SelectBaseParams(const std::string &chain)
Sets the params returned by Params() to those for the given network.
std::unique_ptr< CBaseChainParams > CreateBaseChainParams(const std::string &chain)
Creates and returns a std::unique_ptr<CBaseChainParams> of the chosen chain.
void AppendParamsHelpMessages(std::string &strUsage, bool debugHelp)
Append the help messages for the chainparams options to the parameter string.
void ReadConfigFile(const std::string &confPath)
Definition: system.cpp:832
void ParseParameters(int argc, const char *const argv[])
Definition: system.cpp:371
std::vector< std::string > GetArgs(const std::string &strArg) const
Return a vector of strings of the given argument.
Definition: system.cpp:406
bool IsArgSet(const std::string &strArg) const
Return true if the given argument has been manually set.
Definition: system.cpp:425
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
Definition: system.cpp:449
bool GetBoolArg(const std::string &strArg, bool fDefault) const
Return boolean argument or default value.
Definition: system.cpp:465
std::string GetChainName() const
Looks for -regtest, -testnet and returns the appropriate BIP70 chain name.
Definition: system.cpp:861
static const std::string TESTNET
static const std::string MAIN
Chain name strings.
CConnectionFailed(const std::string &msg)
Definition: pivx-cli.cpp:80
const std::string & get_str() const
@ VSTR
Definition: univalue.h:21
std::string write(unsigned int prettyIndent=0, unsigned int indentLevel=0) const
bool isNull() const
Definition: univalue.h:77
const UniValue & get_obj() const
const std::string & getValStr() const
Definition: univalue.h:65
bool empty() const
Definition: univalue.h:66
bool isStr() const
Definition: univalue.h:81
bool isNum() const
Definition: univalue.h:82
bool read(const char *raw, size_t len)
int get_int() const
UniValue RPCConvertValues(const std::string &strMethod, const std::vector< std::string > &strParams)
Convert positional arguments to command-specific RPC representation.
Definition: client.cpp:250
UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector< std::string > &strParams)
Convert named arguments to command-specific RPC representation.
Definition: client.cpp:269
std::string FormatFullVersion()
raii_evhttp_request obtain_evhttp_request(void(*cb)(struct evhttp_request *, void *), void *arg)
Definition: events.h:45
raii_evhttp_connection obtain_evhttp_connection_base(struct event_base *base, std::string host, uint16_t port)
Definition: events.h:49
raii_event_base obtain_event_base()
Definition: events.h:30
char ** argv
Definition: fuzz.cpp:52
Definition: uint256.h:212
int main(int argc, char *argv[])
Definition: pivx-cli.cpp:389
std::string HelpMessageCli()
Definition: pivx-cli.cpp:34
int CommandLineRPC(int argc, char *argv[])
Definition: pivx-cli.cpp:290
const char * http_errorstring(int code)
Definition: pivx-cli.cpp:149
#define EVENT_LOG_ERR
UniValue CallRPC(const std::string &strMethod, const UniValue &params)
Definition: pivx-cli.cpp:204
#define PACKAGE_NAME
Definition: pivx-config.h:366
bool GetAuthCookie(std::string *cookie_out)
Read the RPC authentication cookie from disk.
Definition: protocol.cpp:100
UniValue JSONRPCRequestObj(const std::string &strMethod, const UniValue &params, const UniValue &id)
JSON-RPC protocol.
Definition: protocol.cpp:26
@ HTTP_BAD_REQUEST
Definition: protocol.h:22
@ HTTP_UNAUTHORIZED
Definition: protocol.h:23
@ HTTP_NOT_FOUND
Definition: protocol.h:25
@ HTTP_INTERNAL_SERVER_ERROR
Definition: protocol.h:27
@ RPC_WALLET_NOT_SPECIFIED
No wallet specified (error when there are multiple wallets loaded)
Definition: protocol.h:80
@ RPC_IN_WARMUP
Transaction already in chain.
Definition: protocol.h:52
Reply structure for request_done to fill in.
Definition: pivx-cli.cpp:141
int status
Definition: pivx-cli.cpp:144
std::string body
Definition: pivx-cli.cpp:146
const char *const PIVX_CONF_FILENAME
Definition: system.cpp:81
bool CheckDataDirOption()
Definition: system.cpp:755
bool SetupNetworking()
Definition: system.cpp:1070
ArgsManager gArgs
Definition: system.cpp:89
void SetupEnvironment()
Definition: system.cpp:1043
fs::path GetConfigFile(const std::string &confPath)
Definition: system.cpp:770
void PrintExceptionContinue(const std::exception *pex, const char *pszThread)
Definition: system.cpp:526
std::string HelpMessageGroup(const std::string &message)
Format a string to be used as group of options in help messages.
Definition: system.cpp:499
std::string HelpMessageOpt(const std::string &option, const std::string &message)
Format a string to be used as option description in help messages.
Definition: system.cpp:503
bool error(const char *fmt, const Args &... args)
Definition: system.h:77
std::string _(const char *psz)
Translation function: Call Translate signal on UI interface, which returns a Optional result.
Definition: system.h:65
bool IsSwitchChar(char c)
Definition: system.h:118
#define strprintf
Definition: tinyformat.h:1056
const UniValue & find_value(const UniValue &obj, const std::string &name)
Definition: univalue.cpp:234
std::string EncodeBase64(Span< const unsigned char > input)
void MilliSleep(int64_t n)
Definition: utiltime.cpp:82