diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 80310ec47..97fad48b7 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1,892 +1,891 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2018 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 #include #include #include #include #include #include /** * Return average network hashes per second based on the last 'lookup' blocks, * or from the last difficulty change if 'lookup' is nonpositive. If 'height' is * nonnegative, compute the estimate at the time when a given block was found. */ static UniValue GetNetworkHashPS(int lookup, int height) { CBlockIndex *pb = ::ChainActive().Tip(); if (height >= 0 && height < ::ChainActive().Height()) { pb = ::ChainActive()[height]; } if (pb == nullptr || !pb->nHeight) { return 0; } // If lookup is -1, then use blocks since last difficulty change. if (lookup <= 0) { lookup = pb->nHeight % Params().GetConsensus().DifficultyAdjustmentInterval() + 1; } // If lookup is larger than chain, then set it to chain length. if (lookup > pb->nHeight) { lookup = pb->nHeight; } CBlockIndex *pb0 = pb; int64_t minTime = pb0->GetBlockTime(); int64_t maxTime = minTime; for (int i = 0; i < lookup; i++) { pb0 = pb0->pprev; int64_t time = pb0->GetBlockTime(); minTime = std::min(time, minTime); maxTime = std::max(time, maxTime); } // In case there's a situation where minTime == maxTime, we don't want a // divide by zero exception. if (minTime == maxTime) { return 0; } arith_uint256 workDiff = pb->nChainWork - pb0->nChainWork; int64_t timeDiff = maxTime - minTime; return workDiff.getdouble() / timeDiff; } static UniValue getnetworkhashps(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() > 2) { throw std::runtime_error( RPCHelpMan{"getnetworkhashps", "\nReturns the estimated network hashes per second " "based on the last n blocks.\n" "Pass in [blocks] to override # of blocks, -1 specifies " "since last difficulty change.\n" "Pass in [height] to estimate the network speed at the " "time when a certain block was found.\n", { - {"nblocks", RPCArg::Type::NUM, true}, - {"height", RPCArg::Type::NUM, true}, + {"nblocks", RPCArg::Type::NUM, /* opt */ true, + /* default_val */ "120", + "The number of blocks, or -1 for blocks since last " + "difficulty change."}, + {"height", RPCArg::Type::NUM, /* opt */ true, + /* default_val */ "-1", + "To estimate at the time of the given height."}, }} - .ToString() + - "\nArguments:\n" - "1. nblocks (numeric, optional, default=120) The number of " - "blocks, or -1 for blocks since last difficulty change.\n" - "2. height (numeric, optional, default=-1) To estimate at the " - "time of the given height.\n" + .ToStringWithArgs() + "\nResult:\n" "x (numeric) Hashes per second estimated\n" "\nExamples:\n" + HelpExampleCli("getnetworkhashps", "") + HelpExampleRpc("getnetworkhashps", "")); } LOCK(cs_main); return GetNetworkHashPS( !request.params[0].isNull() ? request.params[0].get_int() : 120, !request.params[1].isNull() ? request.params[1].get_int() : -1); } UniValue generateBlocks(const Config &config, std::shared_ptr coinbaseScript, int nGenerate, uint64_t nMaxTries, bool keepScript) { static const int nInnerLoopCount = 0x100000; int nHeightEnd = 0; int nHeight = 0; { // Don't keep cs_main locked. LOCK(cs_main); nHeight = ::ChainActive().Height(); nHeightEnd = nHeight + nGenerate; } unsigned int nExtraNonce = 0; UniValue blockHashes(UniValue::VARR); while (nHeight < nHeightEnd && !ShutdownRequested()) { std::unique_ptr pblocktemplate( BlockAssembler(config, g_mempool) .CreateNewBlock(coinbaseScript->reserveScript)); if (!pblocktemplate.get()) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); } CBlock *pblock = &pblocktemplate->block; { LOCK(cs_main); IncrementExtraNonce(pblock, ::ChainActive().Tip(), config.GetMaxBlockSize(), nExtraNonce); } while (nMaxTries > 0 && pblock->nNonce < nInnerLoopCount && !CheckProofOfWork(pblock->GetHash(), pblock->nBits, config.GetChainParams().GetConsensus())) { ++pblock->nNonce; --nMaxTries; } if (nMaxTries == 0) { break; } if (pblock->nNonce == nInnerLoopCount) { continue; } std::shared_ptr shared_pblock = std::make_shared(*pblock); if (!ProcessNewBlock(config, shared_pblock, true, nullptr)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted"); } ++nHeight; blockHashes.push_back(pblock->GetHash().GetHex()); // Mark script as important because it was used at least for one // coinbase output if the script came from the wallet. if (keepScript) { coinbaseScript->KeepScript(); } } return blockHashes; } static UniValue generatetoaddress(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) { throw std::runtime_error( - RPCHelpMan{"generatetoaddress", - "\nMine blocks immediately to a specified address " - "before the RPC call returns)\n", - { - {"nblocks", RPCArg::Type::NUM, false}, - {"address", RPCArg::Type::STR, false}, - {"maxtries", RPCArg::Type::NUM, true}, - }} - .ToString() + - "\nArguments:\n" - "1. nblocks (numeric, required) How many blocks are generated " - "immediately.\n" - "2. address (string, required) The address to send the newly " - "generated bitcoin to.\n" - "3. maxtries (numeric, optional) How many iterations to try " - "(default = 1000000).\n" + RPCHelpMan{ + "generatetoaddress", + "\nMine blocks immediately to a specified address before the " + "RPC call returns)\n", + { + {"nblocks", RPCArg::Type::NUM, /* opt */ false, + /* default_val */ "", + "How many blocks are generated immediately."}, + {"address", RPCArg::Type::STR, /* opt */ false, + /* default_val */ "", + "The address to send the newly generated bitcoin to."}, + {"maxtries", RPCArg::Type::NUM, /* opt */ true, + /* default_val */ "1000000", + "How many iterations to try."}, + }} + .ToStringWithArgs() + "\nResult:\n" "[ blockhashes ] (array) hashes of blocks generated\n" "\nExamples:\n" "\nGenerate 11 blocks to myaddress\n" + HelpExampleCli("generatetoaddress", "11 \"myaddress\"")); } int nGenerate = request.params[0].get_int(); uint64_t nMaxTries = 1000000; if (!request.params[2].isNull()) { nMaxTries = request.params[2].get_int(); } CTxDestination destination = DecodeDestination(request.params[1].get_str(), config.GetChainParams()); if (!IsValidDestination(destination)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Error: Invalid address"); } std::shared_ptr coinbaseScript = std::make_shared(); coinbaseScript->reserveScript = GetScriptForDestination(destination); return generateBlocks(config, coinbaseScript, nGenerate, nMaxTries, false); } static UniValue getmininginfo(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( RPCHelpMan{"getmininginfo", "\nReturns a json object containing mining-related " "information.", {}} - .ToString() + + .ToStringWithArgs() + "\nResult:\n" "{\n" " \"blocks\": nnn, (numeric) The current block\n" " \"currentblocksize\": nnn, (numeric) The last block size\n" " \"currentblocktx\": nnn, (numeric) The last block " "transaction\n" " \"difficulty\": xxx.xxxxx (numeric) The current difficulty\n" " \"networkhashps\": nnn, (numeric) The network hashes per " "second\n" " \"pooledtx\": n (numeric) The size of the mempool\n" " \"chain\": \"xxxx\", (string) current network name as " "defined in BIP70 (main, test, regtest)\n" " \"warnings\": \"...\" (string) any network and " "blockchain warnings\n" "}\n" "\nExamples:\n" + HelpExampleCli("getmininginfo", "") + HelpExampleRpc("getmininginfo", "")); } LOCK(cs_main); UniValue obj(UniValue::VOBJ); obj.pushKV("blocks", int(::ChainActive().Height())); obj.pushKV("currentblocksize", uint64_t(nLastBlockSize)); obj.pushKV("currentblocktx", uint64_t(nLastBlockTx)); obj.pushKV("difficulty", double(GetDifficulty(::ChainActive().Tip()))); obj.pushKV("networkhashps", getnetworkhashps(config, request)); obj.pushKV("pooledtx", uint64_t(g_mempool.size())); obj.pushKV("chain", config.GetChainParams().NetworkIDString()); obj.pushKV("warnings", GetWarnings("statusbar")); return obj; } // NOTE: Unlike wallet RPC (which use BCH values), mining RPCs follow GBT (BIP // 22) in using satoshi amounts static UniValue prioritisetransaction(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 3) { throw std::runtime_error( - RPCHelpMan{"prioritisetransaction", - "Accepts the transaction into mined blocks at a higher " - "(or lower) priority\n", - { - {"txid", RPCArg::Type::STR_HEX, false}, - {"dummy", RPCArg::Type::NUM, false}, - {"fee_delta", RPCArg::Type::NUM, false}, - }} - .ToString() + - "\nArguments:\n" - "1. \"txid\" (string, required) The transaction id.\n" - "2. dummy (numeric, optional) API-Compatibility for " - "previous API. Must be zero or null.\n" - " DEPRECATED. For forward compatibility use named " - "arguments and omit this parameter.\n" - "3. fee_delta (numeric, required) The fee value (in satoshis) " - "to add (or subtract, if negative).\n" - " The fee is not actually paid, only the " - "algorithm for selecting transactions into a block\n" - " considers the transaction as it would have paid " - "a higher (or lower) fee.\n" + RPCHelpMan{ + "prioritisetransaction", + "Accepts the transaction into mined blocks at a higher " + "(or lower) priority\n", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", "The transaction id."}, + {"dummy", RPCArg::Type::NUM, /* opt */ true, + /* default_val */ "null", + "API-Compatibility for previous API. Must be zero or " + "null.\n" + " DEPRECATED. For forward compatibility " + "use named arguments and omit this parameter."}, + {"fee_delta", RPCArg::Type::NUM, /* opt */ false, + /* default_val */ "", + "The fee value (in satoshis) to add (or subtract, if " + "negative).\n" + " The fee is not actually paid, " + "only the algorithm for selecting transactions into a " + "block\n" + " considers the transaction as it would " + "have paid a higher (or lower) fee."}, + }} + .ToStringWithArgs() + "\nResult:\n" "true (boolean) Returns true\n" "\nExamples:\n" + HelpExampleCli("prioritisetransaction", "\"txid\" 0.0 10000") + HelpExampleRpc("prioritisetransaction", "\"txid\", 0.0, 10000")); } LOCK(cs_main); TxId txid(ParseHashV(request.params[0], "txid")); Amount nAmount = request.params[2].get_int64() * SATOSHI; if (!(request.params[1].isNull() || request.params[1].get_real() == 0)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Priority is no longer supported, dummy argument to " "prioritisetransaction must be 0."); } g_mempool.PrioritiseTransaction(txid, nAmount); return true; } // NOTE: Assumes a conclusive result; if result is inconclusive, it must be // handled by caller static UniValue BIP22ValidationResult(const Config &config, const CValidationState &state) { if (state.IsValid()) { return NullUniValue; } if (state.IsError()) { throw JSONRPCError(RPC_VERIFY_ERROR, FormatStateMessage(state)); } if (state.IsInvalid()) { std::string strRejectReason = state.GetRejectReason(); if (strRejectReason.empty()) { return "rejected"; } return strRejectReason; } // Should be impossible. return "valid?"; } static UniValue getblocktemplate(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() > 1) { throw std::runtime_error( RPCHelpMan{ "getblocktemplate", "\nIf the request parameters include a 'mode' key, that is " "used to explicitly select between the default 'template' " "request or a 'proposal'.\n" "It returns data needed to construct a block to work on.\n" "For full specification, see BIPs 22, 23, 9, and 145:\n" " " "https://github.com/bitcoin/bips/blob/master/" "bip-0022.mediawiki\n" " " "https://github.com/bitcoin/bips/blob/master/" "bip-0023.mediawiki\n" " " "https://github.com/bitcoin/bips/blob/master/" "bip-0009.mediawiki#getblocktemplate_changes\n" " ", { {"template_request", RPCArg::Type::OBJ, + /* opt */ true, + /* default_val */ "", + "A json object in the following spec", { - {"mode", RPCArg::Type::STR, true}, - {"capabilities", - RPCArg::Type::ARR, - { - {"support", RPCArg::Type::STR, true}, - }, - true}, + {"mode", RPCArg::Type::STR, /* opt */ true, + /* default_val */ "", + "This must be set to \"template\", \"proposal\" (see " + "BIP 23), or omitted"}, + { + "capabilities", + RPCArg::Type::ARR, + /* opt */ true, + /* default_val */ "", + "A list of strings", + { + {"support", RPCArg::Type::STR, /* opt */ true, + /* default_val */ "", + "client side supported feature, 'longpoll', " + "'coinbasetxn', 'coinbasevalue', 'proposal', " + "'serverlist', 'workid'"}, + }, + }, }, - true, "\"template_request\""}, }} - .ToString() + - "\nArguments:\n" - "1. template_request (json object, optional) A json object " - "in the following spec\n" - " {\n" - " \"mode\":\"template\" (string, optional) This must be " - "set to \"template\", \"proposal\" (see BIP 23), or omitted\n" - " \"capabilities\":[ (array, optional) A list of " - "strings\n" - " \"support\" (string) client side supported " - "feature, 'longpoll', 'coinbasetxn', 'coinbasevalue', 'proposal', " - "'serverlist', 'workid'\n" - " ,...\n" - " ]\n" - " }\n" - "\n" - + .ToStringWithArgs() + "\nResult:\n" "{\n" " \"version\" : n, (numeric) The preferred " "block version\n" " \"previousblockhash\" : \"xxxx\", (string) The hash of " "current highest block\n" " \"transactions\" : [ (array) contents of " "non-coinbase transactions that should be included in the next " "block\n" " {\n" " \"data\" : \"xxxx\", (string) transaction " "data encoded in hexadecimal (byte-for-byte)\n" " \"txid\" : \"xxxx\", (string) transaction id " "encoded in little-endian hexadecimal\n" " \"hash\" : \"xxxx\", (string) hash encoded " "in little-endian hexadecimal (including witness data)\n" " \"depends\" : [ (array) array of numbers " "\n" " n (numeric) transactions " "before this one (by 1-based index in 'transactions' list) that " "must be present in the final block if this one is\n" " ,...\n" " ],\n" " \"fee\": n, (numeric) difference in " "value between transaction inputs and outputs (in satoshis); for " "coinbase transactions, this is a negative Number of the total " "collected block fees (ie, not including the block subsidy); if " "key is not present, fee is unknown and clients MUST NOT assume " "there isn't one\n" " \"sigops\" : n, (numeric) total SigOps " "count, as counted for purposes of block limits; if key is not " "present, sigop count is unknown and clients MUST NOT assume it is " "zero\n" " \"required\" : true|false (boolean) if provided and " "true, this transaction must be in the final block\n" " }\n" " ,...\n" " ],\n" " \"coinbaseaux\" : { (json object) data that " "should be included in the coinbase's scriptSig content\n" " \"flags\" : \"xx\" (string) key name is to " "be ignored, and value included in scriptSig\n" " },\n" " \"coinbasevalue\" : n, (numeric) maximum allowable " "input to coinbase transaction, including the generation award and " "transaction fees (in satoshis)\n" " \"coinbasetxn\" : { ... }, (json object) information " "for coinbase transaction\n" " \"target\" : \"xxxx\", (string) The hash target\n" " \"mintime\" : xxx, (numeric) The minimum " "timestamp appropriate for next block time in seconds since epoch " "(Jan 1 1970 GMT)\n" " \"mutable\" : [ (array of string) list of " "ways the block template may be changed \n" " \"value\" (string) A way the block " "template may be changed, e.g. 'time', 'transactions', " "'prevblock'\n" " ,...\n" " ],\n" " \"noncerange\" : \"00000000ffffffff\",(string) A range of valid " "nonces\n" " \"sigoplimit\" : n, (numeric) limit of sigops " "in blocks\n" " \"sizelimit\" : n, (numeric) limit of block " "size\n" " \"curtime\" : ttt, (numeric) current timestamp " "in seconds since epoch (Jan 1 1970 GMT)\n" " \"bits\" : \"xxxxxxxx\", (string) compressed " "target of next block\n" " \"height\" : n (numeric) The height of the " "next block\n" "}\n" "\nExamples:\n" + HelpExampleCli("getblocktemplate", "") + HelpExampleRpc("getblocktemplate", "")); } LOCK(cs_main); std::string strMode = "template"; UniValue lpval = NullUniValue; std::set setClientRules; if (!request.params[0].isNull()) { const UniValue &oparam = request.params[0].get_obj(); const UniValue &modeval = find_value(oparam, "mode"); if (modeval.isStr()) { strMode = modeval.get_str(); } else if (modeval.isNull()) { /* Do nothing */ } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode"); } lpval = find_value(oparam, "longpollid"); if (strMode == "proposal") { const UniValue &dataval = find_value(oparam, "data"); if (!dataval.isStr()) { throw JSONRPCError(RPC_TYPE_ERROR, "Missing data String key for proposal"); } CBlock block; if (!DecodeHexBlk(block, dataval.get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed"); } const BlockHash hash = block.GetHash(); const CBlockIndex *pindex = LookupBlockIndex(hash); if (pindex) { if (pindex->IsValid(BlockValidity::SCRIPTS)) { return "duplicate"; } if (pindex->nStatus.isInvalid()) { return "duplicate-invalid"; } return "duplicate-inconclusive"; } CBlockIndex *const pindexPrev = ::ChainActive().Tip(); // TestBlockValidity only supports blocks built on the current Tip if (block.hashPrevBlock != pindexPrev->GetBlockHash()) { return "inconclusive-not-best-prevblk"; } CValidationState state; TestBlockValidity(state, config.GetChainParams(), block, pindexPrev, BlockValidationOptions(config) .withCheckPoW(false) .withCheckMerkleRoot(true)); return BIP22ValidationResult(config, state); } } if (strMode != "template") { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode"); } if (!g_connman) { throw JSONRPCError( RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } if (g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) { throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Bitcoin is not connected!"); } if (IsInitialBlockDownload()) { throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Bitcoin is downloading blocks..."); } static unsigned int nTransactionsUpdatedLast; if (!lpval.isNull()) { // Wait to respond until either the best block changes, OR a minute has // passed and there are more transactions uint256 hashWatchedChain; std::chrono::steady_clock::time_point checktxtime; unsigned int nTransactionsUpdatedLastLP; if (lpval.isStr()) { // Format: std::string lpstr = lpval.get_str(); hashWatchedChain = ParseHashV(lpstr.substr(0, 64), "longpollid"); nTransactionsUpdatedLastLP = atoi64(lpstr.substr(64)); } else { // NOTE: Spec does not specify behaviour for non-string longpollid, // but this makes testing easier hashWatchedChain = ::ChainActive().Tip()->GetBlockHash(); nTransactionsUpdatedLastLP = nTransactionsUpdatedLast; } // Release the wallet and main lock while waiting LEAVE_CRITICAL_SECTION(cs_main); { checktxtime = std::chrono::steady_clock::now() + std::chrono::minutes(1); WAIT_LOCK(g_best_block_mutex, lock); while (g_best_block == hashWatchedChain && IsRPCRunning()) { if (g_best_block_cv.wait_until(lock, checktxtime) == std::cv_status::timeout) { // Timeout: Check transactions for update if (g_mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP) { break; } checktxtime += std::chrono::seconds(10); } } } ENTER_CRITICAL_SECTION(cs_main); if (!IsRPCRunning()) { throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down"); } // TODO: Maybe recheck connections/IBD and (if something wrong) send an // expires-immediately template to stop miners? } // Update block static CBlockIndex *pindexPrev; static int64_t nStart; static std::unique_ptr pblocktemplate; if (pindexPrev != ::ChainActive().Tip() || (g_mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5)) { // Clear pindexPrev so future calls make a new block, despite any // failures from here on pindexPrev = nullptr; // Store the pindexBest used before CreateNewBlock, to avoid races nTransactionsUpdatedLast = g_mempool.GetTransactionsUpdated(); CBlockIndex *pindexPrevNew = ::ChainActive().Tip(); nStart = GetTime(); // Create new block CScript scriptDummy = CScript() << OP_TRUE; pblocktemplate = BlockAssembler(config, g_mempool).CreateNewBlock(scriptDummy); if (!pblocktemplate) { throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); } // Need to update only after we know CreateNewBlock succeeded pindexPrev = pindexPrevNew; } assert(pindexPrev); // pointer for convenience CBlock *pblock = &pblocktemplate->block; // Update nTime UpdateTime(pblock, config.GetChainParams().GetConsensus(), pindexPrev); pblock->nNonce = 0; UniValue aCaps(UniValue::VARR); aCaps.push_back("proposal"); UniValue transactions(UniValue::VARR); int index_in_template = 0; for (const auto &it : pblock->vtx) { const CTransaction &tx = *it; uint256 txId = tx.GetId(); if (tx.IsCoinBase()) { index_in_template++; continue; } UniValue entry(UniValue::VOBJ); entry.pushKV("data", EncodeHexTx(tx)); entry.pushKV("txid", txId.GetHex()); entry.pushKV("hash", tx.GetHash().GetHex()); entry.pushKV("fee", pblocktemplate->entries[index_in_template].fees / SATOSHI); int64_t nTxSigOps = pblocktemplate->entries[index_in_template].sigOpCount; entry.pushKV("sigops", nTxSigOps); transactions.push_back(entry); index_in_template++; } UniValue aux(UniValue::VOBJ); aux.pushKV("flags", HexStr(COINBASE_FLAGS.begin(), COINBASE_FLAGS.end())); arith_uint256 hashTarget = arith_uint256().SetCompact(pblock->nBits); UniValue aMutable(UniValue::VARR); aMutable.push_back("time"); aMutable.push_back("transactions"); aMutable.push_back("prevblock"); UniValue result(UniValue::VOBJ); result.pushKV("capabilities", aCaps); result.pushKV("version", pblock->nVersion); result.pushKV("previousblockhash", pblock->hashPrevBlock.GetHex()); result.pushKV("transactions", transactions); result.pushKV("coinbaseaux", aux); result.pushKV("coinbasevalue", int64_t(pblock->vtx[0]->vout[0].nValue / SATOSHI)); result.pushKV("longpollid", ::ChainActive().Tip()->GetBlockHash().GetHex() + i64tostr(nTransactionsUpdatedLast)); result.pushKV("target", hashTarget.GetHex()); result.pushKV("mintime", int64_t(pindexPrev->GetMedianTimePast()) + 1); result.pushKV("mutable", aMutable); result.pushKV("noncerange", "00000000ffffffff"); // FIXME: Allow for mining block greater than 1M. result.pushKV("sigoplimit", GetMaxBlockSigOpsCount(DEFAULT_MAX_BLOCK_SIZE)); result.pushKV("sizelimit", DEFAULT_MAX_BLOCK_SIZE); result.pushKV("curtime", pblock->GetBlockTime()); result.pushKV("bits", strprintf("%08x", pblock->nBits)); result.pushKV("height", int64_t(pindexPrev->nHeight) + 1); return result; } class submitblock_StateCatcher : public CValidationInterface { public: uint256 hash; bool found; CValidationState state; explicit submitblock_StateCatcher(const uint256 &hashIn) : hash(hashIn), found(false), state() {} protected: void BlockChecked(const CBlock &block, const CValidationState &stateIn) override { if (block.GetHash() != hash) { return; } found = true; state = stateIn; } }; static UniValue submitblock(const Config &config, const JSONRPCRequest &request) { // We allow 2 arguments for compliance with BIP22. Argument 2 is ignored. if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( RPCHelpMan{"submitblock", "\nAttempts to submit new block to network.\n" "See https://en.bitcoin.it/wiki/BIP_0022 for full " "specification.\n", { - {"hexdata", RPCArg::Type::STR_HEX, false}, - {"dummy", RPCArg::Type::STR, true}, + {"hexdata", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", + "the hex-encoded block data to submit"}, + {"dummy", RPCArg::Type::STR, /* opt */ true, + /* default_val */ "", + "dummy value, for compatibility with BIP22. This " + "value is ignored."}, }} - .ToString() + - "\nArguments\n" - "1. \"hexdata\" (string, required) the hex-encoded block " - "data to submit\n" - "2. \"dummy\" (optional) dummy value, for compatibility " - "with BIP22. This value is ignored.\n" + .ToStringWithArgs() + "\nResult:\n" "\nExamples:\n" + HelpExampleCli("submitblock", "\"mydata\"") + HelpExampleRpc("submitblock", "\"mydata\"")); } std::shared_ptr blockptr = std::make_shared(); CBlock &block = *blockptr; if (!DecodeHexBlk(block, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed"); } if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block does not start with a coinbase"); } const BlockHash hash = block.GetHash(); { LOCK(cs_main); const CBlockIndex *pindex = LookupBlockIndex(hash); if (pindex) { if (pindex->IsValid(BlockValidity::SCRIPTS)) { return "duplicate"; } if (pindex->nStatus.isInvalid()) { return "duplicate-invalid"; } } } bool new_block; submitblock_StateCatcher sc(block.GetHash()); RegisterValidationInterface(&sc); bool accepted = ProcessNewBlock(config, blockptr, /* fForceProcessing */ true, /* fNewBlock */ &new_block); // We are only interested in BlockChecked which will have been dispatched // in-thread, so no need to sync before unregistering. UnregisterValidationInterface(&sc); // Sync to ensure that the catcher's slots aren't executing when it goes out // of scope and is deleted. SyncWithValidationInterfaceQueue(); if (!new_block && accepted) { return "duplicate"; } if (!sc.found) { return "inconclusive"; } return BIP22ValidationResult(config, sc.state); } static UniValue submitheader(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( - RPCHelpMan{"submitheader", - "\nDecode the given hexdata as a header and submit it " - "as a candidate chain tip if valid." - "\nThrows when the header is invalid.\n", - { - {"hexdata", RPCArg::Type::STR_HEX, false}, - }} - .ToString() + - "\nArguments\n" - "1. \"hexdata\" (string, required) the " - "hex-encoded block header data\n" + RPCHelpMan{ + "submitheader", + "\nDecode the given hexdata as a header and submit it as a " + "candidate chain tip if valid." + "\nThrows when the header is invalid.\n", + { + {"hexdata", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", "the hex-encoded block header data"}, + }} + .ToStringWithArgs() + "\nResult:\n" "None" "\nExamples:\n" + HelpExampleCli("submitheader", "\"aabbcc\"") + HelpExampleRpc("submitheader", "\"aabbcc\"")); } CBlockHeader h; if (!DecodeHexBlockHeader(h, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block header decode failed"); } { LOCK(cs_main); if (!LookupBlockIndex(h.hashPrevBlock)) { throw JSONRPCError(RPC_VERIFY_ERROR, "Must submit previous header (" + h.hashPrevBlock.GetHex() + ") first"); } } CValidationState state; ProcessNewBlockHeaders(config, {h}, state, /* ppindex */ nullptr, /* first_invalid */ nullptr); if (state.IsValid()) { return NullUniValue; } if (state.IsError()) { throw JSONRPCError(RPC_VERIFY_ERROR, FormatStateMessage(state)); } throw JSONRPCError(RPC_VERIFY_ERROR, state.GetRejectReason()); } static UniValue estimatefee(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() > 0) { throw std::runtime_error( RPCHelpMan{"estimatefee", "\nEstimates the approximate fee per kilobyte needed " "for a transaction\n", {}} - .ToString() + + .ToStringWithArgs() + "\nResult:\n" "n (numeric) estimated fee-per-kilobyte\n" "\nExample:\n" + HelpExampleCli("estimatefee", "")); } return ValueFromAmount(g_mempool.estimateFee().GetFeePerK()); } // clang-format off static const ContextFreeRPCCommand commands[] = { // category name actor (function) argNames // ---------- ------------------------ ---------------------- ---------- {"mining", "getnetworkhashps", getnetworkhashps, {"nblocks", "height"}}, {"mining", "getmininginfo", getmininginfo, {}}, {"mining", "prioritisetransaction", prioritisetransaction, {"txid", "dummy", "fee_delta"}}, {"mining", "getblocktemplate", getblocktemplate, {"template_request"}}, {"mining", "submitblock", submitblock, {"hexdata", "dummy"}}, {"mining", "submitheader", submitheader, {"hexdata"}}, {"generating", "generatetoaddress", generatetoaddress, {"nblocks", "address", "maxtries"}}, {"util", "estimatefee", estimatefee, {"nblocks"}}, }; // clang-format on void RegisterMiningRPCCommands(CRPCTable &t) { for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) { t.appendCommand(commands[vcidx].name, &commands[vcidx]); } } diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 848db6752..cb12afcbe 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -1,559 +1,553 @@ // 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. #include #include #include #include #include #include #include #include #include InitInterfaces *g_rpc_interfaces = nullptr; // Converts a hex string to a public key if possible CPubKey HexToPubKey(const std::string &hex_in) { if (!IsHex(hex_in)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + hex_in); } CPubKey vchPubKey(ParseHex(hex_in)); if (!vchPubKey.IsFullyValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + hex_in); } return vchPubKey; } // Retrieves a public key for an address from the given CKeyStore CPubKey AddrToPubKey(const CChainParams &chainparams, CKeyStore *const keystore, const std::string &addr_in) { CTxDestination dest = DecodeDestination(addr_in, chainparams); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address: " + addr_in); } CKeyID key = GetKeyForDestination(*keystore, dest); if (key.IsNull()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("%s does not refer to a key", addr_in)); } CPubKey vchPubKey; if (!keystore->GetPubKey(key, vchPubKey)) { throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, strprintf("no full public key for address %s", addr_in)); } if (!vchPubKey.IsFullyValid()) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet contains an invalid public key"); } return vchPubKey; } // Creates a multisig redeemscript from a given list of public keys and number // required. CScript CreateMultisigRedeemscript(const int required, const std::vector &pubkeys) { // Gather public keys if (required < 1) { throw JSONRPCError( RPC_INVALID_PARAMETER, "a multisignature address must require at least one key to redeem"); } if ((int)pubkeys.size() < required) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("not enough keys supplied (got %u keys, " "but need at least %d to redeem)", pubkeys.size(), required)); } if (pubkeys.size() > 16) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Number of keys involved in the multisignature " "address creation > 16\nReduce the number"); } CScript result = GetScriptForMultisig(required, pubkeys); if (result.size() > MAX_SCRIPT_ELEMENT_SIZE) { throw JSONRPCError( RPC_INVALID_PARAMETER, (strprintf("redeemScript exceeds size limit: %d > %d", result.size(), MAX_SCRIPT_ELEMENT_SIZE))); } return result; } class DescribeAddressVisitor : public boost::static_visitor { public: explicit DescribeAddressVisitor() {} UniValue operator()(const CNoDestination &dest) const { return UniValue(UniValue::VOBJ); } UniValue operator()(const CKeyID &keyID) const { UniValue obj(UniValue::VOBJ); obj.pushKV("isscript", false); return obj; } UniValue operator()(const CScriptID &scriptID) const { UniValue obj(UniValue::VOBJ); obj.pushKV("isscript", true); return obj; } }; UniValue DescribeAddress(const CTxDestination &dest) { return boost::apply_visitor(DescribeAddressVisitor(), dest); } struct Section { Section(const std::string &left, const std::string &right) : m_left{left}, m_right{right} {} const std::string m_left; const std::string m_right; }; struct Sections { std::vector
m_sections; size_t m_max_pad{0}; void PushSection(const Section &s) { m_max_pad = std::max(m_max_pad, s.m_left.size()); m_sections.push_back(s); } enum class OuterType { ARR, OBJ, // Only set on first recursion NAMED_ARG, }; void Push(const RPCArg &arg, const size_t current_indent = 5, const OuterType outer_type = OuterType::NAMED_ARG) { const auto indent = std::string(current_indent, ' '); const auto indent_next = std::string(current_indent + 2, ' '); switch (arg.m_type) { case RPCArg::Type::STR_HEX: case RPCArg::Type::STR: case RPCArg::Type::NUM: case RPCArg::Type::AMOUNT: case RPCArg::Type::BOOL: { // Nothing more to do for non-recursive types on first recursion if (outer_type == OuterType::NAMED_ARG) { return; } auto left = indent; if (arg.m_type_str.size() != 0 && outer_type == OuterType::OBJ) { left += "\"" + arg.m_name + "\": " + arg.m_type_str.at(0); } else { left += outer_type == OuterType::OBJ ? arg.ToStringObj(/* oneline */ false) : arg.ToString(/* oneline */ false); } left += ","; PushSection({left, arg.ToDescriptionString( /* implicitly_required */ outer_type == OuterType::ARR)}); break; } case RPCArg::Type::OBJ: case RPCArg::Type::OBJ_USER_KEYS: { const auto right = outer_type == OuterType::NAMED_ARG ? "" : arg.ToDescriptionString( /* implicitly_required */ outer_type == OuterType::ARR); PushSection({indent + "{", right}); for (const auto &arg_inner : arg.m_inner) { Push(arg_inner, current_indent + 2, OuterType::OBJ); } if (arg.m_type != RPCArg::Type::OBJ) { PushSection({indent_next + "...", ""}); } PushSection( {indent + "}" + (outer_type != OuterType::NAMED_ARG ? "," : ""), ""}); break; } case RPCArg::Type::ARR: { auto left = indent; left += outer_type == OuterType::OBJ ? "\"" + arg.m_name + "\": " : ""; left += "["; const auto right = outer_type == OuterType::NAMED_ARG ? "" : arg.ToDescriptionString( /* implicitly_required */ outer_type == OuterType::ARR); PushSection({left, right}); for (const auto &arg_inner : arg.m_inner) { Push(arg_inner, current_indent + 2, OuterType::ARR); } PushSection({indent_next + "...", ""}); PushSection( {indent + "]" + (outer_type != OuterType::NAMED_ARG ? "," : ""), ""}); break; } // no default case, so the compiler can warn about missing cases } } std::string ToString() const { std::string ret; const size_t pad = m_max_pad + 4; for (const auto &s : m_sections) { if (s.m_right.empty()) { ret += s.m_left; ret += "\n"; continue; } std::string left = s.m_left; left.resize(pad, ' '); ret += left; // Properly pad after newlines std::string right; size_t begin = 0; size_t new_line_pos = s.m_right.find_first_of('\n'); while (true) { right += s.m_right.substr(begin, new_line_pos - begin); if (new_line_pos == std::string::npos) { // No new line break; } right += "\n" + std::string(pad, ' '); begin = s.m_right.find_first_not_of(' ', new_line_pos + 1); if (begin == std::string::npos) { break; // Empty line } new_line_pos = s.m_right.find_first_of('\n', begin + 1); } ret += right; ret += "\n"; } return ret; } }; // Remove once PR14796 backport is completed std::string RPCHelpMan::ToString() const { std::string ret; ret += m_name; bool is_optional{false}; for (const auto &arg : m_args) { ret += " "; if (arg.m_optional) { if (!is_optional) { ret += "( "; } is_optional = true; } else { // Currently we still support unnamed arguments, so any argument // following an optional argument must also be optional If support // for positional arguments is deprecated in the future, remove this // line assert(!is_optional); } ret += arg.ToString(); } if (is_optional) { ret += " )"; } ret += "\n"; ret += m_description; return ret; } // Rename to ToString() once PR14796 is completed std::string RPCHelpMan::ToStringWithArgs() const { std::string ret; // Oneline summary ret += m_name; - bool is_optional{false}; + bool was_optional{false}; for (const auto &arg : m_args) { ret += " "; if (arg.m_optional) { - if (!is_optional) { + if (!was_optional) { ret += "( "; } - is_optional = true; + was_optional = true; } else { - // Currently we still support unnamed arguments, so any argument - // following an optional argument must also be optional If support - // for positional arguments is deprecated in the future, remove this - // line - assert(!is_optional); + if (was_optional) { + ret += ") "; + } + was_optional = false; } ret += arg.ToString(/* oneline */ true); } - if (is_optional) { + if (was_optional) { ret += " )"; } ret += "\n"; // Description ret += m_description; // Arguments Sections sections; for (size_t i{0}; i < m_args.size(); ++i) { const auto &arg = m_args.at(i); if (i == 0) { ret += "\nArguments:\n"; } // Push named argument name and description - const auto str_wrapper = (arg.m_type == RPCArg::Type::STR || - arg.m_type == RPCArg::Type::STR_HEX) - ? "\"" - : ""; sections.m_sections.emplace_back(std::to_string(i + 1) + ". " + - str_wrapper + arg.m_name + - str_wrapper, + arg.m_name, arg.ToDescriptionString()); sections.m_max_pad = std::max(sections.m_max_pad, sections.m_sections.back().m_left.size()); // Recursively push nested args sections.Push(arg); } ret += sections.ToString(); return ret; } std::string RPCArg::ToDescriptionString(const bool implicitly_required) const { std::string ret; ret += "("; if (m_type_str.size() != 0) { ret += m_type_str.at(1); } else { switch (m_type) { case Type::STR_HEX: case Type::STR: { ret += "string"; break; } case Type::NUM: { ret += "numeric"; break; } case Type::AMOUNT: { ret += "numeric or string"; break; } case Type::BOOL: { ret += "boolean"; break; } case Type::OBJ: case Type::OBJ_USER_KEYS: { ret += "json object"; break; } case Type::ARR: { ret += "json array"; break; } // no default case, so the compiler can warn about missing cases } } if (!implicitly_required) { ret += ", "; if (m_optional) { ret += "optional"; if (!m_default_value.empty()) { ret += ", default=" + m_default_value; } else { // TODO enable this assert, when all optional parameters have // their default value documented // assert(false); } } else { ret += "required"; // Default value is ignored, and must not be present assert(m_default_value.empty()); } } ret += ")"; ret += m_description.empty() ? "" : " " + m_description; return ret; } // Remove once PR14796 backport is completed std::string RPCArg::ToStringObj() const { std::string res = "\"" + m_name + "\":"; switch (m_type) { case Type::STR: return res + "\"str\""; case Type::STR_HEX: return res + "\"hex\""; case Type::NUM: return res + "n"; case Type::AMOUNT: return res + "amount"; case Type::BOOL: return res + "bool"; case Type::ARR: res += "["; for (const auto &i : m_inner) { res += i.ToString() + ","; } return res + "...]"; case Type::OBJ: case Type::OBJ_USER_KEYS: // Currently unused, so avoid writing dead code assert(false); // no default case, so the compiler can warn about missing cases } assert(false); } std::string RPCArg::ToStringObj(const bool oneline) const { std::string res; res += "\""; res += m_name; if (oneline) { res += "\":"; } else { res += "\": "; } switch (m_type) { case Type::STR: return res + "\"str\""; case Type::STR_HEX: return res + "\"hex\""; case Type::NUM: return res + "n"; case Type::AMOUNT: return res + "amount"; case Type::BOOL: return res + "bool"; case Type::ARR: res += "["; for (const auto &i : m_inner) { res += i.ToString(oneline) + ","; } return res + "...]"; case Type::OBJ: case Type::OBJ_USER_KEYS: // Currently unused, so avoid writing dead code assert(false); // no default case, so the compiler can warn about missing cases } assert(false); } // Remove once PR14796 backport is completed std::string RPCArg::ToString() const { if (!m_oneline_description.empty()) { return m_oneline_description; } switch (m_type) { case Type::STR_HEX: case Type::STR: { return "\"" + m_name + "\""; } case Type::NUM: case Type::AMOUNT: case Type::BOOL: { return m_name; } case Type::OBJ: case Type::OBJ_USER_KEYS: { std::string res; for (size_t i = 0; i < m_inner.size();) { res += m_inner[i].ToStringObj(); if (++i < m_inner.size()) { res += ","; } } if (m_type == Type::OBJ) { return "{" + res + "}"; } else { return "{" + res + ",...}"; } } case Type::ARR: { std::string res; for (const auto &i : m_inner) { res += i.ToString() + ","; } return "[" + res + "...]"; } // no default case, so the compiler can warn about missing cases } assert(false); } std::string RPCArg::ToString(const bool oneline) const { if (oneline && !m_oneline_description.empty()) { return m_oneline_description; } switch (m_type) { case Type::STR_HEX: case Type::STR: { return "\"" + m_name + "\""; } case Type::NUM: case Type::AMOUNT: case Type::BOOL: { return m_name; } case Type::OBJ: case Type::OBJ_USER_KEYS: { std::string res; for (size_t i = 0; i < m_inner.size();) { res += m_inner[i].ToStringObj(oneline); if (++i < m_inner.size()) { res += ","; } } if (m_type == Type::OBJ) { return "{" + res + "}"; } else { return "{" + res + ",...}"; } } case Type::ARR: { std::string res; for (const auto &i : m_inner) { res += i.ToString(oneline) + ","; } return "[" + res + "...]"; } // no default case, so the compiler can warn about missing cases } assert(false); }