48d92f116e
* Add libbacktrace to depends This is currently only useful to extract symbols. It fails to gather stacktraces when compiled with MinGW, so we can only use it to get symbol information from a stack trace which we gathered outside of libbacktrace. * Add -mbig-obj to CXXFLAGS for MinGW builds * Implement stacktraces for C++ exceptions This is a hack and should only be used for debugging. It works by wrapping the C++ ABI __wrap___cxa_allocate_exception. The wrapper records a backtrace and stores it in a global map. Later the stacktrace can be retrieved with GetExceptionStacktraceStr. This commit also adds handlers to pretty print uncaught exceptions and signals. * Use GetPrettyExceptionStr for all unhandled exceptions * Use --enable-stacktraces in CI for linux32/linux64 * Register exception translators to pretty print exceptions in unit tests * Catch and print python exceptions when stopping nodes Otherwise the code at the bottom is never executed when nodes crash, leading to no output of debug.log files on Travis. * Remove now unneeded/unused TestCrash methods
392 lines
14 KiB
C++
392 lines
14 KiB
C++
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2015 The Bitcoin Core developers
|
|
// Copyright (c) 2014-2017 The Dash Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#if defined(HAVE_CONFIG_H)
|
|
#include "config/dash-config.h"
|
|
#endif
|
|
|
|
#include "chainparamsbase.h"
|
|
#include "clientversion.h"
|
|
#include "rpc/client.h"
|
|
#include "rpc/protocol.h"
|
|
#include "stacktraces.h"
|
|
#include "util.h"
|
|
#include "utilstrencodings.h"
|
|
|
|
#include <boost/filesystem/operations.hpp>
|
|
#include <stdio.h>
|
|
|
|
#include <event2/buffer.h>
|
|
#include <event2/keyvalq_struct.h>
|
|
#include "support/events.h"
|
|
|
|
#include <univalue.h>
|
|
|
|
static const char DEFAULT_RPCCONNECT[] = "127.0.0.1";
|
|
static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900;
|
|
static const bool DEFAULT_NAMED=false;
|
|
static const int CONTINUE_EXECUTION=-1;
|
|
|
|
std::string HelpMessageCli()
|
|
{
|
|
std::string strUsage;
|
|
strUsage += HelpMessageGroup(_("Options:"));
|
|
strUsage += HelpMessageOpt("-?", _("This help message"));
|
|
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), BITCOIN_CONF_FILENAME));
|
|
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory"));
|
|
AppendParamsHelpMessages(strUsage);
|
|
strUsage += HelpMessageOpt("-named", strprintf(_("Pass named instead of positional arguments (default: %s)"), DEFAULT_NAMED));
|
|
strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf(_("Send commands to node running on <ip> (default: %s)"), DEFAULT_RPCCONNECT));
|
|
strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Connect to JSON-RPC on <port> (default: %u or testnet: %u)"), BaseParams(CBaseChainParams::MAIN).RPCPort(), BaseParams(CBaseChainParams::TESTNET).RPCPort()));
|
|
strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start"));
|
|
strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections"));
|
|
strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections"));
|
|
strUsage += HelpMessageOpt("-rpcclienttimeout=<n>", strprintf(_("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)"), DEFAULT_HTTP_CLIENT_TIMEOUT));
|
|
strUsage += HelpMessageOpt("-stdin", _("Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases)"));
|
|
|
|
return strUsage;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Start
|
|
//
|
|
|
|
//
|
|
// Exception thrown on connection error. This error is used to determine
|
|
// when to wait if -rpcwait is given.
|
|
//
|
|
class CConnectionFailed : public std::runtime_error
|
|
{
|
|
public:
|
|
|
|
explicit inline CConnectionFailed(const std::string& msg) :
|
|
std::runtime_error(msg)
|
|
{}
|
|
|
|
};
|
|
|
|
//
|
|
// This function returns either one of EXIT_ codes when it's expected to stop the process or
|
|
// CONTINUE_EXECUTION when it's expected to continue further.
|
|
//
|
|
static int AppInitRPC(int argc, char* argv[])
|
|
{
|
|
//
|
|
// Parameters
|
|
//
|
|
ParseParameters(argc, argv);
|
|
if (argc<2 || IsArgSet("-?") || IsArgSet("-h") || IsArgSet("-help") || IsArgSet("-version")) {
|
|
std::string strUsage = strprintf(_("%s RPC client version"), _(PACKAGE_NAME)) + " " + FormatFullVersion() + "\n";
|
|
if (!IsArgSet("-version")) {
|
|
strUsage += "\n" + _("Usage:") + "\n" +
|
|
" dash-cli [options] <command> [params] " + strprintf(_("Send command to %s"), _(PACKAGE_NAME)) + "\n" +
|
|
" dash-cli [options] -named <command> [name=value] ... " + strprintf(_("Send command to %s (with named arguments)"), _(PACKAGE_NAME)) + "\n" +
|
|
" dash-cli [options] help " + _("List commands") + "\n" +
|
|
" dash-cli [options] help <command> " + _("Get help for a command") + "\n";
|
|
|
|
strUsage += "\n" + HelpMessageCli();
|
|
}
|
|
|
|
fprintf(stdout, "%s", strUsage.c_str());
|
|
if (argc < 2) {
|
|
fprintf(stderr, "Error: too few parameters\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
return EXIT_SUCCESS;
|
|
}
|
|
bool datadirFromCmdLine = IsArgSet("-datadir");
|
|
if (datadirFromCmdLine && !boost::filesystem::is_directory(GetDataDir(false))) {
|
|
fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", GetArg("-datadir", "").c_str());
|
|
return EXIT_FAILURE;
|
|
}
|
|
try {
|
|
ReadConfigFile(GetArg("-conf", BITCOIN_CONF_FILENAME));
|
|
} catch (const std::exception& e) {
|
|
fprintf(stderr,"Error reading configuration file: %s\n", e.what());
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (!datadirFromCmdLine && !boost::filesystem::is_directory(GetDataDir(false))) {
|
|
fprintf(stderr, "Error: Specified data directory \"%s\" from config file does not exist.\n", GetArg("-datadir", "").c_str());
|
|
return EXIT_FAILURE;
|
|
}
|
|
// Check for -testnet or -regtest parameter (BaseParams() calls are only valid after this clause)
|
|
try {
|
|
SelectBaseParams(ChainNameFromCommandLine());
|
|
} catch (const std::exception& e) {
|
|
fprintf(stderr, "Error: %s\n", e.what());
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (GetBoolArg("-rpcssl", false))
|
|
{
|
|
fprintf(stderr, "Error: SSL mode for RPC (-rpcssl) is no longer supported.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
return CONTINUE_EXECUTION;
|
|
}
|
|
|
|
|
|
/** Reply structure for request_done to fill in */
|
|
struct HTTPReply
|
|
{
|
|
HTTPReply(): status(0), error(-1) {}
|
|
|
|
int status;
|
|
int error;
|
|
std::string body;
|
|
};
|
|
|
|
const char *http_errorstring(int code)
|
|
{
|
|
switch(code) {
|
|
#if LIBEVENT_VERSION_NUMBER >= 0x02010300
|
|
case EVREQ_HTTP_TIMEOUT:
|
|
return "timeout reached";
|
|
case EVREQ_HTTP_EOF:
|
|
return "EOF reached";
|
|
case EVREQ_HTTP_INVALID_HEADER:
|
|
return "error while reading header, or invalid header";
|
|
case EVREQ_HTTP_BUFFER_ERROR:
|
|
return "error encountered while reading or writing";
|
|
case EVREQ_HTTP_REQUEST_CANCEL:
|
|
return "request was canceled";
|
|
case EVREQ_HTTP_DATA_TOO_LONG:
|
|
return "response body is larger than allowed";
|
|
#endif
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static void http_request_done(struct evhttp_request *req, void *ctx)
|
|
{
|
|
HTTPReply *reply = static_cast<HTTPReply*>(ctx);
|
|
|
|
if (req == NULL) {
|
|
/* If req is NULL, it means an error occurred while connecting: the
|
|
* error code will have been passed to http_error_cb.
|
|
*/
|
|
reply->status = 0;
|
|
return;
|
|
}
|
|
|
|
reply->status = evhttp_request_get_response_code(req);
|
|
|
|
struct evbuffer *buf = evhttp_request_get_input_buffer(req);
|
|
if (buf)
|
|
{
|
|
size_t size = evbuffer_get_length(buf);
|
|
const char *data = (const char*)evbuffer_pullup(buf, size);
|
|
if (data)
|
|
reply->body = std::string(data, size);
|
|
evbuffer_drain(buf, size);
|
|
}
|
|
}
|
|
|
|
#if LIBEVENT_VERSION_NUMBER >= 0x02010300
|
|
static void http_error_cb(enum evhttp_request_error err, void *ctx)
|
|
{
|
|
HTTPReply *reply = static_cast<HTTPReply*>(ctx);
|
|
reply->error = err;
|
|
}
|
|
#endif
|
|
|
|
UniValue CallRPC(const std::string& strMethod, const UniValue& params)
|
|
{
|
|
std::string host = GetArg("-rpcconnect", DEFAULT_RPCCONNECT);
|
|
int port = GetArg("-rpcport", BaseParams().RPCPort());
|
|
|
|
// Obtain event base
|
|
raii_event_base base = obtain_event_base();
|
|
|
|
// Synchronously look up hostname
|
|
raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port);
|
|
evhttp_connection_set_timeout(evcon.get(), GetArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT));
|
|
|
|
HTTPReply response;
|
|
raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response);
|
|
if (req == NULL)
|
|
throw std::runtime_error("create http request failed");
|
|
#if LIBEVENT_VERSION_NUMBER >= 0x02010300
|
|
evhttp_request_set_error_cb(req.get(), http_error_cb);
|
|
#endif
|
|
|
|
// Get credentials
|
|
std::string strRPCUserColonPass;
|
|
if (GetArg("-rpcpassword", "") == "") {
|
|
// Try fall back to cookie-based authentication if no password is provided
|
|
if (!GetAuthCookie(&strRPCUserColonPass)) {
|
|
throw std::runtime_error(strprintf(
|
|
_("Could not locate RPC credentials. No authentication cookie could be found, and no rpcpassword is set in the configuration file (%s)"),
|
|
GetConfigFile(GetArg("-conf", BITCOIN_CONF_FILENAME)).string().c_str()));
|
|
|
|
}
|
|
} else {
|
|
strRPCUserColonPass = GetArg("-rpcuser", "") + ":" + GetArg("-rpcpassword", "");
|
|
}
|
|
|
|
struct evkeyvalq* output_headers = evhttp_request_get_output_headers(req.get());
|
|
assert(output_headers);
|
|
evhttp_add_header(output_headers, "Host", host.c_str());
|
|
evhttp_add_header(output_headers, "Connection", "close");
|
|
evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());
|
|
|
|
// Attach request data
|
|
std::string strRequest = JSONRPCRequestObj(strMethod, params, 1).write() + "\n";
|
|
struct evbuffer* output_buffer = evhttp_request_get_output_buffer(req.get());
|
|
assert(output_buffer);
|
|
evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
|
|
|
|
int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/");
|
|
req.release(); // ownership moved to evcon in above call
|
|
if (r != 0) {
|
|
throw CConnectionFailed("send http request failed");
|
|
}
|
|
|
|
event_base_dispatch(base.get());
|
|
|
|
if (response.status == 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));
|
|
else if (response.status == HTTP_UNAUTHORIZED)
|
|
throw std::runtime_error("incorrect rpcuser or rpcpassword (authorization failed)");
|
|
else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR)
|
|
throw std::runtime_error(strprintf("server returned HTTP error %d", response.status));
|
|
else if (response.body.empty())
|
|
throw std::runtime_error("no response from server");
|
|
|
|
// Parse reply
|
|
UniValue valReply(UniValue::VSTR);
|
|
if (!valReply.read(response.body))
|
|
throw std::runtime_error("couldn't parse reply from server");
|
|
const UniValue& reply = valReply.get_obj();
|
|
if (reply.empty())
|
|
throw std::runtime_error("expected reply to have result, error and id properties");
|
|
|
|
return reply;
|
|
}
|
|
|
|
int CommandLineRPC(int argc, char *argv[])
|
|
{
|
|
std::string strPrint;
|
|
int nRet = 0;
|
|
try {
|
|
// Skip switches
|
|
while (argc > 1 && IsSwitchChar(argv[1][0])) {
|
|
argc--;
|
|
argv++;
|
|
}
|
|
std::vector<std::string> args = std::vector<std::string>(&argv[1], &argv[argc]);
|
|
if (GetBoolArg("-stdin", false)) {
|
|
// Read one arg per line from stdin and append
|
|
std::string line;
|
|
while (std::getline(std::cin,line))
|
|
args.push_back(line);
|
|
}
|
|
if (args.size() < 1)
|
|
throw std::runtime_error("too few parameters (need at least command)");
|
|
std::string strMethod = args[0];
|
|
args.erase(args.begin()); // Remove trailing method name from arguments vector
|
|
|
|
UniValue params;
|
|
if(GetBoolArg("-named", DEFAULT_NAMED)) {
|
|
params = RPCConvertNamedValues(strMethod, args);
|
|
} else {
|
|
params = RPCConvertValues(strMethod, args);
|
|
}
|
|
|
|
// Execute and handle connection failures with -rpcwait
|
|
const bool fWait = GetBoolArg("-rpcwait", false);
|
|
do {
|
|
try {
|
|
const UniValue reply = CallRPC(strMethod, params);
|
|
|
|
// Parse reply
|
|
const UniValue& result = find_value(reply, "result");
|
|
const UniValue& error = find_value(reply, "error");
|
|
|
|
if (!error.isNull()) {
|
|
// Error
|
|
int code = error["code"].get_int();
|
|
if (fWait && code == RPC_IN_WARMUP)
|
|
throw CConnectionFailed("server in warmup");
|
|
strPrint = "error: " + error.write();
|
|
nRet = abs(code);
|
|
if (error.isObject())
|
|
{
|
|
UniValue errCode = find_value(error, "code");
|
|
UniValue errMsg = find_value(error, "message");
|
|
strPrint = errCode.isNull() ? "" : "error code: "+errCode.getValStr()+"\n";
|
|
|
|
if (errMsg.isStr())
|
|
strPrint += "error message:\n"+errMsg.get_str();
|
|
}
|
|
} else {
|
|
// Result
|
|
if (result.isNull())
|
|
strPrint = "";
|
|
else if (result.isStr())
|
|
strPrint = result.get_str();
|
|
else
|
|
strPrint = result.write(2);
|
|
}
|
|
// Connection succeeded, no need to retry.
|
|
break;
|
|
}
|
|
catch (const CConnectionFailed&) {
|
|
if (fWait)
|
|
MilliSleep(1000);
|
|
else
|
|
throw;
|
|
}
|
|
} while (fWait);
|
|
}
|
|
catch (const boost::thread_interrupted&) {
|
|
throw;
|
|
}
|
|
catch (const std::exception& e) {
|
|
strPrint = std::string("error: ") + e.what();
|
|
nRet = EXIT_FAILURE;
|
|
}
|
|
catch (...) {
|
|
PrintExceptionContinue(std::current_exception(), "CommandLineRPC()");
|
|
throw;
|
|
}
|
|
|
|
if (strPrint != "") {
|
|
fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str());
|
|
}
|
|
return nRet;
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
RegisterPrettyTerminateHander();
|
|
RegisterPrettySignalHandlers();
|
|
|
|
SetupEnvironment();
|
|
if (!SetupNetworking()) {
|
|
fprintf(stderr, "Error: Initializing networking failed\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
try {
|
|
int ret = AppInitRPC(argc, argv);
|
|
if (ret != CONTINUE_EXECUTION)
|
|
return ret;
|
|
} catch (...) {
|
|
PrintExceptionContinue(std::current_exception(), "AppInitRPC()");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
int ret = EXIT_FAILURE;
|
|
try {
|
|
ret = CommandLineRPC(argc, argv);
|
|
} catch (...) {
|
|
PrintExceptionContinue(std::current_exception(), "CommandLineRPC()");
|
|
}
|
|
return ret;
|
|
}
|