diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 39c0f77b8..51f301c48 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1,594 +1,599 @@ // Copyright (c) 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. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MALLOC_INFO #include #endif static UniValue validateaddress(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error(RPCHelpMan{ "validateaddress", "\nReturn information about the given bitcoin address.\n", { {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to validate"}, }, RPCResult{ "{\n" " \"isvalid\" : true|false, (boolean) If the address is " "valid or not. If not, this is the only property returned.\n" " \"address\" : \"address\", (string) The bitcoin " "address validated\n" " \"scriptPubKey\" : \"hex\", (string) The hex-encoded " "scriptPubKey generated by the address\n" " \"isscript\" : true|false, (boolean) If the key is a " "script\n" "}\n"}, RPCExamples{ HelpExampleCli("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + HelpExampleRpc("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")}, } .ToString()); } CTxDestination dest = DecodeDestination(request.params[0].get_str(), config.GetChainParams()); bool isValid = IsValidDestination(dest); UniValue ret(UniValue::VOBJ); ret.pushKV("isvalid", isValid); if (isValid) { if (ret["address"].isNull()) { std::string currentAddress = EncodeDestination(dest, config); ret.pushKV("address", currentAddress); CScript scriptPubKey = GetScriptForDestination(dest); ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); UniValue detail = DescribeAddress(dest); ret.pushKVs(detail); } } return ret; } static UniValue createmultisig(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 2) { std::string msg = RPCHelpMan{ "createmultisig", "\nCreates a multi-signature address with n signature of m " "keys required.\n" "It returns a json object with the address and redeemScript.\n", { {"nrequired", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The number of required signatures out of the n keys."}, {"keys", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A json array of hex-encoded public keys.", { {"key", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex-encoded public key"}, }}, }, RPCResult{"{\n" " \"address\":\"multisigaddress\", (string) The " "value of the new multisig address.\n" " \"redeemScript\":\"script\" (string) The " "string value of the hex-encoded redemption script.\n" "}\n"}, RPCExamples{ "\nCreate a multisig address from 2 public keys\n" + HelpExampleCli( "createmultisig", "2 " "\"[" "\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd3" "42cf11ae157a7ace5fd\\\"," "\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e1" "7e107ef3f6aa5a61626\\\"]\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc( "createmultisig", "2, " "\"[" "\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd3" "42cf11ae157a7ace5fd\\\"," "\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e1" "7e107ef3f6aa5a61626\\\"]\"")}, } .ToString(); throw std::runtime_error(msg); } int required = request.params[0].get_int(); // Get the public keys const UniValue &keys = request.params[1].get_array(); std::vector pubkeys; for (size_t i = 0; i < keys.size(); ++i) { if ((keys[i].get_str().length() == 2 * CPubKey::COMPRESSED_PUBLIC_KEY_SIZE || keys[i].get_str().length() == 2 * CPubKey::PUBLIC_KEY_SIZE) && IsHex(keys[i].get_str())) { pubkeys.push_back(HexToPubKey(keys[i].get_str())); } else { throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n", keys[i].get_str())); } } // Get the output type OutputType output_type = OutputType::LEGACY; // Construct using pay-to-script-hash: const CScript inner = CreateMultisigRedeemscript(required, pubkeys); CBasicKeyStore keystore; const CTxDestination dest = AddAndGetDestinationForScript(keystore, inner, output_type); UniValue result(UniValue::VOBJ); result.pushKV("address", EncodeDestination(dest, config)); result.pushKV("redeemScript", HexStr(inner.begin(), inner.end())); return result; } static UniValue verifymessage(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 3) { throw std::runtime_error(RPCHelpMan{ "verifymessage", "\nVerify a signed message\n", { {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to use for the signature."}, {"signature", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The signature provided by the signer in base 64 encoding " "(see signmessage)."}, {"message", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The message that was signed."}, }, RPCResult{"true|false (boolean) If the signature is verified or " "not.\n"}, RPCExamples{ "\nUnlock the wallet for 30 seconds\n" + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + "\nCreate the signature\n" + HelpExampleCli( "signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + "\nVerify the signature\n" + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4" "XX\" \"signature\" \"my " "message\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4" "XX\", \"signature\", \"my " "message\"")}, } .ToString()); } LOCK(cs_main); std::string strAddress = request.params[0].get_str(); std::string strSign = request.params[1].get_str(); std::string strMessage = request.params[2].get_str(); CTxDestination destination = DecodeDestination(strAddress, config.GetChainParams()); if (!IsValidDestination(destination)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); } const CKeyID *keyID = boost::get(&destination); if (!keyID) { throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); } bool fInvalid = false; std::vector vchSig = DecodeBase64(strSign.c_str(), &fInvalid); if (fInvalid) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding"); } CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; ss << strMessage; CPubKey pubkey; if (!pubkey.RecoverCompact(ss.GetHash(), vchSig)) { return false; } return (pubkey.GetID() == *keyID); } static UniValue signmessagewithprivkey(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 2) { throw std::runtime_error(RPCHelpMan{ "signmessagewithprivkey", "\nSign a message with the private key of an address\n", { {"privkey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The private key to sign the message with."}, {"message", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The message to create a signature of."}, }, RPCResult{"\"signature\" (string) The signature of the " "message encoded in base 64\n"}, RPCExamples{"\nCreate the signature\n" + HelpExampleCli("signmessagewithprivkey", "\"privkey\" \"my message\"") + "\nVerify the signature\n" + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4" "XX\" \"signature\" \"my " "message\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"")}, } .ToString()); } std::string strPrivkey = request.params[0].get_str(); std::string strMessage = request.params[1].get_str(); CKey key = DecodeSecret(strPrivkey); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); } CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; ss << strMessage; std::vector vchSig; if (!key.SignCompact(ss.GetHash(), vchSig)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); } return EncodeBase64(vchSig.data(), vchSig.size()); } static UniValue setmocktime(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error(RPCHelpMan{ "setmocktime", "\nSet the local time to given timestamp (-regtest only)\n", { {"timestamp", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "Unix seconds-since-epoch timestamp\n" " Pass 0 to go back to using the system time."}, }, RPCResults{}, RPCExamples{""}, } .ToString()); } if (!config.GetChainParams().MineBlocksOnDemand()) { throw std::runtime_error( "setmocktime for regression testing (-regtest mode) only"); } // For now, don't change mocktime if we're in the middle of validation, as // this could have an effect on mempool time-based eviction, as well as // IsInitialBlockDownload(). // TODO: figure out the right way to synchronize around mocktime, and // ensure all call sites of GetTime() are accessing this safely. LOCK(cs_main); RPCTypeCheck(request.params, {UniValue::VNUM}); - SetMockTime(request.params[0].get_int64()); + int64_t mockTime = request.params[0].get_int64(); + if (mockTime < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "Timestamp must be 0 or greater"); + } + SetMockTime(mockTime); return NullUniValue; } static UniValue RPCLockedMemoryInfo() { LockedPool::Stats stats = LockedPoolManager::Instance().stats(); UniValue obj(UniValue::VOBJ); obj.pushKV("used", uint64_t(stats.used)); obj.pushKV("free", uint64_t(stats.free)); obj.pushKV("total", uint64_t(stats.total)); obj.pushKV("locked", uint64_t(stats.locked)); obj.pushKV("chunks_used", uint64_t(stats.chunks_used)); obj.pushKV("chunks_free", uint64_t(stats.chunks_free)); return obj; } #ifdef HAVE_MALLOC_INFO static std::string RPCMallocInfo() { char *ptr = nullptr; size_t size = 0; FILE *f = open_memstream(&ptr, &size); if (f) { malloc_info(0, f); fclose(f); if (ptr) { std::string rv(ptr, size); free(ptr); return rv; } } return ""; } #endif static UniValue getmemoryinfo(const Config &config, const JSONRPCRequest &request) { /* Please, avoid using the word "pool" here in the RPC interface or help, * as users will undoubtedly confuse it with the other "memory pool" */ if (request.fHelp || request.params.size() > 1) { throw std::runtime_error(RPCHelpMan{ "getmemoryinfo", "Returns an object containing information about memory usage.\n", { {"mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "determines what kind of information is returned. This " "argument is optional, the default mode is \"stats\".\n" " - \"stats\" returns general statistics about memory usage " "in the daemon.\n" " - \"mallocinfo\" returns an XML string describing " "low-level heap state (only available if compiled with glibc " "2.10+)."}, }, { RPCResult{"mode \"stats\"", "{\n" " \"locked\": { (json object) " "Information about locked memory manager\n" " \"used\": xxxxx, (numeric) Number of " "bytes used\n" " \"free\": xxxxx, (numeric) Number of " "bytes available in current arenas\n" " \"total\": xxxxxxx, (numeric) Total " "number of bytes managed\n" " \"locked\": xxxxxx, (numeric) Amount of " "bytes that succeeded locking. If this number is " "smaller than total, locking pages failed at some " "point and key data could be swapped to disk.\n" " \"chunks_used\": xxxxx, (numeric) Number " "allocated chunks\n" " \"chunks_free\": xxxxx, (numeric) Number " "unused chunks\n" " }\n" "}\n"}, RPCResult{"mode \"mallocinfo\"", "\"...\"\n"}, }, RPCExamples{HelpExampleCli("getmemoryinfo", "") + HelpExampleRpc("getmemoryinfo", "")}, } .ToString()); } std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str(); if (mode == "stats") { UniValue obj(UniValue::VOBJ); obj.pushKV("locked", RPCLockedMemoryInfo()); return obj; } else if (mode == "mallocinfo") { #ifdef HAVE_MALLOC_INFO return RPCMallocInfo(); #else throw JSONRPCError( RPC_INVALID_PARAMETER, "mallocinfo is only available when compiled with glibc 2.10+"); #endif } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode); } } static void EnableOrDisableLogCategories(UniValue cats, bool enable) { cats = cats.get_array(); for (size_t i = 0; i < cats.size(); ++i) { std::string cat = cats[i].get_str(); bool success; if (enable) { success = LogInstance().EnableCategory(cat); } else { success = LogInstance().DisableCategory(cat); } if (!success) { throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat); } } } static UniValue logging(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() > 2) { throw std::runtime_error(RPCHelpMan{ "logging", "Gets and sets the logging configuration.\n" "When called without an argument, returns the list of categories " "with status that are currently being debug logged or not.\n" "When called with arguments, adds or removes categories from debug " "logging and return the lists above.\n" "The arguments are evaluated in order \"include\", \"exclude\".\n" "If an item is both included and excluded, it will thus end up " "being excluded.\n" "The valid logging categories are: " + ListLogCategories() + "\n" "In addition, the following are available as category names " "with special meanings:\n" " - \"all\", \"1\" : represent all logging categories.\n" " - \"none\", \"0\" : even if other logging categories are " "specified, ignore all of them.\n", { {"include", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "", "A json array of categories to add debug logging", { {"include_category", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "the valid logging category"}, }}, {"exclude", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "", "A json array of categories to remove debug logging", { {"exclude_category", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "the valid logging category"}, }}, }, RPCResult{"{ (json object where keys are the " "logging categories, and values indicates its status\n" " \"category\": 0|1, (numeric) if being debug logged " "or not. 0:inactive, 1:active\n" " ...\n" "}\n"}, RPCExamples{HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"") + HelpExampleRpc("logging", "[\"all\"], \"[libevent]\"")}, } .ToString()); } uint32_t original_log_categories = LogInstance().GetCategoryMask(); if (request.params[0].isArray()) { EnableOrDisableLogCategories(request.params[0], true); } if (request.params[1].isArray()) { EnableOrDisableLogCategories(request.params[1], false); } uint32_t updated_log_categories = LogInstance().GetCategoryMask(); uint32_t changed_log_categories = original_log_categories ^ updated_log_categories; /** * Update libevent logging if BCLog::LIBEVENT has changed. * If the library version doesn't allow it, UpdateHTTPServerLogging() * returns false, in which case we should clear the BCLog::LIBEVENT flag. * Throw an error if the user has explicitly asked to change only the * libevent flag and it failed. */ if (changed_log_categories & BCLog::LIBEVENT) { if (!UpdateHTTPServerLogging( LogInstance().WillLogCategory(BCLog::LIBEVENT))) { LogInstance().DisableCategory(BCLog::LIBEVENT); if (changed_log_categories == BCLog::LIBEVENT) { throw JSONRPCError(RPC_INVALID_PARAMETER, "libevent logging cannot be updated when " "using libevent before v2.1.1."); } } } UniValue result(UniValue::VOBJ); std::vector vLogCatActive = ListActiveLogCategories(); for (const auto &logCatActive : vLogCatActive) { result.pushKV(logCatActive.category, logCatActive.active); } return result; } static UniValue echo(const Config &config, const JSONRPCRequest &request) { if (request.fHelp) { throw std::runtime_error(RPCHelpMan{ "echo|echojson ...", "\nSimply echo back the input arguments. This command is for " "testing.\n" "\nThe difference between echo and echojson is that echojson has " "argument conversion enabled in the client-side table in " "bitcoin-cli and the GUI. There is no server-side difference.", {}, RPCResults{}, RPCExamples{""}, } .ToString()); } CHECK_NONFATAL(request.params.size() != 100); return request.params; } // clang-format off static const CRPCCommand commands[] = { // category name actor (function) argNames // ------------------- ------------------------ ---------------------- ---------- { "control", "getmemoryinfo", getmemoryinfo, {"mode"} }, { "control", "logging", logging, {"include", "exclude"} }, { "util", "validateaddress", validateaddress, {"address"} }, { "util", "createmultisig", createmultisig, {"nrequired","keys"} }, { "util", "verifymessage", verifymessage, {"address","signature","message"} }, { "util", "signmessagewithprivkey", signmessagewithprivkey, {"privkey","message"} }, /* Not shown in help */ { "hidden", "setmocktime", setmocktime, {"timestamp"}}, { "hidden", "echo", echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}}, { "hidden", "echojson", echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}}, }; // clang-format on void RegisterMiscRPCCommands(CRPCTable &t) { for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) { t.appendCommand(commands[vcidx].name, &commands[vcidx]); } } diff --git a/src/util/time.cpp b/src/util/time.cpp index df2bbe86b..02f41f2d2 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -1,117 +1,118 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 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 //! For unit testing static std::atomic nMockTime(0); int64_t GetTime() { int64_t mocktime = nMockTime.load(std::memory_order_relaxed); if (mocktime) { return mocktime; } time_t now = time(nullptr); assert(now > 0); return now; } template T GetTime() { const std::chrono::seconds mocktime{ nMockTime.load(std::memory_order_relaxed)}; return std::chrono::duration_cast( mocktime.count() ? mocktime : std::chrono::microseconds{GetTimeMicros()}); } template std::chrono::seconds GetTime(); template std::chrono::milliseconds GetTime(); template std::chrono::microseconds GetTime(); void SetMockTime(int64_t nMockTimeIn) { + assert(nMockTimeIn >= 0); nMockTime.store(nMockTimeIn, std::memory_order_relaxed); } int64_t GetMockTime() { return nMockTime.load(std::memory_order_relaxed); } int64_t GetTimeMillis() { int64_t now = (boost::posix_time::microsec_clock::universal_time() - boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1))) .total_milliseconds(); assert(now > 0); return now; } int64_t GetTimeMicros() { int64_t now = (boost::posix_time::microsec_clock::universal_time() - boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1))) .total_microseconds(); assert(now > 0); return now; } int64_t GetSystemTimeInSeconds() { return GetTimeMicros() / 1000000; } void MilliSleep(int64_t n) { boost::this_thread::sleep_for(boost::chrono::milliseconds(n)); } std::string FormatISO8601DateTime(int64_t nTime) { struct tm ts; time_t time_val = nTime; #ifdef _WIN32 gmtime_s(&ts, &time_val); #else gmtime_r(&time_val, &ts); #endif return strprintf("%04i-%02i-%02iT%02i:%02i:%02iZ", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec); } std::string FormatISO8601Date(int64_t nTime) { struct tm ts; time_t time_val = nTime; #ifdef _WIN32 gmtime_s(&ts, &time_val); #else gmtime_r(&time_val, &ts); #endif return strprintf("%04i-%02i-%02i", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday); } int64_t ParseISO8601DateTime(const std::string &str) { static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0); static const std::locale loc( std::locale::classic(), new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ")); std::istringstream iss(str); iss.imbue(loc); boost::posix_time::ptime ptime(boost::date_time::not_a_date_time); iss >> ptime; if (ptime.is_not_a_date_time() || epoch > ptime) { return 0; } return (ptime - epoch).total_seconds(); } diff --git a/test/functional/abc-rpc-mocktime.py b/test/functional/abc-rpc-mocktime.py new file mode 100755 index 000000000..4606c96bf --- /dev/null +++ b/test/functional/abc-rpc-mocktime.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The Bitcoin developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Test RPCs related to mock time. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_raises_rpc_error + + +class MocktimeTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + + def run_test(self): + self.nodes[0].setmocktime(9223372036854775807) + self.nodes[0].setmocktime(0) + assert_raises_rpc_error(-8, "Timestamp must be 0 or greater", + self.nodes[0].setmocktime, -1) + assert_raises_rpc_error(-8, "Timestamp must be 0 or greater", + self.nodes[0].setmocktime, -9223372036854775808) + + +if __name__ == '__main__': + MocktimeTest().main()