LCOV - code coverage report
Current view: top level - src - pivx-cli.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 95 216 44.0 %
Date: 2025-02-23 09:33:43 Functions: 5 9 55.6 %

          Line data    Source code
       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           0 : std::string HelpMessageCli()
      35             : {
      36           0 :     const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN);
      37           0 :     const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET);
      38           0 :     std::string strUsage;
      39           0 :     strUsage += HelpMessageGroup("Options:");
      40           0 :     strUsage += HelpMessageOpt("-?", "This help message");
      41           0 :     strUsage += HelpMessageOpt("-conf=<file>", strprintf("Specify configuration file (default: %s)", PIVX_CONF_FILENAME));
      42           0 :     strUsage += HelpMessageOpt("-datadir=<dir>", "Specify data directory");
      43           0 :     AppendParamsHelpMessages(strUsage);
      44           0 :     strUsage += HelpMessageOpt("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED));
      45           0 :     strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT));
      46           0 :     strUsage += HelpMessageOpt("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u or testnet: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort()));
      47           0 :     strUsage += HelpMessageOpt("-rpcwait", "Wait for RPC server to start");
      48           0 :     strUsage += HelpMessageOpt("-rpcuser=<user>", "Username for JSON-RPC connections");
      49           0 :     strUsage += HelpMessageOpt("-rpcpassword=<pw>", "Password for JSON-RPC connections");
      50           0 :     strUsage += HelpMessageOpt("-rpcclienttimeout=<n>", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT));
      51           0 :     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           0 :     return strUsage;
      54             : }
      55             : 
      56             : /** libevent event log callback */
      57           0 : 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           0 :     if (severity >= EVENT_LOG_ERR) {
      64           0 :         throw std::runtime_error(strprintf("libevent error: %s", msg));
      65             :     }
      66           0 : }
      67             : 
      68             : //////////////////////////////////////////////////////////////////////////////
      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           0 :     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           3 : static int AppInitRPC(int argc, char* argv[])
      90             : {
      91             :     //
      92             :     // Parameters
      93             :     //
      94           3 :     gArgs.ParseParameters(argc, argv);
      95          15 :     if (argc < 2 || gArgs.IsArgSet("-?") || gArgs.IsArgSet("-h") || gArgs.IsArgSet("-help") || gArgs.IsArgSet("-version")) {
      96           0 :         std::string strUsage = PACKAGE_NAME " RPC client version " + FormatFullVersion() + "\n";
      97           0 :         if (!gArgs.IsArgSet("-version")) {
      98           0 :             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           0 :                         "or:     pivx-cli [options] help <command>      Get help for a command\n";
     103           0 :             strUsage += "\n" + HelpMessageCli();
     104             :         }
     105             : 
     106           0 :         fprintf(stdout, "%s", strUsage.c_str());
     107           0 :         if (argc < 2) {
     108           0 :             fprintf(stderr, "Error: too few parameters\n");
     109             :             return EXIT_FAILURE;
     110             :         }
     111             :         return EXIT_SUCCESS;
     112             :     }
     113           3 :     if (!CheckDataDirOption()) {
     114           0 :         fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str());
     115           0 :         return EXIT_FAILURE;
     116             :     }
     117           3 :     try {
     118           6 :         gArgs.ReadConfigFile(gArgs.GetArg("-conf", PIVX_CONF_FILENAME));
     119           0 :     } catch (const std::exception& e) {
     120           0 :         fprintf(stderr, "Error reading configuration file: %s\n", e.what());
     121           0 :         return EXIT_FAILURE;
     122             :     }
     123             :     // Check for -testnet or -regtest parameter (BaseParams() calls are only valid after this clause)
     124           3 :     try {
     125           3 :         SelectBaseParams(gArgs.GetChainName());
     126           0 :     } catch(const std::exception& e) {
     127           0 :         fprintf(stderr, "Error: %s\n", e.what());
     128           0 :         return EXIT_FAILURE;
     129             :     }
     130           3 :     if (gArgs.GetBoolArg("-rpcssl", false))
     131             :     {
     132           0 :         fprintf(stderr, "Error: SSL mode for RPC (-rpcssl) is no longer supported.\n");
     133           0 :         return EXIT_FAILURE;
     134             :     }
     135             :     return CONTINUE_EXECUTION;
     136             : }
     137             : 
     138             : 
     139             : /** Reply structure for request_done to fill in */
     140           0 : struct HTTPReply
     141             : {
     142           3 :     HTTPReply(): status(0), error(-1) {}
     143             : 
     144             :     int status;
     145             :     int error;
     146             :     std::string body;
     147             : };
     148             : 
     149           0 : const char *http_errorstring(int code)
     150             : {
     151           0 :     switch(code) {
     152             : #if LIBEVENT_VERSION_NUMBER >= 0x02010300
     153             :         case EVREQ_HTTP_TIMEOUT:
     154             :             return "timeout reached";
     155           0 :         case EVREQ_HTTP_EOF:
     156           0 :             return "EOF reached";
     157           0 :         case EVREQ_HTTP_INVALID_HEADER:
     158           0 :             return "error while reading header, or invalid header";
     159           0 :         case EVREQ_HTTP_BUFFER_ERROR:
     160           0 :             return "error encountered while reading or writing";
     161           0 :         case EVREQ_HTTP_REQUEST_CANCEL:
     162           0 :             return "request was canceled";
     163           0 :         case EVREQ_HTTP_DATA_TOO_LONG:
     164           0 :             return "response body is larger than allowed";
     165             : #endif
     166           0 :         default:
     167           0 :             return "unknown";
     168             :     }
     169             : }
     170             : 
     171           3 : static void http_request_done(struct evhttp_request *req, void *ctx)
     172             : {
     173           3 :     HTTPReply *reply = static_cast<HTTPReply*>(ctx);
     174             : 
     175           3 :     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           0 :         reply->status = 0;
     180           0 :         return;
     181             :     }
     182             : 
     183           3 :     reply->status = evhttp_request_get_response_code(req);
     184             : 
     185           3 :     struct evbuffer *buf = evhttp_request_get_input_buffer(req);
     186           3 :     if (buf)
     187             :     {
     188           3 :         size_t size = evbuffer_get_length(buf);
     189           3 :         const char *data = (const char*)evbuffer_pullup(buf, size);
     190           3 :         if (data)
     191           3 :             reply->body = std::string(data, size);
     192           3 :         evbuffer_drain(buf, size);
     193             :     }
     194             : }
     195             : 
     196             : #if LIBEVENT_VERSION_NUMBER >= 0x02010300
     197           0 : static void http_error_cb(enum evhttp_request_error err, void *ctx)
     198             : {
     199           0 :     HTTPReply *reply = static_cast<HTTPReply*>(ctx);
     200           0 :     reply->error = err;
     201           0 : }
     202             : #endif
     203             : 
     204           3 : UniValue CallRPC(const std::string& strMethod, const UniValue& params)
     205             : {
     206           6 :     std::string host = gArgs.GetArg("-rpcconnect", DEFAULT_RPCCONNECT);
     207           3 :     int port = gArgs.GetArg("-rpcport", BaseParams().RPCPort());
     208             : 
     209             :     // Obtain event base
     210           6 :     raii_event_base base = obtain_event_base();
     211             : 
     212             :     // Synchronously look up hostname
     213           9 :     raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port);
     214           3 :     evhttp_connection_set_timeout(evcon.get(), gArgs.GetArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT));
     215             : 
     216           6 :     HTTPReply response;
     217           6 :     raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response);
     218           3 :     if (req == nullptr)
     219           0 :         throw std::runtime_error("create http request failed");
     220             : #if LIBEVENT_VERSION_NUMBER >= 0x02010300
     221           3 :     evhttp_request_set_error_cb(req.get(), http_error_cb);
     222             : #endif
     223             : 
     224             :     // Get credentials
     225           6 :     std::string strRPCUserColonPass;
     226           6 :     if (gArgs.GetArg("-rpcpassword", "") == "") {
     227             :         // Try fall back to cookie-based authentication if no password is provided
     228           3 :         if (!GetAuthCookie(&strRPCUserColonPass)) {
     229           0 :             throw std::runtime_error(strprintf(
     230           0 :                  _("Could not locate RPC credentials. No authentication cookie could be found, and no rpcpassword is set in the configuration file (%s)"),
     231           0 :                     GetConfigFile(gArgs.GetArg("-conf", PIVX_CONF_FILENAME)).string().c_str()));
     232             : 
     233             :         }
     234             :     } else {
     235           0 :         strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
     236             :     }
     237             : 
     238           3 :     struct evkeyvalq* output_headers = evhttp_request_get_output_headers(req.get());
     239           3 :     assert(output_headers);
     240           3 :     evhttp_add_header(output_headers, "Host", host.c_str());
     241           3 :     evhttp_add_header(output_headers, "Connection", "close");
     242           6 :     evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());
     243             : 
     244             :     // Attach request data
     245           9 :     std::string strRequest = JSONRPCRequestObj(strMethod, params, 1).write() + "\n";
     246           3 :     struct evbuffer* output_buffer = evhttp_request_get_output_buffer(req.get());
     247           3 :     assert(output_buffer);
     248           3 :     evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
     249             : 
     250             :     // check if we should use a special wallet endpoint
     251           6 :     std::string endpoint = "/";
     252           3 :     if (!gArgs.GetArgs("-rpcwallet").empty()) {
     253           0 :         std::string walletName = gArgs.GetArg("-rpcwallet", "");
     254           0 :         char* encodedURI = evhttp_uriencode(walletName.c_str(), walletName.size(), false);
     255           0 :         if (encodedURI) {
     256           0 :             endpoint = "/wallet/"+ std::string(encodedURI);
     257           0 :             free(encodedURI);
     258             :         } else {
     259           0 :             throw CConnectionFailed("uri-encode failed");
     260             :         }
     261             :     }
     262           3 :     int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/");
     263           3 :     req.release(); // ownership moved to evcon in above call
     264           3 :     if (r != 0) {
     265           0 :         throw CConnectionFailed("send http request failed");
     266             :     }
     267             : 
     268           3 :     event_base_dispatch(base.get());
     269             : 
     270           3 :     if (response.status == 0)
     271           0 :         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           3 :     else if (response.status == HTTP_UNAUTHORIZED)
     273           0 :         throw std::runtime_error("incorrect rpcuser or rpcpassword (authorization failed)");
     274           3 :     else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR)
     275           0 :         throw std::runtime_error(strprintf("server returned HTTP error %d", response.status));
     276           3 :     else if (response.body.empty())
     277           0 :         throw std::runtime_error("no response from server");
     278             : 
     279             :     // Parse reply
     280           6 :     UniValue valReply(UniValue::VSTR);
     281           3 :     if (!valReply.read(response.body))
     282           0 :         throw std::runtime_error("couldn't parse reply from server");
     283           3 :     const UniValue& reply = valReply.get_obj();
     284           3 :     if (reply.empty())
     285           0 :         throw std::runtime_error("expected reply to have result, error and id properties");
     286             : 
     287           6 :     return reply;
     288             : }
     289             : 
     290           3 : int CommandLineRPC(int argc, char* argv[])
     291             : {
     292           3 :     std::string strPrint;
     293           3 :     int nRet = 0;
     294           6 :     try {
     295             :         // Skip switches
     296           6 :         while (argc > 1 && IsSwitchChar(argv[1][0])) {
     297           3 :             argc--;
     298           3 :             argv++;
     299             :         }
     300             : 
     301             :         // Method
     302           3 :         if (argc < 2)
     303           0 :             throw std::runtime_error("too few parameters");
     304           6 :         std::string strMethod = argv[1];
     305             : 
     306             :         // Parameters default to strings
     307           6 :         std::vector<std::string> strParams(&argv[2], &argv[argc]);
     308           6 :         UniValue params;
     309           3 :         if(gArgs.GetBoolArg("-named", DEFAULT_NAMED)) {
     310           0 :             params = RPCConvertNamedValues(strMethod, strParams);
     311             :         } else {
     312           3 :             params = RPCConvertValues(strMethod, strParams);
     313             :         }
     314             : 
     315             :         // Execute and handle connection failures with -rpcwait
     316           3 :         const bool fWait = gArgs.GetBoolArg("-rpcwait", false);
     317           3 :         do {
     318           3 :             try {
     319           3 :                 const UniValue reply = CallRPC(strMethod, params);
     320             : 
     321             :                 // Parse reply
     322           3 :                 const UniValue& result = find_value(reply, "result");
     323           3 :                 const UniValue& error = find_value(reply, "error");
     324             : 
     325           3 :                 if (!error.isNull()) {
     326             :                     // Error
     327           0 :                     int code = error["code"].get_int();
     328           0 :                     if (fWait && code == RPC_IN_WARMUP)
     329           0 :                         throw CConnectionFailed("server in warmup");
     330           0 :                     strPrint = "error: " + error.write();
     331           0 :                     nRet = abs(code);
     332           0 :                     if (error.isObject()) {
     333           0 :                         UniValue errCode = find_value(error, "code");
     334           0 :                         UniValue errMsg  = find_value(error, "message");
     335           0 :                         strPrint = errCode.isNull() ? "" : "error code: "+errCode.getValStr()+"\n";
     336             : 
     337           0 :                         if (errMsg.isStr())
     338           0 :                             strPrint += "error message:\n"+errMsg.get_str();
     339             : 
     340           0 :                         if (errCode.isNum() && errCode.get_int() == RPC_WALLET_NOT_SPECIFIED) {
     341           0 :                             strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to pivx-cli command line.";
     342             :                         }
     343             :                     }
     344             :                 } else {
     345             :                     // Result
     346           3 :                     if (result.isNull())
     347           0 :                         strPrint = "";
     348           3 :                     else if (result.isStr())
     349           0 :                         strPrint = result.get_str();
     350             :                     else
     351           3 :                         strPrint = result.write(2);
     352             :                 }
     353             :                 // Connection succeeded, no need to retry.
     354           3 :                 break;
     355           0 :             } catch (const CConnectionFailed& e) {
     356           0 :                 if (fWait)
     357           0 :                     MilliSleep(1000);
     358             :                 else
     359           0 :                     throw;
     360             :             }
     361             :         } while (fWait);
     362           0 :     } catch (const boost::thread_interrupted&) {
     363           0 :         throw;
     364           0 :     } catch (const std::exception& e) {
     365           0 :         strPrint = std::string("error: ") + e.what();
     366           0 :         nRet = EXIT_FAILURE;
     367           0 :     } catch (...) {
     368           0 :         PrintExceptionContinue(nullptr, "CommandLineRPC()");
     369           0 :         throw;
     370             :     }
     371             : 
     372           3 :     if (strPrint != "") {
     373           3 :         fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str());
     374             :     }
     375           6 :     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           3 : int main(int argc, char* argv[])
     390             : {
     391             : #endif
     392           3 :     SetupEnvironment();
     393           3 :     if (!SetupNetworking()) {
     394           0 :         fprintf(stderr, "Error: Initializing networking failed\n");
     395           0 :         return EXIT_FAILURE;
     396             :     }
     397           3 :     event_set_log_callback(&libevent_log_cb);
     398             : 
     399           3 :     try {
     400           3 :         int ret = AppInitRPC(argc, argv);
     401           3 :         if (ret != CONTINUE_EXECUTION)
     402             :             return ret;
     403           0 :     } catch (const std::exception& e) {
     404           0 :         PrintExceptionContinue(&e, "AppInitRPC()");
     405           0 :         return EXIT_FAILURE;
     406           0 :     } catch (...) {
     407           0 :         PrintExceptionContinue(nullptr, "AppInitRPC()");
     408           0 :         return EXIT_FAILURE;
     409             :     }
     410             : 
     411           3 :     int ret = EXIT_FAILURE;
     412           3 :     try {
     413           3 :         ret = CommandLineRPC(argc, argv);
     414           0 :     } catch (const std::exception& e) {
     415           0 :         PrintExceptionContinue(&e, "CommandLineRPC()");
     416           0 :     } catch (...) {
     417           0 :         PrintExceptionContinue(nullptr, "CommandLineRPC()");
     418             :     }
     419             :     return ret;
     420             : }

Generated by: LCOV version 1.14