diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 1f949973cf..796c93d49d 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -1,636 +1,641 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include +#include const std::function G_TRANSLATION_FUN = nullptr; 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; static void SetupCliArgs() { const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN); const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET); gArgs.AddArg("-?", _("This help message"), false, OptionsCategory::OPTIONS); gArgs.AddArg("-version", "Print version and exit", false, OptionsCategory::OPTIONS); gArgs.AddArg("-conf=", strprintf(_("Specify configuration file. Relative paths will " "be prefixed by datadir location. (default: %s)"), BITCOIN_CONF_FILENAME), false, OptionsCategory::OPTIONS); gArgs.AddArg("-datadir=", _("Specify data directory"), false, OptionsCategory::OPTIONS); gArgs.AddArg( "-getinfo", _("Get general information from the remote server. Note that unlike " "server-side RPC calls, the results of -getinfo is the result of " "multiple non-atomic requests. Some entries in the result may " "represent results from different states (e.g. wallet balance may be " "as of a different block from the chain state reported)"), false, OptionsCategory::OPTIONS); SetupChainParamsBaseOptions(); gArgs.AddArg( "-named", strprintf(_("Pass named instead of positional arguments (default: %s)"), DEFAULT_NAMED), false, OptionsCategory::OPTIONS); gArgs.AddArg( "-rpcconnect=", strprintf(_("Send commands to node running on (default: %s)"), DEFAULT_RPCCONNECT), false, OptionsCategory::OPTIONS); gArgs.AddArg( "-rpccookiefile=", _("Location of the auth cookie. Relative paths will be prefixed by a " "net-specific datadir location. (default: data dir)"), false, OptionsCategory::OPTIONS); gArgs.AddArg( "-rpcport=", strprintf( _("Connect to JSON-RPC on (default: %u or testnet: %u)"), defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort()), false, OptionsCategory::OPTIONS); gArgs.AddArg("-rpcwait", _("Wait for RPC server to start"), false, OptionsCategory::OPTIONS); gArgs.AddArg("-rpcuser=", _("Username for JSON-RPC connections"), false, OptionsCategory::OPTIONS); gArgs.AddArg("-rpcpassword=", _("Password for JSON-RPC connections"), false, OptionsCategory::OPTIONS); gArgs.AddArg("-rpcclienttimeout=", strprintf(_("Timeout in seconds during HTTP requests, or 0 " "for no timeout. (default: %d)"), DEFAULT_HTTP_CLIENT_TIMEOUT), false, OptionsCategory::OPTIONS); gArgs.AddArg( "-stdinrpcpass", strprintf(_("Read RPC password from standard input as a single line. " "When combined with -stdin, the first line from standard " "input is used for the RPC password.")), false, OptionsCategory::OPTIONS); gArgs.AddArg("-stdin", _("Read extra arguments from standard input, one per line " "until EOF/Ctrl-D (recommended for sensitive information " "such as passphrases)"), false, OptionsCategory::OPTIONS); gArgs.AddArg( "-rpcwallet=", _("Send RPC for non-default wallet on RPC server (needs to exactly " "match corresponding -wallet option passed to bitcoind)"), false, OptionsCategory::OPTIONS); // Hidden gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN); gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN); } ////////////////////////////////////////////////////////////////////////////// // // 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 // SetupCliArgs(); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); return EXIT_FAILURE; } if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { std::string strUsage = PACKAGE_NAME " RPC client version " + FormatFullVersion() + "\n"; if (!gArgs.IsArgSet("-version")) { strUsage += "\n" "Usage: bitcoin-cli [options] [params] " "Send command to " PACKAGE_NAME "\n" "or: bitcoin-cli [options] -named " "[name=value]... Send command to " PACKAGE_NAME " (with named arguments)\n" "or: bitcoin-cli [options] help " "List commands\n" "or: bitcoin-cli [options] help Get " "help for a command\n"; strUsage += "\n" + gArgs.GetHelpMessage(); } fprintf(stdout, "%s", strUsage.c_str()); if (argc < 2) { fprintf(stderr, "Error: too few parameters\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } if (!fs::is_directory(GetDataDir(false))) { fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); return EXIT_FAILURE; } if (!gArgs.ReadConfigFiles(error, true)) { fprintf(stderr, "Error reading configuration file: %s\n", error.c_str()); return EXIT_FAILURE; } // Check for -testnet or -regtest parameter (BaseParams() calls are only // valid after this clause) try { SelectBaseParams(gArgs.GetChainName()); } catch (const std::exception &e) { fprintf(stderr, "Error: %s\n", e.what()); return EXIT_FAILURE; } if (gArgs.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; }; static 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(ctx); if (req == nullptr) { /** * If req is nullptr, 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(ctx); reply->error = err; } #endif /** * Class that handles the conversion from a command-line to a JSON-RPC request, * as well as converting back to a JSON object that can be shown as result. */ class BaseRequestHandler { public: virtual ~BaseRequestHandler() {} virtual UniValue PrepareRequest(const std::string &method, const std::vector &args) = 0; virtual UniValue ProcessReply(const UniValue &batch_in) = 0; }; /** Process getinfo requests */ class GetinfoRequestHandler : public BaseRequestHandler { public: const int ID_NETWORKINFO = 0; const int ID_BLOCKCHAININFO = 1; const int ID_WALLETINFO = 2; /** Create a simulated `getinfo` request. */ UniValue PrepareRequest(const std::string &method, const std::vector &args) override { if (!args.empty()) { throw std::runtime_error("-getinfo takes no arguments"); } UniValue result(UniValue::VARR); result.push_back( JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO)); result.push_back(JSONRPCRequestObj("getblockchaininfo", NullUniValue, ID_BLOCKCHAININFO)); result.push_back( JSONRPCRequestObj("getwalletinfo", NullUniValue, ID_WALLETINFO)); return result; } /** Collect values from the batch and form a simulated `getinfo` reply. */ UniValue ProcessReply(const UniValue &batch_in) override { UniValue result(UniValue::VOBJ); std::vector batch = JSONRPCProcessBatchReply(batch_in, 3); // Errors in getnetworkinfo() and getblockchaininfo() are fatal, pass // them on getwalletinfo() is allowed to fail in case there is no // wallet. if (!batch[ID_NETWORKINFO]["error"].isNull()) { return batch[ID_NETWORKINFO]; } if (!batch[ID_BLOCKCHAININFO]["error"].isNull()) { return batch[ID_BLOCKCHAININFO]; } result.pushKV("version", batch[ID_NETWORKINFO]["result"]["version"]); result.pushKV("protocolversion", batch[ID_NETWORKINFO]["result"]["protocolversion"]); if (!batch[ID_WALLETINFO].isNull()) { result.pushKV("walletversion", batch[ID_WALLETINFO]["result"]["walletversion"]); result.pushKV("balance", batch[ID_WALLETINFO]["result"]["balance"]); } result.pushKV("blocks", batch[ID_BLOCKCHAININFO]["result"]["blocks"]); result.pushKV("timeoffset", batch[ID_NETWORKINFO]["result"]["timeoffset"]); result.pushKV("connections", batch[ID_NETWORKINFO]["result"]["connections"]); result.pushKV("proxy", batch[ID_NETWORKINFO]["result"]["networks"][0]["proxy"]); result.pushKV("difficulty", batch[ID_BLOCKCHAININFO]["result"]["difficulty"]); result.pushKV( "testnet", UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"].get_str() == "test")); if (!batch[ID_WALLETINFO].isNull()) { result.pushKV("walletversion", batch[ID_WALLETINFO]["result"]["walletversion"]); result.pushKV("balance", batch[ID_WALLETINFO]["result"]["balance"]); result.pushKV("keypoololdest", batch[ID_WALLETINFO]["result"]["keypoololdest"]); result.pushKV("keypoolsize", batch[ID_WALLETINFO]["result"]["keypoolsize"]); if (!batch[ID_WALLETINFO]["result"]["unlocked_until"].isNull()) { result.pushKV("unlocked_until", batch[ID_WALLETINFO]["result"]["unlocked_until"]); } result.pushKV("paytxfee", batch[ID_WALLETINFO]["result"]["paytxfee"]); } result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]); result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]); return JSONRPCReplyObj(result, NullUniValue, 1); } }; /** Process default single requests */ class DefaultRequestHandler : public BaseRequestHandler { public: UniValue PrepareRequest(const std::string &method, const std::vector &args) override { UniValue params; if (gArgs.GetBoolArg("-named", DEFAULT_NAMED)) { params = RPCConvertNamedValues(method, args); } else { params = RPCConvertValues(method, args); } return JSONRPCRequestObj(method, params, 1); } UniValue ProcessReply(const UniValue &reply) override { return reply.get_obj(); } }; static UniValue CallRPC(BaseRequestHandler *rh, const std::string &strMethod, const std::vector &args) { std::string host; // In preference order, we choose the following for the port: // 1. -rpcport // 2. port in -rpcconnect (ie following : in ipv4 or ]: in ipv6) // 3. default port for chain int port = BaseParams().RPCPort(); SplitHostPort(gArgs.GetArg("-rpcconnect", DEFAULT_RPCCONNECT), port, host); port = gArgs.GetArg("-rpcport", port); // 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(), gArgs.GetArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT)); HTTPReply response; raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void *)&response); if (req == nullptr) 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; bool failedToGetAuthCookie = false; if (gArgs.GetArg("-rpcpassword", "") == "") { // Try fall back to cookie-based authentication if no password is // provided if (!GetAuthCookie(&strRPCUserColonPass)) { failedToGetAuthCookie = true; } } else { strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.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 = rh->PrepareRequest(strMethod, args).write() + "\n"; struct evbuffer *output_buffer = evhttp_request_get_output_buffer(req.get()); assert(output_buffer); evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); // check if we should use a special wallet endpoint std::string endpoint = "/"; if (!gArgs.GetArgs("-rpcwallet").empty()) { std::string walletName = gArgs.GetArg("-rpcwallet", ""); char *encodedURI = evhttp_uriencode(walletName.c_str(), walletName.size(), false); if (encodedURI) { endpoint = "/wallet/" + std::string(encodedURI); free(encodedURI); } else { throw CConnectionFailed("uri-encode failed"); } } int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, endpoint.c_str()); // ownership moved to evcon in above call req.release(); if (r != 0) { throw CConnectionFailed("send http request failed"); } event_base_dispatch(base.get()); if (response.status == 0) { std::string responseErrorMessage; if (response.error != -1) { responseErrorMessage = strprintf(" (error code %d - \"%s\")", response.error, http_errorstring(response.error)); } throw CConnectionFailed( strprintf("Could not connect to the server %s:%d%s\n\nMake sure " "the bitcoind server is running and that you are " "connecting to the correct RPC port.", host, port, responseErrorMessage)); } else if (response.status == HTTP_UNAUTHORIZED) { if (failedToGetAuthCookie) { throw std::runtime_error(strprintf( _("Could not locate RPC credentials. No authentication cookie " "could be found, and RPC password is not set. See " "-rpcpassword and -stdinrpcpass. Configuration file: (%s)"), GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)) .string() .c_str())); } else { throw std::runtime_error( "Authorization failed: Incorrect rpcuser or rpcpassword"); } } 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 = rh->ProcessReply(valReply); if (reply.empty()) { throw std::runtime_error( "expected reply to have result, error and id properties"); } return reply; } static 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::string rpcPass; if (gArgs.GetBoolArg("-stdinrpcpass", false)) { if (!std::getline(std::cin, rpcPass)) throw std::runtime_error("-stdinrpcpass specified but failed " "to read from standard input"); gArgs.ForceSetArg("-rpcpassword", rpcPass); } std::vector args = std::vector(&argv[1], &argv[argc]); if (gArgs.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); } } std::unique_ptr rh; std::string method; if (gArgs.GetBoolArg("-getinfo", false)) { rh.reset(new GetinfoRequestHandler()); method = ""; } else { rh.reset(new DefaultRequestHandler()); if (args.size() < 1) { throw std::runtime_error( "too few parameters (need at least command)"); } method = args[0]; // Remove trailing method name from arguments vector args.erase(args.begin()); } // Execute and handle connection failures with -rpcwait const bool fWait = gArgs.GetBoolArg("-rpcwait", false); do { try { const UniValue reply = CallRPC(rh.get(), method, args); // 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(); } if (errCode.isNum() && errCode.get_int() == RPC_WALLET_NOT_SPECIFIED) { strPrint += "\nTry adding " "\"-rpcwallet=\" option to " "bitcoin-cli command line."; } } } 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(nullptr, "CommandLineRPC()"); throw; } if (strPrint != "") { fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); } return nRet; } int main(int argc, char *argv[]) { +#ifdef WIN32 + util::WinCmdLineArgs winArgs; + std::tie(argc, argv) = winArgs.get(); +#endif 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 (const std::exception &e) { PrintExceptionContinue(&e, "AppInitRPC()"); return EXIT_FAILURE; } catch (...) { PrintExceptionContinue(nullptr, "AppInitRPC()"); return EXIT_FAILURE; } int ret = EXIT_FAILURE; try { ret = CommandLineRPC(argc, argv); } catch (const std::exception &e) { PrintExceptionContinue(&e, "CommandLineRPC()"); } catch (...) { PrintExceptionContinue(nullptr, "CommandLineRPC()"); } return ret; } diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index b1d288592f..9bdd31f129 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -1,225 +1,229 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include const std::function G_TRANSLATION_FUN = nullptr; /* Introduction text for doxygen: */ /*! \mainpage Developer documentation * * \section intro_sec Introduction * * This is the developer documentation of Bitcoin ABC * (https://www.bitcoinabc.org/). Bitcoin ABC is a client for the digital * currency called Bitcoin Cash (https://www.bitcoincash.org/), which enables * instant payments to anyone, anywhere in the world. Bitcoin Cash uses * peer-to-peer technology to operate with no central authority: managing * transactions and issuing money are carried out collectively by the network. * * The software is a community-driven open source project, released under the * MIT license. * * \section Navigation * Use the buttons Namespaces, Classes or * Files at the top of the page to start navigating the code. */ static void WaitForShutdown() { while (!ShutdownRequested()) { MilliSleep(200); } Interrupt(); } ////////////////////////////////////////////////////////////////////////////// // // Start // static bool AppInit(int argc, char *argv[]) { // FIXME: Ideally, we'd like to build the config here, but that's currently // not possible as the whole application has too many global state. However, // this is a first step. auto &config = const_cast(GetConfig()); RPCServer rpcServer; HTTPRPCRequestProcessor httpRPCRequestProcessor(config, rpcServer); bool fRet = false; // // Parameters // // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's // main() SetupServerArgs(); #if HAVE_DECL_DAEMON gArgs.AddArg("-daemon", _("Run in the background as a daemon and accept commands"), false, OptionsCategory::OPTIONS); #endif std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); return false; } // Process help and version before taking care about datadir if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { std::string strUsage = PACKAGE_NAME " Daemon version " + FormatFullVersion() + "\n"; if (gArgs.IsArgSet("-version")) { strUsage += FormatParagraph(LicenseInfo()); } else { strUsage += "\nUsage: bitcoind [options] " "Start " PACKAGE_NAME " Daemon\n"; strUsage += "\n" + gArgs.GetHelpMessage(); } fprintf(stdout, "%s", strUsage.c_str()); return true; } try { if (!fs::is_directory(GetDataDir(false))) { fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); return false; } if (!gArgs.ReadConfigFiles(error)) { fprintf(stderr, "Error reading configuration file: %s\n", error.c_str()); return false; } // Check for -testnet or -regtest parameter (Params() calls are only // valid after this clause) try { SelectParams(gArgs.GetChainName()); } catch (const std::exception &e) { fprintf(stderr, "Error: %s\n", e.what()); return false; } // Make sure we create the net-specific data directory early on: if it // is new, this has a side effect of also creating // //wallets/. // // TODO: this should be removed once GetDataDir() no longer creates the // wallets/ subdirectory. // See more info at: // https://reviews.bitcoinabc.org/D3312 GetDataDir(true); // Error out when loose non-argument tokens are encountered on command // line for (int i = 1; i < argc; i++) { if (!IsSwitchChar(argv[i][0])) { fprintf(stderr, "Error: Command line contains unexpected token '%s', " "see bitcoind -h for a list of options.\n", argv[i]); return false; } } // -server defaults to true for bitcoind but not for the GUI so do this // here gArgs.SoftSetBoolArg("-server", true); // Set this early so that parameter interactions go to console InitLogging(); InitParameterInteraction(); if (!AppInitBasicSetup()) { // InitError will have been called with detailed error, which ends // up on console return false; } if (!AppInitParameterInteraction(config)) { // InitError will have been called with detailed error, which ends // up on console return false; } if (!AppInitSanityChecks()) { // InitError will have been called with detailed error, which ends // up on console return false; } if (gArgs.GetBoolArg("-daemon", false)) { #if HAVE_DECL_DAEMON #if defined(MAC_OSX) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif fprintf(stdout, "Bitcoin server starting\n"); // Daemonize if (daemon(1, 0)) { // don't chdir (1), do close FDs (0) fprintf(stderr, "Error: daemon() failed: %s\n", strerror(errno)); return false; } #if defined(MAC_OSX) #pragma GCC diagnostic pop #endif #else fprintf( stderr, "Error: -daemon is not supported on this operating system\n"); return false; #endif // HAVE_DECL_DAEMON } // Lock data directory after daemonization if (!AppInitLockDataDirectory()) { // If locking the data directory failed, exit immediately return false; } fRet = AppInitMain(config, rpcServer, httpRPCRequestProcessor); } catch (const std::exception &e) { PrintExceptionContinue(&e, "AppInit()"); } catch (...) { PrintExceptionContinue(nullptr, "AppInit()"); } if (!fRet) { Interrupt(); } else { WaitForShutdown(); } Shutdown(); return fRet; } int main(int argc, char *argv[]) { +#ifdef WIN32 + util::WinCmdLineArgs winArgs; + std::tie(argc, argv) = winArgs.get(); +#endif SetupEnvironment(); // Connect bitcoind signal handlers noui_connect(); return (AppInit(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE); } diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index c1b3c65381..fc02e49ef6 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -1,857 +1,861 @@ // Copyright (c) 2011-2016 The Bitcoin 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_WALLET #include #include #endif #include #include #include #include #include #include #include #include #include #include #if defined(QT_STATICPLUGIN) #include #if QT_VERSION < 0x050400 Q_IMPORT_PLUGIN(AccessibleFactory) #endif #if defined(QT_QPA_PLATFORM_XCB) Q_IMPORT_PLUGIN(QXcbIntegrationPlugin); #elif defined(QT_QPA_PLATFORM_WINDOWS) Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #elif defined(QT_QPA_PLATFORM_COCOA) Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); #endif #endif #include // Declare meta types used for QMetaObject::invokeMethod Q_DECLARE_METATYPE(bool *) Q_DECLARE_METATYPE(Amount) Q_DECLARE_METATYPE(uint256) // Config is non-copyable so we can only register pointers to it Q_DECLARE_METATYPE(Config *) static void InitMessage(const std::string &message) { LogPrintf("init message: %s\n", message); } /** * Translate string to current locale using Qt. */ const std::function G_TRANSLATION_FUN = [](const char *psz) { return QCoreApplication::translate("bitcoin-abc", psz).toStdString(); }; static QString GetLangTerritory() { QSettings settings; // Get desired locale (e.g. "de_DE") // 1) System default language QString lang_territory = QLocale::system().name(); // 2) Language from QSettings QString lang_territory_qsettings = settings.value("language", "").toString(); if (!lang_territory_qsettings.isEmpty()) { lang_territory = lang_territory_qsettings; } // 3) -lang command line argument lang_territory = QString::fromStdString( gArgs.GetArg("-lang", lang_territory.toStdString())); return lang_territory; } /** Set up translations */ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTranslator, QTranslator &translatorBase, QTranslator &translator) { // Remove old translators QApplication::removeTranslator(&qtTranslatorBase); QApplication::removeTranslator(&qtTranslator); QApplication::removeTranslator(&translatorBase); QApplication::removeTranslator(&translator); // Get desired locale (e.g. "de_DE") // 1) System default language QString lang_territory = GetLangTerritory(); // Convert to "de" only by truncating "_DE" QString lang = lang_territory; lang.truncate(lang_territory.lastIndexOf('_')); // Load language files for configured locale: // - First load the translator for the base language, without territory // - Then load the more specific locale translator // Load e.g. qt_de.qm if (qtTranslatorBase.load( "qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { QApplication::installTranslator(&qtTranslatorBase); } // Load e.g. qt_de_DE.qm if (qtTranslator.load( "qt_" + lang_territory, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { QApplication::installTranslator(&qtTranslator); } // Load e.g. bitcoin_de.qm (shortcut "de" needs to be defined in // bitcoin.qrc) if (translatorBase.load(lang, ":/translations/")) { QApplication::installTranslator(&translatorBase); } // Load e.g. bitcoin_de_DE.qm (shortcut "de_DE" needs to be defined in // bitcoin.qrc) if (translator.load(lang_territory, ":/translations/")) { QApplication::installTranslator(&translator); } } /* qDebug() message handler --> debug.log */ void DebugMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { Q_UNUSED(context); if (type == QtDebugMsg) { LogPrint(BCLog::QT, "GUI: %s\n", msg.toStdString()); } else { LogPrintf("GUI: %s\n", msg.toStdString()); } } /** * Class encapsulating Bitcoin ABC startup and shutdown. * Allows running startup and shutdown in a different thread from the UI thread. */ class BitcoinABC : public QObject { Q_OBJECT public: explicit BitcoinABC(interfaces::Node &node); public Q_SLOTS: void initialize(Config *config, RPCServer *rpcServer, HTTPRPCRequestProcessor *httpRPCRequestProcessor); void shutdown(); Q_SIGNALS: void initializeResult(bool success); void shutdownResult(); void runawayException(const QString &message); private: /// Pass fatal exception message to UI thread void handleRunawayException(const std::exception *e); interfaces::Node &m_node; }; /** Main Bitcoin application object */ class BitcoinApplication : public QApplication { Q_OBJECT public: explicit BitcoinApplication(interfaces::Node &node, int &argc, char **argv); ~BitcoinApplication(); #ifdef ENABLE_WALLET /// Create payment server void createPaymentServer(); #endif /// parameter interaction/setup based on rules void parameterSetup(); /// Create options model void createOptionsModel(bool resetSettings); /// Create main window void createWindow(const Config *, const NetworkStyle *networkStyle); /// Create splash screen void createSplashScreen(const NetworkStyle *networkStyle); /// Request core initialization void requestInitialize(Config &config, RPCServer &rpcServer, HTTPRPCRequestProcessor &httpRPCRequestProcessor); /// Request core shutdown void requestShutdown(Config &config); /// Get process return value int getReturnValue() const { return returnValue; } /// Get window identifier of QMainWindow (BitcoinGUI) WId getMainWinId() const; /// Setup platform style void setupPlatformStyle(); public Q_SLOTS: void initializeResult(bool success); void shutdownResult(); /// Handle runaway exceptions. Shows a message box with the problem and /// quits the program. void handleRunawayException(const QString &message); Q_SIGNALS: void requestedInitialize(Config *config, RPCServer *rpcServer, HTTPRPCRequestProcessor *httpRPCRequestProcessor); void requestedShutdown(); void stopThread(); void splashFinished(QWidget *window); private: QThread *coreThread; interfaces::Node &m_node; OptionsModel *optionsModel; ClientModel *clientModel; BitcoinGUI *window; QTimer *pollShutdownTimer; #ifdef ENABLE_WALLET PaymentServer *paymentServer; std::vector m_wallet_models; #endif int returnValue; const PlatformStyle *platformStyle; std::unique_ptr shutdownWindow; void startThread(); }; #include BitcoinABC::BitcoinABC(interfaces::Node &node) : QObject(), m_node(node) {} void BitcoinABC::handleRunawayException(const std::exception *e) { PrintExceptionContinue(e, "Runaway exception"); Q_EMIT runawayException(QString::fromStdString(m_node.getWarnings("gui"))); } void BitcoinABC::initialize(Config *config, RPCServer *rpcServer, HTTPRPCRequestProcessor *httpRPCRequestProcessor) { try { qDebug() << __func__ << ": Running initialization in thread"; bool rv = m_node.appInitMain(*config, *rpcServer, *httpRPCRequestProcessor); Q_EMIT initializeResult(rv); } catch (const std::exception &e) { handleRunawayException(&e); } catch (...) { handleRunawayException(nullptr); } } void BitcoinABC::shutdown() { try { qDebug() << __func__ << ": Running Shutdown in thread"; m_node.appShutdown(); qDebug() << __func__ << ": Shutdown finished"; Q_EMIT shutdownResult(); } catch (const std::exception &e) { handleRunawayException(&e); } catch (...) { handleRunawayException(nullptr); } } BitcoinApplication::BitcoinApplication(interfaces::Node &node, int &argc, char **argv) : QApplication(argc, argv), coreThread(0), m_node(node), optionsModel(0), clientModel(0), window(0), pollShutdownTimer(0), #ifdef ENABLE_WALLET paymentServer(0), m_wallet_models(), #endif returnValue(0), platformStyle(0) { setQuitOnLastWindowClosed(false); } void BitcoinApplication::setupPlatformStyle() { // UI per-platform customization // This must be done inside the BitcoinApplication constructor, or after it, // because PlatformStyle::instantiate requires a QApplication. std::string platformName; platformName = gArgs.GetArg("-uiplatform", BitcoinGUI::DEFAULT_UIPLATFORM); platformStyle = PlatformStyle::instantiate(QString::fromStdString(platformName)); // Fall back to "other" if specified name not found. if (!platformStyle) { platformStyle = PlatformStyle::instantiate("other"); } assert(platformStyle); } BitcoinApplication::~BitcoinApplication() { if (coreThread) { qDebug() << __func__ << ": Stopping thread"; Q_EMIT stopThread(); coreThread->wait(); qDebug() << __func__ << ": Stopped thread"; } delete window; window = 0; #ifdef ENABLE_WALLET delete paymentServer; paymentServer = 0; #endif delete optionsModel; optionsModel = 0; delete platformStyle; platformStyle = 0; } #ifdef ENABLE_WALLET void BitcoinApplication::createPaymentServer() { paymentServer = new PaymentServer(this); } #endif void BitcoinApplication::createOptionsModel(bool resetSettings) { optionsModel = new OptionsModel(m_node, nullptr, resetSettings); } void BitcoinApplication::createWindow(const Config *config, const NetworkStyle *networkStyle) { window = new BitcoinGUI(m_node, config, platformStyle, networkStyle, 0); pollShutdownTimer = new QTimer(window); connect(pollShutdownTimer, SIGNAL(timeout()), window, SLOT(detectShutdown())); } void BitcoinApplication::createSplashScreen(const NetworkStyle *networkStyle) { SplashScreen *splash = new SplashScreen(m_node, 0, networkStyle); // We don't hold a direct pointer to the splash screen after creation, but // the splash screen will take care of deleting itself when slotFinish // happens. splash->show(); connect(this, SIGNAL(splashFinished(QWidget *)), splash, SLOT(slotFinish(QWidget *))); connect(this, SIGNAL(requestedShutdown()), splash, SLOT(close())); } void BitcoinApplication::startThread() { if (coreThread) { return; } coreThread = new QThread(this); BitcoinABC *executor = new BitcoinABC(m_node); executor->moveToThread(coreThread); /* communication to and from thread */ connect(executor, SIGNAL(initializeResult(bool)), this, SLOT(initializeResult(bool))); connect(executor, SIGNAL(shutdownResult()), this, SLOT(shutdownResult())); connect(executor, SIGNAL(runawayException(QString)), this, SLOT(handleRunawayException(QString))); // Note on how Qt works: it tries to directly invoke methods if the signal // is emitted on the same thread that the target object 'lives' on. // But if the target object 'lives' on another thread (executor here does) // the SLOT will be invoked asynchronously at a later time in the thread // of the target object. So.. we pass a pointer around. If you pass // a reference around (even if it's non-const) you'll get Qt generating // code to copy-construct the parameter in question (Q_DECLARE_METATYPE // and qRegisterMetaType generate this code). For the Config class, // which is noncopyable, we can't do this. So.. we have to pass // pointers to Config around. Make sure Config &/Config * isn't a // temporary (eg it lives somewhere aside from the stack) or this will // crash because initialize() gets executed in another thread at some // unspecified time (after) requestedInitialize() is emitted! connect(this, SIGNAL(requestedInitialize(Config *, RPCServer *, HTTPRPCRequestProcessor *)), executor, SLOT(initialize(Config *, RPCServer *, HTTPRPCRequestProcessor *))); connect(this, SIGNAL(requestedShutdown()), executor, SLOT(shutdown())); /* make sure executor object is deleted in its own thread */ connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater())); connect(this, SIGNAL(stopThread()), coreThread, SLOT(quit())); coreThread->start(); } void BitcoinApplication::parameterSetup() { m_node.initLogging(); m_node.initParameterInteraction(); } void BitcoinApplication::requestInitialize( Config &config, RPCServer &rpcServer, HTTPRPCRequestProcessor &httpRPCRequestProcessor) { qDebug() << __func__ << ": Requesting initialize"; startThread(); // IMPORTANT: config must NOT be a reference to a temporary because below // signal may be connected to a slot that will be executed as a queued // connection in another thread! Q_EMIT requestedInitialize(&config, &rpcServer, &httpRPCRequestProcessor); } void BitcoinApplication::requestShutdown(Config &config) { // Show a simple window indicating shutdown status. Do this first as some of // the steps may take some time below, for example the RPC console may still // be executing a command. shutdownWindow.reset(ShutdownWindow::showShutdownWindow(window)); qDebug() << __func__ << ": Requesting shutdown"; startThread(); window->hide(); window->setClientModel(0); pollShutdownTimer->stop(); #ifdef ENABLE_WALLET window->removeAllWallets(); for (WalletModel *walletModel : m_wallet_models) { delete walletModel; } m_wallet_models.clear(); #endif delete clientModel; clientModel = 0; m_node.startShutdown(); // Request shutdown from core thread Q_EMIT requestedShutdown(); } void BitcoinApplication::initializeResult(bool success) { qDebug() << __func__ << ": Initialization result: " << success; returnValue = success ? EXIT_SUCCESS : EXIT_FAILURE; if (!success) { // Make sure splash screen doesn't stick around during shutdown. Q_EMIT splashFinished(window); // Exit first main loop invocation. quit(); return; } // Log this only after AppInitMain finishes, as then logging setup is // guaranteed complete. qWarning() << "Platform customization:" << platformStyle->getName(); #ifdef ENABLE_WALLET PaymentServer::LoadRootCAs(); paymentServer->setOptionsModel(optionsModel); #endif clientModel = new ClientModel(m_node, optionsModel); window->setClientModel(clientModel); #ifdef ENABLE_WALLET bool fFirstWallet = true; auto wallets = m_node.getWallets(); for (auto &wallet : wallets) { WalletModel *const walletModel = new WalletModel( std::move(wallet), m_node, platformStyle, optionsModel); window->addWallet(walletModel); if (fFirstWallet) { window->setCurrentWallet(walletModel->getWalletName()); fFirstWallet = false; } connect( walletModel, SIGNAL(coinsSent(WalletModel *, SendCoinsRecipient, QByteArray)), paymentServer, SLOT(fetchPaymentACK(WalletModel *, const SendCoinsRecipient &, QByteArray))); m_wallet_models.push_back(walletModel); } #endif // If -min option passed, start window minimized. if (gArgs.GetBoolArg("-min", false)) { window->showMinimized(); } else { window->show(); } Q_EMIT splashFinished(window); #ifdef ENABLE_WALLET // Now that initialization/startup is done, process any command-line // bitcoincash: URIs or payment requests: connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), window, SLOT(handlePaymentRequest(SendCoinsRecipient))); connect(window, SIGNAL(receivedURI(QString)), paymentServer, SLOT(handleURIOrFile(QString))); connect(paymentServer, SIGNAL(message(QString, QString, unsigned int)), window, SLOT(message(QString, QString, unsigned int))); QTimer::singleShot(100, paymentServer, SLOT(uiReady())); #endif pollShutdownTimer->start(200); } void BitcoinApplication::shutdownResult() { // Exit second main loop invocation after shutdown finished. quit(); } void BitcoinApplication::handleRunawayException(const QString &message) { QMessageBox::critical( 0, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. Bitcoin can no longer continue " "safely and will quit.") + QString("\n\n") + message); ::exit(EXIT_FAILURE); } WId BitcoinApplication::getMainWinId() const { if (!window) { return 0; } return window->winId(); } static void SetupUIArgs() { #ifdef ENABLE_WALLET gArgs.AddArg("-allowselfsignedrootcertificates", strprintf("Allow self signed root certificates (default: %d)", DEFAULT_SELFSIGNED_ROOTCERTS), true, OptionsCategory::GUI); #endif gArgs.AddArg( "-choosedatadir", strprintf(QObject::tr("Choose data directory on startup (default: %d)") .toStdString(), DEFAULT_CHOOSE_DATADIR), false, OptionsCategory::GUI); gArgs.AddArg( "-lang=", QObject::tr( "Set language, for example \"de_DE\" (default: system locale)") .toStdString(), false, OptionsCategory::GUI); gArgs.AddArg("-min", QObject::tr("Start minimized").toStdString(), false, OptionsCategory::GUI); gArgs.AddArg( "-rootcertificates=", QObject::tr( "Set SSL root certificates for payment request (default: -system-)") .toStdString(), false, OptionsCategory::GUI); gArgs.AddArg( "-splash", strprintf(QObject::tr("Show splash screen on startup (default: %d)") .toStdString(), DEFAULT_SPLASHSCREEN), false, OptionsCategory::GUI); gArgs.AddArg( "-resetguisettings", QObject::tr("Reset all settings changed in the GUI").toStdString(), false, OptionsCategory::GUI); gArgs.AddArg("-uiplatform", strprintf("Select platform to customize UI for (one of " "windows, macosx, other; default: %s)", BitcoinGUI::DEFAULT_UIPLATFORM), true, OptionsCategory::GUI); } #ifndef BITCOIN_QT_TEST static void MigrateSettings() { assert(!QApplication::applicationName().isEmpty()); static const QString legacyAppName("Bitcoin-Qt"), #ifdef Q_OS_DARWIN // Macs and/or iOS et al use a domain-style name for Settings // files. All other platforms use a simple orgname. This // difference is documented in the QSettings class documentation. legacyOrg("bitcoin.org"); #else legacyOrg("Bitcoin"); #endif QSettings // below picks up settings file location based on orgname,appname legacy(legacyOrg, legacyAppName), // default c'tor below picks up settings file location based on // QApplication::applicationName(), et al -- which was already set // in main() abc; #ifdef Q_OS_DARWIN // Disable bogus OSX keys from MacOS system-wide prefs that may cloud our // judgement ;) (this behavior is also documented in QSettings docs) legacy.setFallbacksEnabled(false); abc.setFallbacksEnabled(false); #endif const QStringList legacyKeys(legacy.allKeys()); // We only migrate settings if we have Core settings but no Bitcoin-ABC // settings if (!legacyKeys.isEmpty() && abc.allKeys().isEmpty()) { for (const QString &key : legacyKeys) { // now, copy settings over abc.setValue(key, legacy.value(key)); } } } int main(int argc, char *argv[]) { +#ifdef WIN32 + util::WinCmdLineArgs winArgs; + std::tie(argc, argv) = winArgs.get(); +#endif SetupEnvironment(); std::unique_ptr node = interfaces::MakeNode(); // Do not refer to data directory yet, this can be overridden by // Intro::pickDataDirectory /// 1. Basic Qt initialization (not dependent on parameters or /// configuration) Q_INIT_RESOURCE(bitcoin); Q_INIT_RESOURCE(bitcoin_locale); BitcoinApplication app(*node, argc, argv); #if QT_VERSION > 0x050100 // Generate high-dpi pixmaps QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif #if QT_VERSION >= 0x050600 QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif #ifdef Q_OS_MAC QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); #endif // Register meta types used for QMetaObject::invokeMethod qRegisterMetaType(); // Need to pass name here as Amount is a typedef (see // http://qt-project.org/doc/qt-5/qmetatype.html#qRegisterMetaType) // IMPORTANT if it is no longer a typedef use the normal variant above qRegisterMetaType("Amount"); qRegisterMetaType>("std::function"); // Need to register any types Qt doesn't know about if you intend // to use them with the signal/slot mechanism Qt provides. Even pointers. // Note that class Config is noncopyable and so we can't register a // non-pointer version of it with Qt, because Qt expects to be able to // copy-construct non-pointers to objects for invoking slots // behind-the-scenes in the 'Queued' connection case. qRegisterMetaType(); /// 2. Parse command-line options. We do this after qt in order to show an /// error if there are problems parsing these // Command-line options take precedence: node->setupServerArgs(); SetupUIArgs(); std::string error; if (!node->parseParameters(argc, argv, error)) { QMessageBox::critical( 0, QObject::tr(PACKAGE_NAME), QObject::tr("Error parsing command line arguments: %1.") .arg(QString::fromStdString(error))); return EXIT_FAILURE; } // Now that the QApplication is setup and we have parsed our parameters, we // can set the platform style app.setupPlatformStyle(); /// 3. Application identification // must be set before OptionsModel is initialized or translations are // loaded, as it is used to locate QSettings. // Note: If you move these calls somewhere else, be sure to bring // MigrateSettings() below along for the ride. QApplication::setOrganizationName(QAPP_ORG_NAME); QApplication::setOrganizationDomain(QAPP_ORG_DOMAIN); QApplication::setApplicationName(QAPP_APP_NAME_DEFAULT); // Migrate settings from core's/our old GUI settings to Bitcoin ABC // only if core's exist but Bitcoin ABC's doesn't. // NOTE -- this function needs to be called *after* the above 3 lines // that set the app orgname and app name! If you move the above 3 lines // to elsewhere, take this call with you! MigrateSettings(); /// 4. Initialization of translations, so that intro dialog is in user's /// language. Now that QSettings are accessible, initialize translations. QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator; initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator); // Show help message immediately after parsing command-line options (for // "-lang") and setting locale, but before showing splash screen. if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { HelpMessageDialog help(*node, nullptr, gArgs.IsArgSet("-version")); help.showOrPrint(); return EXIT_SUCCESS; } /// 5. Now that settings and translations are available, ask user for data /// directory. User language is set up: pick a data directory. if (!Intro::pickDataDirectory(*node)) { return EXIT_SUCCESS; } /// 6. Determine availability of data and blocks directory and parse /// bitcoin.conf /// - Do not call GetDataDir(true) before this step finishes. if (!fs::is_directory(GetDataDir(false))) { QMessageBox::critical( 0, QObject::tr(PACKAGE_NAME), QObject::tr( "Error: Specified data directory \"%1\" does not exist.") .arg(QString::fromStdString(gArgs.GetArg("-datadir", "")))); return EXIT_FAILURE; } if (!node->readConfigFiles(error)) { QMessageBox::critical( 0, QObject::tr(PACKAGE_NAME), QObject::tr("Error: Cannot parse configuration file: %1.") .arg(QString::fromStdString(error))); return EXIT_FAILURE; } /// 7. Determine network (and switch to network specific options) // - Do not call Params() before this step. // - Do this after parsing the configuration file, as the network can be // switched there. // - QSettings() will use the new application name after this, resulting in // network-specific settings. // - Needs to be done before createOptionsModel. // Check for -testnet or -regtest parameter (Params() calls are only valid // after this clause) try { node->selectParams(gArgs.GetChainName()); } catch (std::exception &e) { QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), QObject::tr("Error: %1").arg(e.what())); return EXIT_FAILURE; } #ifdef ENABLE_WALLET // Parse URIs on command line -- this can affect Params() PaymentServer::ipcParseCommandLine(*node, argc, argv); #endif QScopedPointer networkStyle(NetworkStyle::instantiate( QString::fromStdString(Params().NetworkIDString()))); assert(!networkStyle.isNull()); // Allow for separate UI settings for testnets QApplication::setApplicationName(networkStyle->getAppName()); // Re-initialize translations after changing application name (language in // network-specific settings can be different) initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator); #ifdef ENABLE_WALLET /// 8. URI IPC sending // - Do this early as we don't want to bother initializing if we are just // calling IPC // - Do this *after* setting up the data directory, as the data directory // hash is used in the name // of the server. // - Do this after creating app and setting up translations, so errors are // translated properly. if (PaymentServer::ipcSendCommandLine()) { exit(EXIT_SUCCESS); } // Start up the payment server early, too, so impatient users that click on // bitcoincash: links repeatedly have their payment requests routed to this // process: app.createPaymentServer(); #endif /// 9. Main GUI initialization // Install global event filter that makes sure that long tooltips can be // word-wrapped. app.installEventFilter( new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app)); #if defined(Q_OS_WIN) // Install global event filter for processing Windows session related // Windows messages (WM_QUERYENDSESSION and WM_ENDSESSION) qApp->installNativeEventFilter(new WinShutdownMonitor()); #endif // Install qDebug() message handler to route to debug.log qInstallMessageHandler(DebugMessageHandler); // Allow parameter interaction before we create the options model app.parameterSetup(); // Load GUI settings from QSettings app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false)); // Subscribe to global signals from core std::unique_ptr handler = node->handleInitMessage(InitMessage); // Get global config Config &config = const_cast(GetConfig()); if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) { app.createSplashScreen(networkStyle.data()); } RPCServer rpcServer; HTTPRPCRequestProcessor httpRPCRequestProcessor(config, rpcServer); try { app.createWindow(&config, networkStyle.data()); // Perform base initialization before spinning up // initialization/shutdown thread. This is acceptable because this // function only contains steps that are quick to execute, so the GUI // thread won't be held up. if (!node->baseInitialize(config)) { // A dialog with detailed error will have been shown by InitError() return EXIT_FAILURE; } app.requestInitialize(config, rpcServer, httpRPCRequestProcessor); #if defined(Q_OS_WIN) WinShutdownMonitor::registerShutdownBlockReason( QObject::tr("%1 didn't yet exit safely...") .arg(QObject::tr(PACKAGE_NAME)), (HWND)app.getMainWinId()); #endif app.exec(); app.requestShutdown(config); app.exec(); return app.getReturnValue(); } catch (const std::exception &e) { PrintExceptionContinue(&e, "Runaway exception"); app.handleRunawayException( QString::fromStdString(node->getWarnings("gui"))); } catch (...) { PrintExceptionContinue(nullptr, "Runaway exception"); app.handleRunawayException( QString::fromStdString(node->getWarnings("gui"))); } return EXIT_FAILURE; } #endif // BITCOIN_QT_TEST diff --git a/src/util/system.cpp b/src/util/system.cpp index 9d78a4bb51..7429c4379a 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -1,1351 +1,1380 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #if (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)) #include #include #endif #ifndef WIN32 // for posix_fallocate #ifdef __linux__ #ifdef _POSIX_C_SOURCE #undef _POSIX_C_SOURCE #endif #define _POSIX_C_SOURCE 200112L #endif // __linux__ #include #include #include #include #include #else #ifdef _MSC_VER #pragma warning(disable : 4786) #pragma warning(disable : 4804) #pragma warning(disable : 4805) #pragma warning(disable : 4717) #endif #ifdef _WIN32_WINNT #undef _WIN32_WINNT #endif #define _WIN32_WINNT 0x0501 #ifdef _WIN32_IE #undef _WIN32_IE #endif #define _WIN32_IE 0x0501 #define WIN32_LEAN_AND_MEAN 1 #ifndef NOMINMAX #define NOMINMAX #endif #include #include /* for _commit */ +#include #include #endif #ifdef HAVE_SYS_PRCTL_H #include #endif #ifdef HAVE_MALLOPT_ARENA_MAX #include #endif // Application startup time (used for uptime calculation) const int64_t nStartupTime = GetTime(); const char *const BITCOIN_CONF_FILENAME = "bitcoin.conf"; const char *const BITCOIN_PID_FILENAME = "bitcoind.pid"; ArgsManager gArgs; /** Init OpenSSL library multithreading support */ static std::unique_ptr ppmutexOpenSSL; void locking_callback(int mode, int i, const char *file, int line) NO_THREAD_SAFETY_ANALYSIS { if (mode & CRYPTO_LOCK) { ENTER_CRITICAL_SECTION(ppmutexOpenSSL[i]); } else { LEAVE_CRITICAL_SECTION(ppmutexOpenSSL[i]); } } // Singleton for wrapping OpenSSL setup/teardown. class CInit { public: CInit() { // Init OpenSSL library multithreading support. ppmutexOpenSSL.reset(new CCriticalSection[CRYPTO_num_locks()]); CRYPTO_set_locking_callback(locking_callback); // OpenSSL can optionally load a config file which lists optional // loadable modules and engines. We don't use them so we don't require // the config. However some of our libs may call functions which attempt // to load the config file, possibly resulting in an exit() or crash if // it is missing or corrupt. Explicitly tell OpenSSL not to try to load // the file. The result for our libs will be that the config appears to // have been loaded and there are no modules/engines available. OPENSSL_no_config(); #ifdef WIN32 // Seed OpenSSL PRNG with current contents of the screen. RAND_screen(); #endif // Seed OpenSSL PRNG with performance counter. RandAddSeed(); } ~CInit() { // Securely erase the memory used by the PRNG. RAND_cleanup(); // Shutdown OpenSSL library multithreading support. CRYPTO_set_locking_callback(nullptr); // Clear the set of locks now to maintain symmetry with the constructor. ppmutexOpenSSL.reset(); } } instance_of_cinit; /** * A map that contains all the currently held directory locks. After successful * locking, these will be held here until the global destructor cleans them up * and thus automatically unlocks them, or ReleaseDirectoryLocks is called. */ static std::map> dir_locks; /** Mutex to protect dir_locks. */ static std::mutex cs_dir_locks; bool LockDirectory(const fs::path &directory, const std::string lockfile_name, bool probe_only) { std::lock_guard ulock(cs_dir_locks); fs::path pathLockFile = directory / lockfile_name; // If a lock for this directory already exists in the map, don't try to // re-lock it if (dir_locks.count(pathLockFile.string())) { return true; } // Create empty lock file if it doesn't exist. FILE *file = fsbridge::fopen(pathLockFile, "a"); if (file) { fclose(file); } try { auto lock = std::make_unique( pathLockFile.string().c_str()); if (!lock->try_lock()) { return false; } if (!probe_only) { // Lock successful and we're not just probing, put it into the map dir_locks.emplace(pathLockFile.string(), std::move(lock)); } } catch (const boost::interprocess::interprocess_exception &e) { return error("Error while attempting to lock directory %s: %s", directory.string(), e.what()); } return true; } void ReleaseDirectoryLocks() { std::lock_guard ulock(cs_dir_locks); dir_locks.clear(); } bool DirIsWritable(const fs::path &directory) { fs::path tmpFile = directory / fs::unique_path(); FILE *file = fsbridge::fopen(tmpFile, "a"); if (!file) { return false; } fclose(file); remove(tmpFile); return true; } bool CheckDiskSpace(const fs::path &dir, uint64_t additional_bytes) { // 50 MiB constexpr uint64_t min_disk_space = 52428800; uint64_t free_bytes_available = fs::space(dir).available; return free_bytes_available >= min_disk_space + additional_bytes; } /** * Interpret a string argument as a boolean. * * The definition of atoi() requires that non-numeric string values like "foo", * return 0. This means that if a user unintentionally supplies a non-integer * argument here, the return value is always false. This means that -foo=false * does what the user probably expects, but -foo=true is well defined but does * not do what they probably expected. * * The return value of atoi() is undefined when given input not representable as * an int. On most systems this means string value between "-2147483648" and * "2147483647" are well defined (this method will return true). Setting * -txindex=2147483648 on most systems, however, is probably undefined. * * For a more extensive discussion of this topic (and a wide range of opinions * on the Right Way to change this code), see PR12713. */ static bool InterpretBool(const std::string &strValue) { if (strValue.empty()) { return true; } return (atoi(strValue) != 0); } /** Internal helper functions for ArgsManager */ class ArgsManagerHelper { public: typedef std::map> MapArgs; /** Determine whether to use config settings in the default section, * See also comments around ArgsManager::ArgsManager() below. */ static inline bool UseDefaultSection(const ArgsManager &am, const std::string &arg) { return (am.m_network == CBaseChainParams::MAIN || am.m_network_only_args.count(arg) == 0); } /** Convert regular argument into the network-specific setting */ static inline std::string NetworkArg(const ArgsManager &am, const std::string &arg) { assert(arg.length() > 1 && arg[0] == '-'); return "-" + am.m_network + "." + arg.substr(1); } /** Find arguments in a map and add them to a vector */ static inline void AddArgs(std::vector &res, const MapArgs &map_args, const std::string &arg) { auto it = map_args.find(arg); if (it != map_args.end()) { res.insert(res.end(), it->second.begin(), it->second.end()); } } /** * Return true/false if an argument is set in a map, and also * return the first (or last) of the possibly multiple values it has */ static inline std::pair GetArgHelper(const MapArgs &map_args, const std::string &arg, bool getLast = false) { auto it = map_args.find(arg); if (it == map_args.end() || it->second.empty()) { return std::make_pair(false, std::string()); } if (getLast) { return std::make_pair(true, it->second.back()); } else { return std::make_pair(true, it->second.front()); } } /** * Get the string value of an argument, returning a pair of a boolean * indicating the argument was found, and the value for the argument * if it was found (or the empty string if not found). */ static inline std::pair GetArg(const ArgsManager &am, const std::string &arg) { LOCK(am.cs_args); std::pair found_result(false, std::string()); // We pass "true" to GetArgHelper in order to return the last // argument value seen from the command line (so "bitcoind -foo=bar // -foo=baz" gives GetArg(am,"foo")=={true,"baz"} found_result = GetArgHelper(am.m_override_args, arg, true); if (found_result.first) { return found_result; } // But in contrast we return the first argument seen in a config file, // so "foo=bar \n foo=baz" in the config file gives // GetArg(am,"foo")={true,"bar"} if (!am.m_network.empty()) { found_result = GetArgHelper(am.m_config_args, NetworkArg(am, arg)); if (found_result.first) { return found_result; } } if (UseDefaultSection(am, arg)) { found_result = GetArgHelper(am.m_config_args, arg); if (found_result.first) { return found_result; } } return found_result; } /* Special test for -testnet and -regtest args, because we don't want to be * confused by craziness like "[regtest] testnet=1" */ static inline bool GetNetBoolArg(const ArgsManager &am, const std::string &net_arg) { std::pair found_result(false, std::string()); found_result = GetArgHelper(am.m_override_args, net_arg, true); if (!found_result.first) { found_result = GetArgHelper(am.m_config_args, net_arg, true); if (!found_result.first) { // not set return false; } } // is set, so evaluate return InterpretBool(found_result.second); } }; /** * Interpret -nofoo as if the user supplied -foo=0. * * This method also tracks when the -no form was supplied, and if so, checks * whether there was a double-negative (-nofoo=0 -> -foo=1). * * If there was not a double negative, it removes the "no" from the key, and * returns true, indicating the caller should clear the args vector to indicate * a negated option. * * If there was a double negative, it removes "no" from the key, sets the value * to "1" and returns false. * * If there was no "no", it leaves key and value untouched and returns false. * * Where an option was negated can be later checked using the IsArgNegated() * method. One use case for this is to have a way to disable options that are * not normally boolean (e.g. using -nodebuglogfile to request that debug log * output is not sent to any file at all). */ static bool InterpretNegatedOption(std::string &key, std::string &val) { assert(key[0] == '-'); size_t option_index = key.find('.'); if (option_index == std::string::npos) { option_index = 1; } else { ++option_index; } if (key.substr(option_index, 2) == "no") { bool bool_val = InterpretBool(val); key.erase(option_index, 2); if (!bool_val) { // Double negatives like -nofoo=0 are supported (but discouraged) LogPrintf( "Warning: parsed potentially confusing double-negative %s=%s\n", key, val); val = "1"; } else { return true; } } return false; } ArgsManager::ArgsManager() : /* These options would cause cross-contamination if values for mainnet * were used while running on regtest/testnet (or vice-versa). * Setting them as section_only_args ensures that sharing a config file * between mainnet and regtest/testnet won't cause problems due to these * parameters by accident. */ m_network_only_args{ "-addnode", "-connect", "-port", "-bind", "-rpcport", "-rpcbind", "-wallet", } { // nothing to do } void ArgsManager::WarnForSectionOnlyArgs() { // if there's no section selected, don't worry if (m_network.empty()) { return; } // if it's okay to use the default section for this network, don't worry if (m_network == CBaseChainParams::MAIN) { return; } for (const auto &arg : m_network_only_args) { std::pair found_result; // if this option is overridden it's fine found_result = ArgsManagerHelper::GetArgHelper(m_override_args, arg); if (found_result.first) { continue; } // if there's a network-specific value for this option, it's fine found_result = ArgsManagerHelper::GetArgHelper( m_config_args, ArgsManagerHelper::NetworkArg(*this, arg)); if (found_result.first) { continue; } // if there isn't a default value for this option, it's fine found_result = ArgsManagerHelper::GetArgHelper(m_config_args, arg); if (!found_result.first) { continue; } // otherwise, issue a warning LogPrintf("Warning: Config setting for %s only applied on %s network " "when in [%s] section.\n", arg, m_network, m_network); } } void ArgsManager::SelectConfigNetwork(const std::string &network) { m_network = network; } bool ArgsManager::ParseParameters(int argc, const char *const argv[], std::string &error) { LOCK(cs_args); m_override_args.clear(); for (int i = 1; i < argc; i++) { std::string key(argv[i]); std::string val; size_t is_index = key.find('='); if (is_index != std::string::npos) { val = key.substr(is_index + 1); key.erase(is_index); } #ifdef WIN32 std::transform(key.begin(), key.end(), key.begin(), ::tolower); if (key[0] == '/') { key[0] = '-'; } #endif if (key[0] != '-') { break; } // Transform --foo to -foo if (key.length() > 1 && key[1] == '-') { key.erase(0, 1); } // Check for -nofoo if (InterpretNegatedOption(key, val)) { m_override_args[key].clear(); } else { m_override_args[key].push_back(val); } // Check that the arg is known if (!(IsSwitchChar(key[0]) && key.size() == 1)) { if (!IsArgKnown(key)) { error = strprintf("Invalid parameter %s", key.c_str()); return false; } } } // we do not allow -includeconf from command line, so we clear it here auto it = m_override_args.find("-includeconf"); if (it != m_override_args.end()) { if (it->second.size() > 0) { for (const auto &ic : it->second) { fprintf(stderr, "warning: -includeconf cannot be used from " "commandline; ignoring -includeconf=%s\n", ic.c_str()); } m_override_args.erase(it); } } return true; } bool ArgsManager::IsArgKnown(const std::string &key) const { size_t option_index = key.find('.'); std::string arg_no_net; if (option_index == std::string::npos) { arg_no_net = key; } else { arg_no_net = std::string("-") + key.substr(option_index + 1, std::string::npos); } for (const auto &arg_map : m_available_args) { if (arg_map.second.count(arg_no_net)) { return true; } } return false; } std::vector ArgsManager::GetArgs(const std::string &strArg) const { std::vector result = {}; // special case if (IsArgNegated(strArg)) { return result; } LOCK(cs_args); ArgsManagerHelper::AddArgs(result, m_override_args, strArg); if (!m_network.empty()) { ArgsManagerHelper::AddArgs( result, m_config_args, ArgsManagerHelper::NetworkArg(*this, strArg)); } if (ArgsManagerHelper::UseDefaultSection(*this, strArg)) { ArgsManagerHelper::AddArgs(result, m_config_args, strArg); } return result; } bool ArgsManager::IsArgSet(const std::string &strArg) const { // special case if (IsArgNegated(strArg)) { return true; } return ArgsManagerHelper::GetArg(*this, strArg).first; } bool ArgsManager::IsArgNegated(const std::string &strArg) const { LOCK(cs_args); const auto &ov = m_override_args.find(strArg); if (ov != m_override_args.end()) { return ov->second.empty(); } if (!m_network.empty()) { const auto &cfs = m_config_args.find(ArgsManagerHelper::NetworkArg(*this, strArg)); if (cfs != m_config_args.end()) { return cfs->second.empty(); } } const auto &cf = m_config_args.find(strArg); if (cf != m_config_args.end()) { return cf->second.empty(); } return false; } std::string ArgsManager::GetArg(const std::string &strArg, const std::string &strDefault) const { if (IsArgNegated(strArg)) { return "0"; } std::pair found_res = ArgsManagerHelper::GetArg(*this, strArg); if (found_res.first) { return found_res.second; } return strDefault; } int64_t ArgsManager::GetArg(const std::string &strArg, int64_t nDefault) const { if (IsArgNegated(strArg)) { return 0; } std::pair found_res = ArgsManagerHelper::GetArg(*this, strArg); if (found_res.first) { return atoi64(found_res.second); } return nDefault; } bool ArgsManager::GetBoolArg(const std::string &strArg, bool fDefault) const { if (IsArgNegated(strArg)) { return false; } std::pair found_res = ArgsManagerHelper::GetArg(*this, strArg); if (found_res.first) { return InterpretBool(found_res.second); } return fDefault; } bool ArgsManager::SoftSetArg(const std::string &strArg, const std::string &strValue) { LOCK(cs_args); if (IsArgSet(strArg)) { return false; } ForceSetArg(strArg, strValue); return true; } bool ArgsManager::SoftSetBoolArg(const std::string &strArg, bool fValue) { if (fValue) { return SoftSetArg(strArg, std::string("1")); } else { return SoftSetArg(strArg, std::string("0")); } } void ArgsManager::ForceSetArg(const std::string &strArg, const std::string &strValue) { LOCK(cs_args); m_override_args[strArg] = {strValue}; } /** * This function is only used for testing purpose so * so we should not worry about element uniqueness and * integrity of mapMultiArgs data structure */ void ArgsManager::ForceSetMultiArg(const std::string &strArg, const std::string &strValue) { LOCK(cs_args); m_override_args[strArg].push_back(strValue); } void ArgsManager::AddArg(const std::string &name, const std::string &help, const bool debug_only, const OptionsCategory &cat) { // Split arg name from its help param size_t eq_index = name.find('='); if (eq_index == std::string::npos) { eq_index = name.size(); } std::map &arg_map = m_available_args[cat]; auto ret = arg_map.emplace( name.substr(0, eq_index), Arg(name.substr(eq_index, name.size() - eq_index), help, debug_only)); // Make sure an insertion actually happened. assert(ret.second); } void ArgsManager::ClearArg(const std::string &strArg) { LOCK(cs_args); m_override_args.erase(strArg); m_config_args.erase(strArg); } std::string ArgsManager::GetHelpMessage() const { const bool show_debug = gArgs.GetBoolArg("-help-debug", false); std::string usage = ""; for (const auto &arg_map : m_available_args) { switch (arg_map.first) { case OptionsCategory::OPTIONS: usage += HelpMessageGroup("Options:"); break; case OptionsCategory::CONNECTION: usage += HelpMessageGroup("Connection options:"); break; case OptionsCategory::ZMQ: usage += HelpMessageGroup("ZeroMQ notification options:"); break; case OptionsCategory::DEBUG_TEST: usage += HelpMessageGroup("Debugging/Testing options:"); break; case OptionsCategory::NODE_RELAY: usage += HelpMessageGroup("Node relay options:"); break; case OptionsCategory::BLOCK_CREATION: usage += HelpMessageGroup("Block creation options:"); break; case OptionsCategory::RPC: usage += HelpMessageGroup("RPC server options:"); break; case OptionsCategory::WALLET: usage += HelpMessageGroup("Wallet options:"); break; case OptionsCategory::WALLET_DEBUG_TEST: if (show_debug) { usage += HelpMessageGroup("Wallet debugging/testing options:"); } break; case OptionsCategory::CHAINPARAMS: usage += HelpMessageGroup("Chain selection options:"); break; case OptionsCategory::GUI: usage += HelpMessageGroup("UI Options:"); break; case OptionsCategory::COMMANDS: usage += HelpMessageGroup("Commands:"); break; case OptionsCategory::REGISTER_COMMANDS: usage += HelpMessageGroup("Register Commands:"); break; default: break; } // When we get to the hidden options, stop if (arg_map.first == OptionsCategory::HIDDEN) { break; } for (const auto &arg : arg_map.second) { if (show_debug || !arg.second.m_debug_only) { std::string name; if (arg.second.m_help_param.empty()) { name = arg.first; } else { name = arg.first + arg.second.m_help_param; } usage += HelpMessageOpt(name, arg.second.m_help_text); } } } return usage; } bool HelpRequested(const ArgsManager &args) { return args.IsArgSet("-?") || args.IsArgSet("-h") || args.IsArgSet("-help"); } static const int screenWidth = 79; static const int optIndent = 2; static const int msgIndent = 7; std::string HelpMessageGroup(const std::string &message) { return std::string(message) + std::string("\n\n"); } std::string HelpMessageOpt(const std::string &option, const std::string &message) { return std::string(optIndent, ' ') + std::string(option) + std::string("\n") + std::string(msgIndent, ' ') + FormatParagraph(message, screenWidth - msgIndent, msgIndent) + std::string("\n\n"); } static std::string FormatException(const std::exception *pex, const char *pszThread) { #ifdef WIN32 char pszModule[MAX_PATH] = ""; GetModuleFileNameA(nullptr, pszModule, sizeof(pszModule)); #else const char *pszModule = "bitcoin"; #endif if (pex) { return strprintf("EXCEPTION: %s \n%s \n%s in %s \n", typeid(*pex).name(), pex->what(), pszModule, pszThread); } else { return strprintf("UNKNOWN EXCEPTION \n%s in %s \n", pszModule, pszThread); } } void PrintExceptionContinue(const std::exception *pex, const char *pszThread) { std::string message = FormatException(pex, pszThread); LogPrintf("\n\n************************\n%s\n", message); fprintf(stderr, "\n\n************************\n%s\n", message.c_str()); } fs::path GetDefaultDataDir() { // Windows < Vista: C:\Documents and Settings\Username\Application Data\Bitcoin // Windows >= Vista: C:\Users\Username\AppData\Roaming\Bitcoin // Mac: ~/Library/Application Support/Bitcoin // Unix: ~/.bitcoin #ifdef WIN32 // Windows return GetSpecialFolderPath(CSIDL_APPDATA) / "Bitcoin"; #else fs::path pathRet; char *pszHome = getenv("HOME"); if (pszHome == nullptr || strlen(pszHome) == 0) { pathRet = fs::path("/"); } else { pathRet = fs::path(pszHome); } #ifdef MAC_OSX // Mac return pathRet / "Library/Application Support/Bitcoin"; #else // Unix return pathRet / ".bitcoin"; #endif #endif } static fs::path g_blocks_path_cache_net_specific; static fs::path pathCached; static fs::path pathCachedNetSpecific; static CCriticalSection csPathCached; const fs::path &GetBlocksDir() { LOCK(csPathCached); fs::path &path = g_blocks_path_cache_net_specific; // This can be called during exceptions by LogPrintf(), so we cache the // value so we don't have to do memory allocations after that. if (!path.empty()) { return path; } if (gArgs.IsArgSet("-blocksdir")) { path = fs::system_complete(gArgs.GetArg("-blocksdir", "")); if (!fs::is_directory(path)) { path = ""; return path; } } else { path = GetDataDir(false); } path /= BaseParams().DataDir(); path /= "blocks"; fs::create_directories(path); return path; } const fs::path &GetDataDir(bool fNetSpecific) { LOCK(csPathCached); fs::path &path = fNetSpecific ? pathCachedNetSpecific : pathCached; // This can be called during exceptions by LogPrintf(), so we cache the // value so we don't have to do memory allocations after that. if (!path.empty()) { return path; } if (gArgs.IsArgSet("-datadir")) { path = fs::system_complete(gArgs.GetArg("-datadir", "")); if (!fs::is_directory(path)) { path = ""; return path; } } else { path = GetDefaultDataDir(); } if (fNetSpecific) { path /= BaseParams().DataDir(); } if (fs::create_directories(path)) { // This is the first run, create wallets subdirectory too // // TODO: this is an ugly way to create the wallets/ directory and // really shouldn't be done here. Once this is fixed, please // also remove the corresponding line in bitcoind.cpp AppInit. // See more info at: // https://reviews.bitcoinabc.org/D3312 fs::create_directories(path / "wallets"); } return path; } void ClearDatadirCache() { LOCK(csPathCached); pathCached = fs::path(); pathCachedNetSpecific = fs::path(); g_blocks_path_cache_net_specific = fs::path(); } fs::path GetConfigFile(const std::string &confPath) { return AbsPathForConfigVal(fs::path(confPath), false); } static std::string TrimString(const std::string &str, const std::string &pattern) { std::string::size_type front = str.find_first_not_of(pattern); if (front == std::string::npos) { return std::string(); } std::string::size_type end = str.find_last_not_of(pattern); return str.substr(front, end - front + 1); } static std::vector> GetConfigOptions(std::istream &stream) { std::vector> options; std::string str, prefix; std::string::size_type pos; while (std::getline(stream, str)) { if ((pos = str.find('#')) != std::string::npos) { str = str.substr(0, pos); } const static std::string pattern = " \t\r\n"; str = TrimString(str, pattern); if (!str.empty()) { if (*str.begin() == '[' && *str.rbegin() == ']') { prefix = str.substr(1, str.size() - 2) + '.'; } else if ((pos = str.find('=')) != std::string::npos) { std::string name = prefix + TrimString(str.substr(0, pos), pattern); std::string value = TrimString(str.substr(pos + 1), pattern); options.emplace_back(name, value); } } } return options; } bool ArgsManager::ReadConfigStream(std::istream &stream, std::string &error, bool ignore_invalid_keys) { LOCK(cs_args); for (const std::pair &option : GetConfigOptions(stream)) { std::string strKey = std::string("-") + option.first; std::string strValue = option.second; if (InterpretNegatedOption(strKey, strValue)) { m_config_args[strKey].clear(); } else { m_config_args[strKey].push_back(strValue); } // Check that the arg is known if (!IsArgKnown(strKey) && !ignore_invalid_keys) { error = strprintf("Invalid configuration value %s", option.first.c_str()); return false; } } return true; } bool ArgsManager::ReadConfigFiles(std::string &error, bool ignore_invalid_keys) { { LOCK(cs_args); m_config_args.clear(); } const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME); fs::ifstream stream(GetConfigFile(confPath)); // ok to not have a config file if (stream.good()) { if (!ReadConfigStream(stream, error, ignore_invalid_keys)) { return false; } // if there is an -includeconf in the override args, but it is empty, // that means the user passed '-noincludeconf' on the command line, in // which case we should not include anything bool emptyIncludeConf; { LOCK(cs_args); emptyIncludeConf = m_override_args.count("-includeconf") == 0; } if (emptyIncludeConf) { std::vector includeconf(GetArgs("-includeconf")); { // We haven't set m_network yet (that happens in // SelectParams()), so manually check for network.includeconf // args. std::vector includeconf_net(GetArgs( std::string("-") + GetChainName() + ".includeconf")); includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); } // Remove -includeconf from configuration, so we can warn about // recursion later { LOCK(cs_args); m_config_args.erase("-includeconf"); m_config_args.erase(std::string("-") + GetChainName() + ".includeconf"); } for (const std::string &to_include : includeconf) { fs::ifstream include_config(GetConfigFile(to_include)); if (include_config.good()) { if (!ReadConfigStream(include_config, error, ignore_invalid_keys)) { return false; } LogPrintf("Included configuration file %s\n", to_include.c_str()); } else { fprintf(stderr, "Failed to include configuration file %s\n", to_include.c_str()); } } // Warn about recursive -includeconf includeconf = GetArgs("-includeconf"); { std::vector includeconf_net(GetArgs( std::string("-") + GetChainName() + ".includeconf")); includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); } for (const std::string &to_include : includeconf) { fprintf(stderr, "warning: -includeconf cannot be used from included " "files; ignoring -includeconf=%s\n", to_include.c_str()); } } } // If datadir is changed in .conf file: ClearDatadirCache(); if (!fs::is_directory(GetDataDir(false))) { error = strprintf("specified data directory \"%s\" does not exist.", gArgs.GetArg("-datadir", "").c_str()); return false; } return true; } std::string ArgsManager::GetChainName() const { bool fRegTest = ArgsManagerHelper::GetNetBoolArg(*this, "-regtest"); bool fTestNet = ArgsManagerHelper::GetNetBoolArg(*this, "-testnet"); if (fTestNet && fRegTest) { throw std::runtime_error( "Invalid combination of -regtest and -testnet."); } if (fRegTest) { return CBaseChainParams::REGTEST; } if (fTestNet) { return CBaseChainParams::TESTNET; } return CBaseChainParams::MAIN; } #ifndef WIN32 fs::path GetPidFile() { return AbsPathForConfigVal( fs::path(gArgs.GetArg("-pid", BITCOIN_PID_FILENAME))); } void CreatePidFile(const fs::path &path, pid_t pid) { FILE *file = fsbridge::fopen(path, "w"); if (file) { fprintf(file, "%d\n", pid); fclose(file); } } #endif bool RenameOver(fs::path src, fs::path dest) { #ifdef WIN32 return MoveFileExA(src.string().c_str(), dest.string().c_str(), MOVEFILE_REPLACE_EXISTING) != 0; #else int rc = std::rename(src.string().c_str(), dest.string().c_str()); return (rc == 0); #endif /* WIN32 */ } /** * Ignores exceptions thrown by Boost's create_directories if the requested * directory exists. Specifically handles case where path p exists, but it * wasn't possible for the user to write to the parent directory. */ bool TryCreateDirectories(const fs::path &p) { try { return fs::create_directories(p); } catch (const fs::filesystem_error &) { if (!fs::exists(p) || !fs::is_directory(p)) { throw; } } // create_directory didn't create the directory, it had to have existed // already. return false; } bool FileCommit(FILE *file) { // harmless if redundantly called if (fflush(file) != 0) { LogPrintf("%s: fflush failed: %d\n", __func__, errno); return false; } #ifdef WIN32 HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); if (FlushFileBuffers(hFile) == 0) { LogPrintf("%s: FlushFileBuffers failed: %d\n", __func__, GetLastError()); return false; } #else #if defined(__linux__) || defined(__NetBSD__) // Ignore EINVAL for filesystems that don't support sync if (fdatasync(fileno(file)) != 0 && errno != EINVAL) { LogPrintf("%s: fdatasync failed: %d\n", __func__, errno); return false; } #elif defined(MAC_OSX) && defined(F_FULLFSYNC) // Manpage says "value other than -1" is returned on success if (fcntl(fileno(file), F_FULLFSYNC, 0) == -1) { LogPrintf("%s: fcntl F_FULLFSYNC failed: %d\n", __func__, errno); return false; } #else if (fsync(fileno(file)) != 0 && errno != EINVAL) { LogPrintf("%s: fsync failed: %d\n", __func__, errno); return false; } #endif #endif return true; } bool TruncateFile(FILE *file, unsigned int length) { #if defined(WIN32) return _chsize(_fileno(file), length) == 0; #else return ftruncate(fileno(file), length) == 0; #endif } /** * This function tries to raise the file descriptor limit to the requested * number. It returns the actual file descriptor limit (which may be more or * less than nMinFD) */ int RaiseFileDescriptorLimit(int nMinFD) { #if defined(WIN32) return 2048; #else struct rlimit limitFD; if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) { if (limitFD.rlim_cur < (rlim_t)nMinFD) { limitFD.rlim_cur = nMinFD; if (limitFD.rlim_cur > limitFD.rlim_max) { limitFD.rlim_cur = limitFD.rlim_max; } setrlimit(RLIMIT_NOFILE, &limitFD); getrlimit(RLIMIT_NOFILE, &limitFD); } return limitFD.rlim_cur; } // getrlimit failed, assume it's fine. return nMinFD; #endif } /** * This function tries to make a particular range of a file allocated * (corresponding to disk space) it is advisory, and the range specified in the * arguments will never contain live data. */ void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length) { #if defined(WIN32) // Windows-specific version. HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); LARGE_INTEGER nFileSize; int64_t nEndPos = (int64_t)offset + length; nFileSize.u.LowPart = nEndPos & 0xFFFFFFFF; nFileSize.u.HighPart = nEndPos >> 32; SetFilePointerEx(hFile, nFileSize, 0, FILE_BEGIN); SetEndOfFile(hFile); #elif defined(MAC_OSX) // OSX specific version. fstore_t fst; fst.fst_flags = F_ALLOCATECONTIG; fst.fst_posmode = F_PEOFPOSMODE; fst.fst_offset = 0; fst.fst_length = (off_t)offset + length; fst.fst_bytesalloc = 0; if (fcntl(fileno(file), F_PREALLOCATE, &fst) == -1) { fst.fst_flags = F_ALLOCATEALL; fcntl(fileno(file), F_PREALLOCATE, &fst); } ftruncate(fileno(file), fst.fst_length); #elif defined(__linux__) // Version using posix_fallocate. off_t nEndPos = (off_t)offset + length; posix_fallocate(fileno(file), 0, nEndPos); #else // Fallback version // TODO: just write one byte per block static const char buf[65536] = {}; if (fseek(file, offset, SEEK_SET)) { return; } while (length > 0) { unsigned int now = 65536; if (length < now) { now = length; } // Allowed to fail; this function is advisory anyway. fwrite(buf, 1, now, file); length -= now; } #endif } #ifdef WIN32 fs::path GetSpecialFolderPath(int nFolder, bool fCreate) { char pszPath[MAX_PATH] = ""; if (SHGetSpecialFolderPathA(nullptr, pszPath, nFolder, fCreate)) { return fs::path(pszPath); } LogPrintf( "SHGetSpecialFolderPathA() failed, could not obtain requested path.\n"); return fs::path(""); } #endif void runCommand(const std::string &strCommand) { if (strCommand.empty()) { return; } #ifndef WIN32 int nErr = ::system(strCommand.c_str()); #else int nErr = ::_wsystem( std::wstring_convert, wchar_t>() .from_bytes(strCommand) .c_str()); #endif if (nErr) { LogPrintf("runCommand error: system(%s) returned %d\n", strCommand, nErr); } } void RenameThread(const char *name) { #if defined(PR_SET_NAME) // Only the first 15 characters are used (16 - NUL terminator) ::prctl(PR_SET_NAME, name, 0, 0, 0); #elif (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)) pthread_set_name_np(pthread_self(), name); #elif defined(MAC_OSX) pthread_setname_np(name); #else // Prevent warnings for unused parameters... (void)name; #endif } void SetupEnvironment() { #ifdef HAVE_MALLOPT_ARENA_MAX // glibc-specific: On 32-bit systems set the number of arenas to 1. By // default, since glibc 2.10, the C library will create up to two heap // arenas per core. This is known to cause excessive virtual address space // usage in our usage. Work around it by setting the maximum number of // arenas to 1. if (sizeof(void *) == 4) { mallopt(M_ARENA_MAX, 1); } #endif // On most POSIX systems (e.g. Linux, but not BSD) the environment's locale may // be invalid, in which case the "C" locale is used as fallback. #if !defined(WIN32) && !defined(MAC_OSX) && !defined(__FreeBSD__) && \ !defined(__OpenBSD__) try { // Raises a runtime error if current locale is invalid. std::locale(""); } catch (const std::runtime_error &) { setenv("LC_ALL", "C", 1); } +#elif defined(WIN32) + // Set the default input/output charset is utf-8 + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); #endif // The path locale is lazy initialized and to avoid deinitialization errors // in multithreading environments, it is set explicitly by the main thread. // A dummy locale is used to extract the internal default locale, used by // fs::path, which is then used to explicitly imbue the path. std::locale loc = fs::path::imbue(std::locale::classic()); #ifndef WIN32 fs::path::imbue(loc); #else fs::path::imbue(std::locale(loc, new std::codecvt_utf8_utf16())); #endif } bool SetupNetworking() { #ifdef WIN32 // Initialize Windows Sockets. WSADATA wsadata; int ret = WSAStartup(MAKEWORD(2, 2), &wsadata); if (ret != NO_ERROR || LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) { return false; } #endif return true; } int GetNumCores() { return boost::thread::physical_concurrency(); } std::string CopyrightHolders(const std::string &strPrefix) { return strPrefix + strprintf(_(COPYRIGHT_HOLDERS), _(COPYRIGHT_HOLDERS_SUBSTITUTION)); } // Obtain the application startup time (used for uptime calculation) int64_t GetStartupTime() { return nStartupTime; } fs::path AbsPathForConfigVal(const fs::path &path, bool net_specific) { return fs::absolute(path, GetDataDir(net_specific)); } int ScheduleBatchPriority() { #ifdef SCHED_BATCH const static sched_param param{}; if (int ret = pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m)) { LogPrintf("Failed to pthread_setschedparam: %s\n", strerror(errno)); return ret; } return 0; #else return 1; #endif } + +namespace util { +#ifdef WIN32 +WinCmdLineArgs::WinCmdLineArgs() { + wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &argc); + std::wstring_convert, wchar_t> utf8_cvt; + argv = new char *[argc]; + args.resize(argc); + for (int i = 0; i < argc; i++) { + args[i] = utf8_cvt.to_bytes(wargv[i]); + argv[i] = &*args[i].begin(); + } + LocalFree(wargv); +} + +WinCmdLineArgs::~WinCmdLineArgs() { + delete[] argv; +} + +std::pair WinCmdLineArgs::get() { + return std::make_pair(argc, argv); +} +#endif +} // namespace util diff --git a/src/util/system.h b/src/util/system.h index 51db3c9fd1..6519929ec5 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -1,368 +1,383 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. /** * Server/client environment: argument handling, config file parsing, * thread wrappers, startup time */ #ifndef BITCOIN_UTIL_SYSTEM_H #define BITCOIN_UTIL_SYSTEM_H #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include #include // for boost::thread_interrupted #include #include #include #include #include #include #include +#include #include // Application startup time (used for uptime calculation) int64_t GetStartupTime(); extern const char *const BITCOIN_CONF_FILENAME; extern const char *const BITCOIN_PID_FILENAME; /** Translate a message to the native language of the user. */ const extern std::function G_TRANSLATION_FUN; /** * Translation function. * If no translation function is set, simply return the input. */ inline std::string _(const char *psz) { return G_TRANSLATION_FUN ? (G_TRANSLATION_FUN)(psz) : psz; } void SetupEnvironment(); bool SetupNetworking(); template bool error(const char *fmt, const Args &... args) { LogPrintf("ERROR: " + tfm::format(fmt, args...) + "\n"); return false; } void PrintExceptionContinue(const std::exception *pex, const char *pszThread); bool FileCommit(FILE *file); bool TruncateFile(FILE *file, unsigned int length); int RaiseFileDescriptorLimit(int nMinFD); void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length); bool RenameOver(fs::path src, fs::path dest); bool LockDirectory(const fs::path &directory, const std::string lockfile_name, bool probe_only = false); bool DirIsWritable(const fs::path &directory); bool CheckDiskSpace(const fs::path &dir, uint64_t additional_bytes = 0); /** * Release all directory locks. This is used for unit testing only, at runtime * the global destructor will take care of the locks. */ void ReleaseDirectoryLocks(); bool TryCreateDirectories(const fs::path &p); fs::path GetDefaultDataDir(); // The blocks directory is always net specific. const fs::path &GetBlocksDir(); const fs::path &GetDataDir(bool fNetSpecific = true); void ClearDatadirCache(); fs::path GetConfigFile(const std::string &confPath); #ifndef WIN32 fs::path GetPidFile(); void CreatePidFile(const fs::path &path, pid_t pid); #endif #ifdef WIN32 fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true); #endif void runCommand(const std::string &strCommand); /** * Most paths passed as configuration arguments are treated as relative to * the datadir if they are not absolute. * * @param path The path to be conditionally prefixed with datadir. * @param net_specific Forwarded to GetDataDir(). * @return The normalized path. */ fs::path AbsPathForConfigVal(const fs::path &path, bool net_specific = true); inline bool IsSwitchChar(char c) { #ifdef WIN32 return c == '-' || c == '/'; #else return c == '-'; #endif } enum class OptionsCategory { OPTIONS, CONNECTION, WALLET, WALLET_DEBUG_TEST, ZMQ, DEBUG_TEST, CHAINPARAMS, NODE_RELAY, BLOCK_CREATION, RPC, GUI, COMMANDS, REGISTER_COMMANDS, // Always the last option to avoid printing these in the help HIDDEN, }; class ArgsManager { protected: friend class ArgsManagerHelper; struct Arg { std::string m_help_param; std::string m_help_text; bool m_debug_only; Arg(const std::string &help_param, const std::string &help_text, bool debug_only) : m_help_param(help_param), m_help_text(help_text), m_debug_only(debug_only){}; }; mutable CCriticalSection cs_args; std::map> m_override_args; std::map> m_config_args; std::string m_network; std::set m_network_only_args; std::map> m_available_args; bool ReadConfigStream(std::istream &stream, std::string &error, bool ignore_invalid_keys = false); public: ArgsManager(); /** * Select the network in use */ void SelectConfigNetwork(const std::string &network); bool ParseParameters(int argc, const char *const argv[], std::string &error); bool ReadConfigFiles(std::string &error, bool ignore_invalid_keys = false); /** * Log warnings for options in m_section_only_args when they are specified * in the default section but not overridden on the command line or in a * network-specific section in the config file. */ void WarnForSectionOnlyArgs(); /** * Return a vector of strings of the given argument * * @param strArg Argument to get (e.g. "-foo") * @return command-line arguments */ std::vector GetArgs(const std::string &strArg) const; /** * Return true if the given argument has been manually set. * * @param strArg Argument to get (e.g. "-foo") * @return true if the argument has been set */ bool IsArgSet(const std::string &strArg) const; /** * Return true if the argument was originally passed as a negated option, * i.e. -nofoo. * * @param strArg Argument to get (e.g. "-foo") * @return true if the argument was passed negated */ bool IsArgNegated(const std::string &strArg) const; /** * Return string argument or default value. * * @param strArg Argument to get (e.g. "-foo") * @param default (e.g. "1") * @return command-line argument or default value */ std::string GetArg(const std::string &strArg, const std::string &strDefault) const; /** * Return integer argument or default value. * * @param strArg Argument to get (e.g. "-foo") * @param default (e.g. 1) * @return command-line argument (0 if invalid number) or default value */ int64_t GetArg(const std::string &strArg, int64_t nDefault) const; /** * Return boolean argument or default value. * * @param strArg Argument to get (e.g. "-foo") * @param default (true or false) * @return command-line argument or default value */ bool GetBoolArg(const std::string &strArg, bool fDefault) const; /** * Set an argument if it doesn't already have a value. * * @param strArg Argument to set (e.g. "-foo") * @param strValue Value (e.g. "1") * @return true if argument gets set, false if it already had a value */ bool SoftSetArg(const std::string &strArg, const std::string &strValue); /** * Set a boolean argument if it doesn't already have a value. * * @param strArg Argument to set (e.g. "-foo") * @param fValue Value (e.g. false) * @return true if argument gets set, false if it already had a value */ bool SoftSetBoolArg(const std::string &strArg, bool fValue); // Forces an arg setting. Called by SoftSetArg() if the arg hasn't already // been set. Also called directly in testing. void ForceSetArg(const std::string &strArg, const std::string &strValue); // Forces a multi arg setting, used only in testing void ForceSetMultiArg(const std::string &strArg, const std::string &strValue); /** * Looks for -regtest, -testnet and returns the appropriate BIP70 chain * name. * @return CBaseChainParams::MAIN by default; raises runtime error if an * invalid combination is given. */ std::string GetChainName() const; /** * Add argument */ void AddArg(const std::string &name, const std::string &help, const bool debug_only, const OptionsCategory &cat); // Remove an arg setting, used only in testing void ClearArg(const std::string &strArg); /** * Clear available arguments */ void ClearArgs() { m_available_args.clear(); } /** * Get the help string */ std::string GetHelpMessage() const; /** * Check whether we know of this arg */ bool IsArgKnown(const std::string &key) const; }; extern ArgsManager gArgs; /** * @return true if help has been requested via a command-line arg */ bool HelpRequested(const ArgsManager &args); /** * Format a string to be used as group of options in help messages. * * @param message Group name (e.g. "RPC server options:") * @return the formatted string */ std::string HelpMessageGroup(const std::string &message); /** * Format a string to be used as option description in help messages. * * @param option Option message (e.g. "-rpcuser=") * @param message Option description (e.g. "Username for JSON-RPC connections") * @return the formatted string */ std::string HelpMessageOpt(const std::string &option, const std::string &message); /** * Return the number of physical cores available on the current system. * @note This does not count virtual cores, such as those provided by * HyperThreading when boost is newer than 1.56. */ int GetNumCores(); void RenameThread(const char *name); /** * .. and a wrapper that just calls func once */ template void TraceThread(const char *name, Callable func) { std::string s = strprintf("bitcoin-%s", name); RenameThread(s.c_str()); try { LogPrintf("%s thread start\n", name); func(); LogPrintf("%s thread exit\n", name); } catch (const boost::thread_interrupted &) { LogPrintf("%s thread interrupt\n", name); throw; } catch (const std::exception &e) { PrintExceptionContinue(&e, name); throw; } catch (...) { PrintExceptionContinue(nullptr, name); throw; } } std::string CopyrightHolders(const std::string &strPrefix); namespace util { //! Simplification of std insertion template inline void insert(Tdst &dst, const Tsrc &src) { dst.insert(dst.begin(), src.begin(), src.end()); } template inline void insert(std::set &dst, const Tsrc &src) { dst.insert(src.begin(), src.end()); } +#ifdef WIN32 +class WinCmdLineArgs { +public: + WinCmdLineArgs(); + ~WinCmdLineArgs(); + std::pair get(); + +private: + int argc; + char **argv; + std::vector args; +}; +#endif + } // namespace util /** * On platforms that support it, tell the kernel the calling thread is * CPU-intensive and non-interactive. See SCHED_BATCH in sched(7) for details. * * @return The return value of sched_setschedule(), or 1 on systems without * sched_setchedule(). */ int ScheduleBatchPriority(); #endif // BITCOIN_UTIL_SYSTEM_H diff --git a/test/functional/feature_uacomment.py b/test/functional/feature_uacomment.py index af25a98190..6d8ddaa5a4 100755 --- a/test/functional/feature_uacomment.py +++ b/test/functional/feature_uacomment.py @@ -1,43 +1,43 @@ #!/usr/bin/env python3 # Copyright (c) 2017 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the -uacomment option.""" import re from test_framework.test_framework import BitcoinTestFramework from test_framework.test_node import ErrorMatch from test_framework.util import assert_equal class UacommentTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True def run_test(self): self.log.info("test multiple -uacomment") test_uacomment = self.nodes[0].getnetworkinfo()["subversion"] assert_equal(test_uacomment[-12:-2], "testnode-0") self.restart_node(0, ["-uacomment=foo"]) foo_uacomment = self.nodes[0].getnetworkinfo()["subversion"][-17:-2] assert_equal(foo_uacomment, "testnode-0; foo") self.log.info("test -uacomment max length") self.stop_node(0) expected = r"Error: Total length of network version string \([0-9]+\) exceeds maximum length \(256\). Reduce the number or size of uacomments." self.nodes[0].assert_start_raises_init_error( ["-uacomment=" + 'a' * 256], expected, match=ErrorMatch.FULL_REGEX) self.log.info("test -uacomment unsafe characters") - for unsafe_char in ['/', ':', '(', ')']: + for unsafe_char in ['/', ':', '(', ')', '₿', '🏃']: expected = r"Error: User Agent comment \(" + re.escape( unsafe_char) + r"\) contains unsafe characters." self.nodes[0].assert_start_raises_init_error( ["-uacomment=" + unsafe_char], expected, match=ErrorMatch.FULL_REGEX) if __name__ == '__main__': UacommentTest().main()