diff options
Diffstat (limited to 'src/rpc')
-rw-r--r-- | src/rpc/blockchain.cpp | 101 | ||||
-rw-r--r-- | src/rpc/blockchain.h | 6 | ||||
-rw-r--r-- | src/rpc/client.cpp | 67 | ||||
-rw-r--r-- | src/rpc/client.h | 5 | ||||
-rw-r--r-- | src/rpc/external_signer.cpp | 2 | ||||
-rw-r--r-- | src/rpc/fees.cpp | 6 | ||||
-rw-r--r-- | src/rpc/mempool.cpp | 75 | ||||
-rw-r--r-- | src/rpc/mining.cpp | 43 | ||||
-rw-r--r-- | src/rpc/net.cpp | 103 | ||||
-rw-r--r-- | src/rpc/node.cpp | 36 | ||||
-rw-r--r-- | src/rpc/output_script.cpp | 7 | ||||
-rw-r--r-- | src/rpc/rawtransaction.cpp | 178 | ||||
-rw-r--r-- | src/rpc/rawtransaction_util.cpp | 45 | ||||
-rw-r--r-- | src/rpc/rawtransaction_util.h | 9 | ||||
-rw-r--r-- | src/rpc/register.h | 2 | ||||
-rw-r--r-- | src/rpc/request.cpp | 11 | ||||
-rw-r--r-- | src/rpc/server.cpp | 11 | ||||
-rw-r--r-- | src/rpc/server_util.cpp | 16 | ||||
-rw-r--r-- | src/rpc/server_util.h | 5 | ||||
-rw-r--r-- | src/rpc/txoutproof.cpp | 6 | ||||
-rw-r--r-- | src/rpc/util.cpp | 238 | ||||
-rw-r--r-- | src/rpc/util.h | 117 |
22 files changed, 669 insertions, 420 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 784fb64d36..fb22321d90 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -15,7 +15,6 @@ #include <core_io.h> #include <deploymentinfo.h> #include <deploymentstatus.h> -#include <fs.h> #include <hash.h> #include <index/blockfilterindex.h> #include <index/coinstatsindex.h> @@ -25,6 +24,7 @@ #include <net_processing.h> #include <node/blockstorage.h> #include <node/context.h> +#include <node/transaction.h> #include <node/utxo_snapshot.h> #include <primitives/transaction.h> #include <rpc/server.h> @@ -38,6 +38,7 @@ #include <undo.h> #include <univalue.h> #include <util/check.h> +#include <util/fs.h> #include <util/strencodings.h> #include <util/system.h> #include <util/translation.h> @@ -431,7 +432,8 @@ static RPCHelpMan getblockfrompeer() "We must have the header for this block, e.g. using submitheader.\n" "Subsequent calls for the same block and a new peer will cause the response from the previous peer to be ignored.\n" "Peers generally ignore requests for a stale block that they never fully verified, or one that is more than a month old.\n" - "When a peer does not respond with a block, we will disconnect.\n\n" + "When a peer does not respond with a block, we will disconnect.\n" + "Note: The block could be re-pruned as soon as it is received.\n\n" "Returns an empty JSON object if the request was successfully scheduled.", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash to try to fetch"}, @@ -444,11 +446,6 @@ static RPCHelpMan getblockfrompeer() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, { - UniValue::VSTR, // blockhash - UniValue::VNUM, // peer_id - }); - const NodeContext& node = EnsureAnyNodeContext(request.context); ChainstateManager& chainman = EnsureChainman(node); PeerManager& peerman = EnsurePeerman(node); @@ -464,7 +461,7 @@ static RPCHelpMan getblockfrompeer() // Fetching blocks before the node has syncing past their height can prevent block files from // being pruned, so we avoid it if the node is in prune mode. - if (node::fPruneMode && index->nHeight > WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()->nHeight)) { + if (chainman.m_blockman.IsPruneMode() && index->nHeight > WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()->nHeight)) { throw JSONRPCError(RPC_MISC_ERROR, "In prune mode, only blocks that the node has already synced previously can be fetched from a peer"); } @@ -569,7 +566,7 @@ static RPCHelpMan getblockheader() if (!fVerbose) { - CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION); + DataStream ssBlock{}; ssBlock << pblockindex->GetBlockHeader(); std::string strHex = HexStr(ssBlock); return strHex; @@ -654,7 +651,8 @@ static RPCHelpMan getblock() "If verbosity is 3, returns an Object with information about block <hash> and information about each transaction, including prevout information for inputs (only for unpruned blocks in the current best chain).\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"}, - {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{1}, "0 for hex-encoded data, 1 for a JSON object, 2 for JSON object with transaction data, and 3 for JSON object with transaction data including prevout information for inputs"}, + {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{1}, "0 for hex-encoded data, 1 for a JSON object, 2 for JSON object with transaction data, and 3 for JSON object with transaction data including prevout information for inputs", + RPCArgOptions{.skip_type_check = true}}, }, { RPCResult{"for verbosity = 0", @@ -778,10 +776,11 @@ static RPCHelpMan pruneblockchain() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - if (!node::fPruneMode) + ChainstateManager& chainman = EnsureAnyChainman(request.context); + if (!chainman.m_blockman.IsPruneMode()) { throw JSONRPCError(RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode."); + } - ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); Chainstate& active_chainstate = chainman.ActiveChainstate(); CChain& active_chain = active_chainstate.m_chain; @@ -872,7 +871,11 @@ static RPCHelpMan gettxoutsetinfo() "Note this call may take some time if you are not using coinstatsindex.\n", { {"hash_type", RPCArg::Type::STR, RPCArg::Default{"hash_serialized_2"}, "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm), 'muhash', 'none'."}, - {"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).", RPCArgOptions{.type_str={"", "string or numeric"}}}, + {"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).", + RPCArgOptions{ + .skip_type_check = true, + .type_str = {"", "string or numeric"}, + }}, {"use_index", RPCArg::Type::BOOL, RPCArg::Default{true}, "Use coinstatsindex, if available."}, }, RPCResult{ @@ -1108,7 +1111,7 @@ static RPCHelpMan verifychain() {"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS)}, "The number of blocks to check."}, }, RPCResult{ - RPCResult::Type::BOOL, "", "Verified or not"}, + RPCResult::Type::BOOL, "", "Verification finished successfully. If false, check debug.log for reason."}, RPCExamples{ HelpExampleCli("verifychain", "") + HelpExampleRpc("verifychain", "") @@ -1123,7 +1126,7 @@ static RPCHelpMan verifychain() Chainstate& active_chainstate = chainman.ActiveChainstate(); return CVerifyDB().VerifyDB( - active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth); + active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth) == VerifyDBResult::SUCCESS; }, }; } @@ -1246,7 +1249,6 @@ RPCHelpMan getblockchaininfo() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const ArgsManager& args{EnsureAnyArgsman(request.context)}; ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); Chainstate& active_chainstate = chainman.ActiveChainstate(); @@ -1265,15 +1267,14 @@ RPCHelpMan getblockchaininfo() obj.pushKV("initialblockdownload", active_chainstate.IsInitialBlockDownload()); obj.pushKV("chainwork", tip.nChainWork.GetHex()); obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage()); - obj.pushKV("pruned", node::fPruneMode); - if (node::fPruneMode) { + obj.pushKV("pruned", chainman.m_blockman.IsPruneMode()); + if (chainman.m_blockman.IsPruneMode()) { obj.pushKV("pruneheight", chainman.m_blockman.GetFirstStoredBlock(tip)->nHeight); - // if 0, execution bypasses the whole if block. - bool automatic_pruning{args.GetIntArg("-prune", 0) != 1}; + const bool automatic_pruning{chainman.m_blockman.GetPruneTarget() != BlockManager::PRUNE_TARGET_MANUAL}; obj.pushKV("automatic_pruning", automatic_pruning); if (automatic_pruning) { - obj.pushKV("prune_target_size", node::nPruneTarget); + obj.pushKV("prune_target_size", chainman.m_blockman.GetPruneTarget()); } } @@ -1742,7 +1743,11 @@ static RPCHelpMan getblockstats() "\nCompute per block statistics for a given window. All amounts are in satoshis.\n" "It won't work for some heights with pruning.\n", { - {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block", RPCArgOptions{.type_str={"", "string or numeric"}}}, + {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block", + RPCArgOptions{ + .skip_type_check = true, + .type_str = {"", "string or numeric"}, + }}, {"stats", RPCArg::Type::ARR, RPCArg::DefaultHint{"all values"}, "Values to plot (see result below)", { {"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"}, @@ -2100,6 +2105,10 @@ static RPCHelpMan scantxoutset() " combo(<pubkey>) P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH outputs for the given pubkey\n" " pkh(<pubkey>) P2PKH outputs for the given pubkey\n" " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n" + " tr(<pubkey>) P2TR\n" + " tr(<pubkey>,{pk(<pubkey>)}) P2TR with single fallback pubkey in tapscript\n" + " rawtr(<pubkey>) P2TR with the specified key as output key rather than inner\n" + " wsh(and_v(v:pk(<pubkey>),after(2))) P2WSH miniscript with mandatory pubkey and a timelock\n" "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n" "or more path elements separated by \"/\", and optionally ending in \"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n" "unhardened or hardened child keys.\n" @@ -2144,8 +2153,6 @@ static RPCHelpMan scantxoutset() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}); - UniValue result(UniValue::VOBJ); if (request.params[0].get_str() == "status") { CoinsViewScanReserver reserver; @@ -2268,17 +2275,47 @@ public: } }; +static bool CheckBlockFilterMatches(BlockManager& blockman, const CBlockIndex& blockindex, const GCSFilter::ElementSet& needles) +{ + const CBlock block{GetBlockChecked(blockman, &blockindex)}; + const CBlockUndo block_undo{GetUndoChecked(blockman, &blockindex)}; + + // Check if any of the outputs match the scriptPubKey + for (const auto& tx : block.vtx) { + if (std::any_of(tx->vout.cbegin(), tx->vout.cend(), [&](const auto& txout) { + return needles.count(std::vector<unsigned char>(txout.scriptPubKey.begin(), txout.scriptPubKey.end())) != 0; + })) { + return true; + } + } + // Check if any of the inputs match the scriptPubKey + for (const auto& txundo : block_undo.vtxundo) { + if (std::any_of(txundo.vprevout.cbegin(), txundo.vprevout.cend(), [&](const auto& coin) { + return needles.count(std::vector<unsigned char>(coin.out.scriptPubKey.begin(), coin.out.scriptPubKey.end())) != 0; + })) { + return true; + } + } + + return false; +} + static RPCHelpMan scanblocks() { return RPCHelpMan{"scanblocks", - "\nReturn relevant blockhashes for given descriptors.\n" + "\nReturn relevant blockhashes for given descriptors (requires blockfilterindex).\n" "This call may take several minutes. Make sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)", { scan_action_arg_desc, scan_objects_arg_desc, RPCArg{"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "Height to start to scan from"}, RPCArg{"stop_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"chain tip"}, "Height to stop to scan"}, - RPCArg{"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"} + RPCArg{"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"}, + RPCArg{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", + { + {"filter_false_positives", RPCArg::Type::BOOL, RPCArg::Default{false}, "Filter false positives (slower and may fail on pruned nodes). Otherwise they may occur at a rate of 1/M"}, + }, + RPCArgOptions{.oneline_description="\"options\""}}, }, { scan_result_status_none, @@ -2338,6 +2375,9 @@ static RPCHelpMan scanblocks() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype"); } + UniValue options{request.params[5].isNull() ? UniValue::VOBJ : request.params[5]}; + bool filter_false_positives{options.exists("filter_false_positives") ? options["filter_false_positives"].get_bool() : false}; + BlockFilterIndex* index = GetBlockFilterIndex(filtertype); if (!index) { throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name); @@ -2408,6 +2448,15 @@ static RPCHelpMan scanblocks() for (const BlockFilter& filter : filters) { // compare the elements-set with each filter if (filter.GetFilter().MatchAny(needle_set)) { + if (filter_false_positives) { + // Double check the filter matches by scanning the block + const CBlockIndex& blockindex = *CHECK_NONFATAL(WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(filter.GetBlockHash()))); + + if (!CheckBlockFilterMatches(chainman.m_blockman, blockindex, needle_set)) { + continue; + } + } + blocks.push_back(filter.GetBlockHash().GetHex()); LogPrint(BCLog::RPC, "scanblocks: found match in %s\n", filter.GetBlockHash().GetHex()); } diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index 6cdb5fa48b..0a86085db0 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021 The Bitcoin Core developers +// Copyright (c) 2017-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -7,17 +7,15 @@ #include <consensus/amount.h> #include <core_io.h> -#include <fs.h> #include <streams.h> #include <sync.h> +#include <util/fs.h> #include <validation.h> #include <any> #include <stdint.h> #include <vector> -extern RecursiveMutex cs_main; - class CBlock; class CBlockIndex; class Chainstate; diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index b3434b80c7..4459dd71aa 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -1,13 +1,16 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2021 The Bitcoin Core developers +// Copyright (c) 2009-2022 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 <rpc/client.h> +#include <tinyformat.h> #include <util/system.h> #include <set> #include <stdint.h> +#include <string> +#include <string_view> class CRPCConvertParam { @@ -34,6 +37,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "generatetodescriptor", 0, "num_blocks" }, { "generatetodescriptor", 2, "maxtries" }, { "generateblock", 1, "transactions" }, + { "generateblock", 2, "submit" }, { "getnetworkhashps", 0, "nblocks" }, { "getnetworkhashps", 1, "height" }, { "sendtoaddress", 1, "amount" }, @@ -86,6 +90,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "scanblocks", 1, "scanobjects" }, { "scanblocks", 2, "start_height" }, { "scanblocks", 3, "stop_height" }, + { "scanblocks", 5, "options" }, { "scantxoutset", 1, "scanobjects" }, { "addmultisigaddress", 0, "nrequired" }, { "addmultisigaddress", 1, "keys" }, @@ -102,6 +107,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getchaintxstats", 0, "nblocks" }, { "gettransaction", 1, "include_watchonly" }, { "gettransaction", 2, "verbose" }, + { "getrawtransaction", 1, "verbosity" }, { "getrawtransaction", 1, "verbose" }, { "createrawtransaction", 0, "inputs" }, { "createrawtransaction", 1, "outputs" }, @@ -112,6 +118,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "signrawtransactionwithkey", 2, "prevtxs" }, { "signrawtransactionwithwallet", 1, "prevtxs" }, { "sendrawtransaction", 1, "maxfeerate" }, + { "sendrawtransaction", 2, "maxburnamount" }, { "testmempoolaccept", 0, "rawtxs" }, { "testmempoolaccept", 1, "maxfeerate" }, { "submitpackage", 0, "package" }, @@ -225,11 +232,16 @@ private: public: CRPCConvertTable(); - bool convert(const std::string& method, int idx) { - return (members.count(std::make_pair(method, idx)) > 0); + /** Return arg_value as UniValue, and first parse it if it is a non-string parameter */ + UniValue ArgToUniValue(std::string_view arg_value, const std::string& method, int param_idx) + { + return members.count({method, param_idx}) > 0 ? ParseNonRFCJSONValue(arg_value) : arg_value; } - bool convert(const std::string& method, const std::string& name) { - return (membersByName.count(std::make_pair(method, name)) > 0); + + /** Return arg_value as UniValue, and first parse it if it is a non-string parameter */ + UniValue ArgToUniValue(std::string_view arg_value, const std::string& method, const std::string& param_name) + { + return membersByName.count({method, param_name}) > 0 ? ParseNonRFCJSONValue(arg_value) : arg_value; } }; @@ -246,13 +258,11 @@ static CRPCConvertTable rpcCvtTable; /** Non-RFC4627 JSON parser, accepts internal values (such as numbers, true, false, null) * as well as objects and arrays. */ -UniValue ParseNonRFCJSONValue(const std::string& strVal) +UniValue ParseNonRFCJSONValue(std::string_view raw) { - UniValue jVal; - if (!jVal.read(std::string("[")+strVal+std::string("]")) || - !jVal.isArray() || jVal.size()!=1) - throw std::runtime_error(std::string("Error parsing JSON: ") + strVal); - return jVal[0]; + UniValue parsed; + if (!parsed.read(raw)) throw std::runtime_error(tfm::format("Error parsing JSON: %s", raw)); + return parsed; } UniValue RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams) @@ -260,15 +270,8 @@ UniValue RPCConvertValues(const std::string &strMethod, const std::vector<std::s UniValue params(UniValue::VARR); for (unsigned int idx = 0; idx < strParams.size(); idx++) { - const std::string& strVal = strParams[idx]; - - if (!rpcCvtTable.convert(strMethod, idx)) { - // insert string value directly - params.push_back(strVal); - } else { - // parse string as JSON, insert bool/number/object/etc. value - params.push_back(ParseNonRFCJSONValue(strVal)); - } + std::string_view value{strParams[idx]}; + params.push_back(rpcCvtTable.ArgToUniValue(value, strMethod, idx)); } return params; @@ -279,27 +282,27 @@ UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<s UniValue params(UniValue::VOBJ); UniValue positional_args{UniValue::VARR}; - for (const std::string &s: strParams) { + for (std::string_view s: strParams) { size_t pos = s.find('='); if (pos == std::string::npos) { - positional_args.push_back(rpcCvtTable.convert(strMethod, positional_args.size()) ? ParseNonRFCJSONValue(s) : s); + positional_args.push_back(rpcCvtTable.ArgToUniValue(s, strMethod, positional_args.size())); continue; } - std::string name = s.substr(0, pos); - std::string value = s.substr(pos+1); + std::string name{s.substr(0, pos)}; + std::string_view value{s.substr(pos+1)}; - if (!rpcCvtTable.convert(strMethod, name)) { - // insert string value directly - params.pushKV(name, value); - } else { - // parse string as JSON, insert bool/number/object/etc. value - params.pushKV(name, ParseNonRFCJSONValue(value)); - } + // Intentionally overwrite earlier named values with later ones as a + // convenience for scripts and command line users that want to merge + // options. + params.pushKV(name, rpcCvtTable.ArgToUniValue(value, strMethod, name)); } if (!positional_args.empty()) { - params.pushKV("args", positional_args); + // Use __pushKV instead of pushKV to avoid overwriting an explicit + // "args" value with an implicit one. Let the RPC server handle the + // request as given. + params.__pushKV("args", positional_args); } return params; diff --git a/src/rpc/client.h b/src/rpc/client.h index b9fee5bbb3..3c5c4fc4d6 100644 --- a/src/rpc/client.h +++ b/src/rpc/client.h @@ -6,6 +6,9 @@ #ifndef BITCOIN_RPC_CLIENT_H #define BITCOIN_RPC_CLIENT_H +#include <string> +#include <string_view> + #include <univalue.h> /** Convert positional arguments to command-specific RPC representation */ @@ -17,6 +20,6 @@ UniValue RPCConvertNamedValues(const std::string& strMethod, const std::vector<s /** Non-RFC4627 JSON parser, accepts internal values (such as numbers, true, false, null) * as well as objects and arrays. */ -UniValue ParseNonRFCJSONValue(const std::string& strVal); +UniValue ParseNonRFCJSONValue(std::string_view raw); #endif // BITCOIN_RPC_CLIENT_H diff --git a/src/rpc/external_signer.cpp b/src/rpc/external_signer.cpp index 4de7fc4205..f5a6913572 100644 --- a/src/rpc/external_signer.cpp +++ b/src/rpc/external_signer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2021 The Bitcoin Core developers +// Copyright (c) 2018-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp index e50bf00473..62396d4c58 100644 --- a/src/rpc/fees.cpp +++ b/src/rpc/fees.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2021 The Bitcoin Core developers +// Copyright (c) 2009-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -63,8 +63,6 @@ static RPCHelpMan estimatesmartfee() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR}); - CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); const NodeContext& node = EnsureAnyNodeContext(request.context); const CTxMemPool& mempool = EnsureMemPool(node); @@ -155,8 +153,6 @@ static RPCHelpMan estimaterawfee() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true); - CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 7a0c361ae0..927b4ce1fc 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -9,7 +9,6 @@ #include <chainparams.h> #include <core_io.h> -#include <fs.h> #include <kernel/mempool_entry.h> #include <node/mempool_persist_args.h> #include <policy/rbf.h> @@ -18,11 +17,15 @@ #include <rpc/server.h> #include <rpc/server_util.h> #include <rpc/util.h> +#include <script/standard.h> #include <txmempool.h> #include <univalue.h> +#include <util/fs.h> #include <util/moneystr.h> #include <util/time.h> +#include <utility> + using kernel::DumpMempool; using node::DEFAULT_MAX_RAW_TX_FEE_RATE; @@ -42,7 +45,11 @@ static RPCHelpMan sendrawtransaction() {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"}, {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())}, "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + - "/kvB.\nSet to 0 to accept any fee rate.\n"}, + "/kvB.\nSet to 0 to accept any fee rate."}, + {"maxburnamount", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(0)}, + "Reject transactions with provably unspendable outputs (e.g. 'datacarrier' outputs that use the OP_RETURN opcode) greater than the specified value, expressed in " + CURRENCY_UNIT + ".\n" + "If burning funds through unspendable outputs is desired, increase this value.\n" + "This check is based on heuristics and does not guarantee spendability of outputs.\n"}, }, RPCResult{ RPCResult::Type::STR_HEX, "", "The transaction hash in hex" @@ -59,15 +66,19 @@ static RPCHelpMan sendrawtransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, { - UniValue::VSTR, - UniValueType(), // VNUM or VSTR, checked inside AmountFromValue() - }); + const CAmount max_burn_amount = request.params[2].isNull() ? 0 : AmountFromValue(request.params[2]); CMutableTransaction mtx; if (!DecodeHexTx(mtx, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input."); } + + for (const auto& out : mtx.vout) { + if((out.scriptPubKey.IsUnspendable() || !out.scriptPubKey.HasValidOps()) && out.nValue > max_burn_amount) { + throw JSONRPCTransactionError(TransactionError::MAX_BURN_EXCEEDED); + } + } + CTransactionRef tx(MakeTransactionRef(std::move(mtx))); const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ? @@ -124,6 +135,10 @@ static RPCHelpMan testmempoolaccept() {RPCResult::Type::OBJ, "fees", /*optional=*/true, "Transaction fees (only present if 'allowed' is true)", { {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT}, + {RPCResult::Type::STR_AMOUNT, "effective-feerate", /*optional=*/false, "the effective feerate in " + CURRENCY_UNIT + " per KvB. May differ from the base feerate if, for example, there are modified fees from prioritisetransaction or a package feerate was used."}, + {RPCResult::Type::ARR, "effective-includes", /*optional=*/false, "transactions whose fees and vsizes are included in effective-feerate.", + {RPCResult{RPCResult::Type::STR_HEX, "", "transaction wtxid in hex"}, + }}, }}, {RPCResult::Type::STR, "reject-reason", /*optional=*/true, "Rejection string (only present when 'allowed' is false)"}, }}, @@ -141,10 +156,6 @@ static RPCHelpMan testmempoolaccept() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, { - UniValue::VARR, - UniValueType(), // VNUM or VSTR, checked inside AmountFromValue() - }); const UniValue raw_transactions = request.params[0].get_array(); if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) { throw JSONRPCError(RPC_INVALID_PARAMETER, @@ -215,6 +226,12 @@ static RPCHelpMan testmempoolaccept() result_inner.pushKV("vsize", virtual_size); UniValue fees(UniValue::VOBJ); fees.pushKV("base", ValueFromAmount(fee)); + fees.pushKV("effective-feerate", ValueFromAmount(tx_result.m_effective_feerate.value().GetFeePerK())); + UniValue effective_includes_res(UniValue::VARR); + for (const auto& wtxid : tx_result.m_wtxids_fee_calculations.value()) { + effective_includes_res.push_back(wtxid.ToString()); + } + fees.pushKV("effective-includes", effective_includes_res); result_inner.pushKV("fees", fees); } } else { @@ -449,19 +466,17 @@ static RPCHelpMan getmempoolancestors() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); } - CTxMemPool::setEntries setAncestors; - std::string dummy; - mempool.CalculateMemPoolAncestors(*it, setAncestors, CTxMemPool::Limits::NoLimits(), dummy, false); + auto ancestors{mempool.AssumeCalculateMemPoolAncestors(__func__, *it, CTxMemPool::Limits::NoLimits(), /*fSearchForParents=*/false)}; if (!fVerbose) { UniValue o(UniValue::VARR); - for (CTxMemPool::txiter ancestorIt : setAncestors) { + for (CTxMemPool::txiter ancestorIt : ancestors) { o.push_back(ancestorIt->GetTx().GetHash().ToString()); } return o; } else { UniValue o(UniValue::VOBJ); - for (CTxMemPool::txiter ancestorIt : setAncestors) { + for (CTxMemPool::txiter ancestorIt : ancestors) { const CTxMemPoolEntry &e = *ancestorIt; const uint256& _hash = e.GetTx().GetHash(); UniValue info(UniValue::VOBJ); @@ -768,10 +783,13 @@ static RPCHelpMan submitpackage() {RPCResult::Type::NUM, "vsize", "Virtual transaction size as defined in BIP 141."}, {RPCResult::Type::OBJ, "fees", "Transaction fees", { {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT}, + {RPCResult::Type::STR_AMOUNT, "effective-feerate", /*optional=*/true, "if the transaction was not already in the mempool, the effective feerate in " + CURRENCY_UNIT + " per KvB. For example, the package feerate and/or feerate with modified fees from prioritisetransaction."}, + {RPCResult::Type::ARR, "effective-includes", /*optional=*/true, "if effective-feerate is provided, the wtxids of the transactions whose fees and vsizes are included in effective-feerate.", + {{RPCResult::Type::STR_HEX, "", "transaction wtxid in hex"}, + }}, }}, }} }}, - {RPCResult::Type::STR_AMOUNT, "package-feerate", /*optional=*/true, "package feerate used for feerate checks in " + CURRENCY_UNIT + " per KvB. Excludes transactions which were deduplicated or accepted individually."}, {RPCResult::Type::ARR, "replaced-transactions", /*optional=*/true, "List of txids of replaced transactions", { {RPCResult::Type::STR_HEX, "", "The transaction id"}, @@ -787,9 +805,6 @@ static RPCHelpMan submitpackage() if (!Params().IsMockableChain()) { throw std::runtime_error("submitpackage is for regression testing (-regtest mode) only"); } - RPCTypeCheck(request.params, { - UniValue::VARR, - }); const UniValue raw_transactions = request.params[0].get_array(); if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) { throw JSONRPCError(RPC_INVALID_PARAMETER, @@ -838,15 +853,16 @@ static RPCHelpMan submitpackage() NONFATAL_UNREACHABLE(); } } + size_t num_broadcast{0}; for (const auto& tx : txns) { - size_t num_submitted{0}; std::string err_string; - const auto err = BroadcastTransaction(node, tx, err_string, 0, true, true); + const auto err = BroadcastTransaction(node, tx, err_string, /*max_tx_fee=*/0, /*relay=*/true, /*wait_callback=*/true); if (err != TransactionError::OK) { throw JSONRPCTransactionError(err, strprintf("transaction broadcast failed: %s (all transactions were submitted, %d transactions were broadcast successfully)", - err_string, num_submitted)); + err_string, num_broadcast)); } + num_broadcast++; } UniValue rpc_result{UniValue::VOBJ}; UniValue tx_result_map{UniValue::VOBJ}; @@ -856,6 +872,7 @@ static RPCHelpMan submitpackage() CHECK_NONFATAL(it != package_result.m_tx_results.end()); UniValue result_inner{UniValue::VOBJ}; result_inner.pushKV("txid", tx->GetHash().GetHex()); + const auto& tx_result = it->second; if (it->second.m_result_type == MempoolAcceptResult::ResultType::DIFFERENT_WITNESS) { result_inner.pushKV("other-wtxid", it->second.m_other_wtxid.value().GetHex()); } @@ -864,6 +881,17 @@ static RPCHelpMan submitpackage() result_inner.pushKV("vsize", int64_t{it->second.m_vsize.value()}); UniValue fees(UniValue::VOBJ); fees.pushKV("base", ValueFromAmount(it->second.m_base_fees.value())); + if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) { + // Effective feerate is not provided for MEMPOOL_ENTRY transactions even + // though modified fees is known, because it is unknown whether package + // feerate was used when it was originally submitted. + fees.pushKV("effective-feerate", ValueFromAmount(tx_result.m_effective_feerate.value().GetFeePerK())); + UniValue effective_includes_res(UniValue::VARR); + for (const auto& wtxid : tx_result.m_wtxids_fee_calculations.value()) { + effective_includes_res.push_back(wtxid.ToString()); + } + fees.pushKV("effective-includes", effective_includes_res); + } result_inner.pushKV("fees", fees); if (it->second.m_replaced_transactions.has_value()) { for (const auto& ptx : it->second.m_replaced_transactions.value()) { @@ -874,9 +902,6 @@ static RPCHelpMan submitpackage() tx_result_map.pushKV(tx->GetWitnessHash().GetHex(), result_inner); } rpc_result.pushKV("tx-results", tx_result_map); - if (package_result.m_package_feerate.has_value()) { - rpc_result.pushKV("package-feerate", ValueFromAmount(package_result.m_package_feerate.value().GetFeePerK())); - } UniValue replaced_list(UniValue::VARR); for (const uint256& hash : replaced_txids) replaced_list.push_back(hash.ToString()); rpc_result.pushKV("replaced-transactions", replaced_list); diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 98383fdaca..d55e20ba5e 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2021 The Bitcoin Core developers +// Copyright (c) 2009-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -115,9 +115,9 @@ static RPCHelpMan getnetworkhashps() }; } -static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& max_tries, uint256& block_hash) +static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& max_tries, std::shared_ptr<const CBlock>& block_out, bool process_new_block) { - block_hash.SetNull(); + block_out.reset(); block.hashMerkleRoot = BlockMerkleRoot(block); while (max_tries > 0 && block.nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(block.GetHash(), block.nBits, chainman.GetConsensus()) && !ShutdownRequested()) { @@ -131,12 +131,14 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& return true; } - std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block); - if (!chainman.ProcessNewBlock(shared_pblock, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr)) { + block_out = std::make_shared<const CBlock>(block); + + if (!process_new_block) return true; + + if (!chainman.ProcessNewBlock(block_out, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted"); } - block_hash = block.GetHash(); return true; } @@ -147,16 +149,15 @@ static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& me std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler{chainman.ActiveChainstate(), &mempool}.CreateNewBlock(coinbase_script)); if (!pblocktemplate.get()) throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); - CBlock *pblock = &pblocktemplate->block; - uint256 block_hash; - if (!GenerateBlock(chainman, *pblock, nMaxTries, block_hash)) { + std::shared_ptr<const CBlock> block_out; + if (!GenerateBlock(chainman, pblocktemplate->block, nMaxTries, block_out, /*process_new_block=*/true)) { break; } - if (!block_hash.IsNull()) { + if (block_out) { --nGenerate; - blockHashes.push_back(block_hash.GetHex()); + blockHashes.push_back(block_out->GetHash().GetHex()); } } return blockHashes; @@ -295,11 +296,13 @@ static RPCHelpMan generateblock() {"rawtx/txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""}, }, }, + {"submit", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to submit the block before the RPC call returns or to return it as hex."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "hash", "hash of generated block"}, + {RPCResult::Type::STR_HEX, "hex", /*optional=*/true, "hex of generated block, only present when submit=false"}, } }, RPCExamples{ @@ -348,6 +351,7 @@ static RPCHelpMan generateblock() } } + const bool process_new_block{request.params[2].isNull() ? true : request.params[2].get_bool()}; CBlock block; ChainstateManager& chainman = EnsureChainman(node); @@ -376,15 +380,20 @@ static RPCHelpMan generateblock() } } - uint256 block_hash; + std::shared_ptr<const CBlock> block_out; uint64_t max_tries{DEFAULT_MAX_TRIES}; - if (!GenerateBlock(chainman, block, max_tries, block_hash) || block_hash.IsNull()) { + if (!GenerateBlock(chainman, block, max_tries, block_out, process_new_block) || !block_out) { throw JSONRPCError(RPC_MISC_ERROR, "Failed to make block."); } UniValue obj(UniValue::VOBJ); - obj.pushKV("hash", block_hash.GetHex()); + obj.pushKV("hash", block_out->GetHash().GetHex()); + if (!process_new_block) { + CDataStream block_ser{SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()}; + block_ser << *block_out; + obj.pushKV("hex", HexStr(block_ser)); + } return obj; }, }; @@ -441,7 +450,7 @@ static RPCHelpMan prioritisetransaction() "Accepts the transaction into mined blocks at a higher (or lower) priority\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id."}, - {"dummy", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "API-Compatibility for previous API. Must be zero or null.\n" + {"dummy", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "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, RPCArg::Optional::NO, "The fee value (in satoshis) to add (or subtract, if negative).\n" " Note, that this value is not a fee rate. It is a value to modify absolute fee of the TX.\n" @@ -513,8 +522,8 @@ static RPCHelpMan getblocktemplate() { {"template_request", RPCArg::Type::OBJ, RPCArg::Default{UniValue::VOBJ}, "Format of the template", { - {"mode", RPCArg::Type::STR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "This must be set to \"template\", \"proposal\" (see BIP 23), or omitted"}, - {"capabilities", RPCArg::Type::ARR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "A list of strings", + {"mode", RPCArg::Type::STR, /* treat as named arg */ RPCArg::Optional::OMITTED, "This must be set to \"template\", \"proposal\" (see BIP 23), or omitted"}, + {"capabilities", RPCArg::Type::ARR, /* treat as named arg */ RPCArg::Optional::OMITTED, "A list of strings", { {"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "client side supported feature, 'longpoll', 'coinbasevalue', 'proposal', 'serverlist', 'workid'"}, }}, diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 1a4cd09284..7ffa777ef4 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2021 The Bitcoin Core developers +// Copyright (c) 2009-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -114,7 +114,7 @@ static RPCHelpMan getpeerinfo() { {RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"} }}, - {RPCResult::Type::BOOL, "relaytxes", /*optional=*/true, "Whether we relay transactions to this peer"}, + {RPCResult::Type::BOOL, "relaytxes", "Whether we relay transactions to this peer"}, {RPCResult::Type::NUM_TIME, "lastsend", "The " + UNIX_EPOCH_TIME + " of the last send"}, {RPCResult::Type::NUM_TIME, "lastrecv", "The " + UNIX_EPOCH_TIME + " of the last receive"}, {RPCResult::Type::NUM_TIME, "last_transaction", "The " + UNIX_EPOCH_TIME + " of the last valid transaction received from this peer"}, @@ -131,17 +131,17 @@ static RPCHelpMan getpeerinfo() {RPCResult::Type::BOOL, "inbound", "Inbound (true) or Outbound (false)"}, {RPCResult::Type::BOOL, "bip152_hb_to", "Whether we selected peer as (compact blocks) high-bandwidth peer"}, {RPCResult::Type::BOOL, "bip152_hb_from", "Whether peer selected us as (compact blocks) high-bandwidth peer"}, - {RPCResult::Type::NUM, "startingheight", /*optional=*/true, "The starting height (block) of the peer"}, - {RPCResult::Type::NUM, "presynced_headers", /*optional=*/true, "The current height of header pre-synchronization with this peer, or -1 if no low-work sync is in progress"}, - {RPCResult::Type::NUM, "synced_headers", /*optional=*/true, "The last header we have in common with this peer"}, - {RPCResult::Type::NUM, "synced_blocks", /*optional=*/true, "The last block we have in common with this peer"}, - {RPCResult::Type::ARR, "inflight", /*optional=*/true, "", + {RPCResult::Type::NUM, "startingheight", "The starting height (block) of the peer"}, + {RPCResult::Type::NUM, "presynced_headers", "The current height of header pre-synchronization with this peer, or -1 if no low-work sync is in progress"}, + {RPCResult::Type::NUM, "synced_headers", "The last header we have in common with this peer"}, + {RPCResult::Type::NUM, "synced_blocks", "The last block we have in common with this peer"}, + {RPCResult::Type::ARR, "inflight", "", { {RPCResult::Type::NUM, "n", "The heights of blocks we're currently asking from this peer"}, }}, - {RPCResult::Type::BOOL, "addr_relay_enabled", /*optional=*/true, "Whether we participate in address relay with this peer"}, - {RPCResult::Type::NUM, "addr_processed", /*optional=*/true, "The total number of addresses processed, excluding those dropped due to rate limiting"}, - {RPCResult::Type::NUM, "addr_rate_limited", /*optional=*/true, "The total number of addresses dropped due to rate limiting"}, + {RPCResult::Type::BOOL, "addr_relay_enabled", "Whether we participate in address relay with this peer"}, + {RPCResult::Type::NUM, "addr_processed", "The total number of addresses processed, excluding those dropped due to rate limiting"}, + {RPCResult::Type::NUM, "addr_rate_limited", "The total number of addresses dropped due to rate limiting"}, {RPCResult::Type::ARR, "permissions", "Any special permissions that have been granted to this peer", { {RPCResult::Type::STR, "permission_type", Join(NET_PERMISSIONS_DOC, ",\n") + ".\n"}, @@ -185,10 +185,18 @@ static RPCHelpMan getpeerinfo() UniValue obj(UniValue::VOBJ); CNodeStateStats statestats; bool fStateStats = peerman.GetNodeStateStats(stats.nodeid, statestats); + // GetNodeStateStats() requires the existence of a CNodeState and a Peer object + // to succeed for this peer. These are created at connection initialisation and + // exist for the duration of the connection - except if there is a race where the + // peer got disconnected in between the GetNodeStats() and the GetNodeStateStats() + // calls. In this case, the peer doesn't need to be reported here. + if (!fStateStats) { + continue; + } obj.pushKV("id", stats.nodeid); obj.pushKV("addr", stats.m_addr_name); if (stats.addrBind.IsValid()) { - obj.pushKV("addrbind", stats.addrBind.ToString()); + obj.pushKV("addrbind", stats.addrBind.ToStringAddrPort()); } if (!(stats.addrLocal.empty())) { obj.pushKV("addrlocal", stats.addrLocal); @@ -197,12 +205,10 @@ static RPCHelpMan getpeerinfo() if (stats.m_mapped_as != 0) { obj.pushKV("mapped_as", uint64_t(stats.m_mapped_as)); } - ServiceFlags services{fStateStats ? statestats.their_services : ServiceFlags::NODE_NONE}; + ServiceFlags services{statestats.their_services}; obj.pushKV("services", strprintf("%016x", services)); obj.pushKV("servicesnames", GetServicesNames(services)); - if (fStateStats) { - obj.pushKV("relaytxes", statestats.m_relay_txs); - } + obj.pushKV("relaytxes", statestats.m_relay_txs); obj.pushKV("lastsend", count_seconds(stats.m_last_send)); obj.pushKV("lastrecv", count_seconds(stats.m_last_recv)); obj.pushKV("last_transaction", count_seconds(stats.m_last_tx_time)); @@ -217,7 +223,7 @@ static RPCHelpMan getpeerinfo() if (stats.m_min_ping_time < std::chrono::microseconds::max()) { obj.pushKV("minping", Ticks<SecondsDouble>(stats.m_min_ping_time)); } - if (fStateStats && statestats.m_ping_wait > 0s) { + if (statestats.m_ping_wait > 0s) { obj.pushKV("pingwait", Ticks<SecondsDouble>(statestats.m_ping_wait)); } obj.pushKV("version", stats.nVersion); @@ -228,26 +234,24 @@ static RPCHelpMan getpeerinfo() obj.pushKV("inbound", stats.fInbound); obj.pushKV("bip152_hb_to", stats.m_bip152_highbandwidth_to); obj.pushKV("bip152_hb_from", stats.m_bip152_highbandwidth_from); - if (fStateStats) { - obj.pushKV("startingheight", statestats.m_starting_height); - obj.pushKV("presynced_headers", statestats.presync_height); - obj.pushKV("synced_headers", statestats.nSyncHeight); - obj.pushKV("synced_blocks", statestats.nCommonHeight); - UniValue heights(UniValue::VARR); - for (const int height : statestats.vHeightInFlight) { - heights.push_back(height); - } - obj.pushKV("inflight", heights); - obj.pushKV("addr_relay_enabled", statestats.m_addr_relay_enabled); - obj.pushKV("addr_processed", statestats.m_addr_processed); - obj.pushKV("addr_rate_limited", statestats.m_addr_rate_limited); + obj.pushKV("startingheight", statestats.m_starting_height); + obj.pushKV("presynced_headers", statestats.presync_height); + obj.pushKV("synced_headers", statestats.nSyncHeight); + obj.pushKV("synced_blocks", statestats.nCommonHeight); + UniValue heights(UniValue::VARR); + for (const int height : statestats.vHeightInFlight) { + heights.push_back(height); } + obj.pushKV("inflight", heights); + obj.pushKV("addr_relay_enabled", statestats.m_addr_relay_enabled); + obj.pushKV("addr_processed", statestats.m_addr_processed); + obj.pushKV("addr_rate_limited", statestats.m_addr_rate_limited); UniValue permissions(UniValue::VARR); for (const auto& permission : NetPermissions::ToStrings(stats.m_permission_flags)) { permissions.push_back(permission); } obj.pushKV("permissions", permissions); - obj.pushKV("minfeefilter", fStateStats ? ValueFromAmount(statestats.m_fee_filter_received) : 0); + obj.pushKV("minfeefilter", ValueFromAmount(statestats.m_fee_filter_received)); UniValue sendPerMsgType(UniValue::VOBJ); for (const auto& i : stats.mapSendBytesPerMsgType) { @@ -354,7 +358,6 @@ static RPCHelpMan addconnection() throw std::runtime_error("addconnection is for regression testing (-regtest mode) only."); } - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR}); const std::string address = request.params[0].get_str(); const std::string conn_type_in{TrimString(request.params[1].get_str())}; ConnectionType conn_type{}; @@ -493,7 +496,7 @@ static RPCHelpMan getaddednodeinfo() UniValue addresses(UniValue::VARR); if (info.fConnected) { UniValue address(UniValue::VOBJ); - address.pushKV("address", info.resolvedAddress.ToString()); + address.pushKV("address", info.resolvedAddress.ToStringAddrPort()); address.pushKV("connected", info.fInbound ? "inbound" : "outbound"); addresses.push_back(address); } @@ -568,7 +571,7 @@ static UniValue GetNetworksInfo() obj.pushKV("name", GetNetworkName(network)); obj.pushKV("limited", !IsReachable(network)); obj.pushKV("reachable", IsReachable(network)); - obj.pushKV("proxy", proxy.IsValid() ? proxy.proxy.ToStringIPPort() : std::string()); + obj.pushKV("proxy", proxy.IsValid() ? proxy.proxy.ToStringAddrPort() : std::string()); obj.pushKV("proxy_randomize_credentials", proxy.randomize_credentials); networks.push_back(obj); } @@ -661,7 +664,7 @@ static RPCHelpMan getnetworkinfo() for (const std::pair<const CNetAddr, LocalServiceInfo> &item : mapLocalHost) { UniValue rec(UniValue::VOBJ); - rec.pushKV("address", item.first.ToString()); + rec.pushKV("address", item.first.ToStringAddr()); rec.pushKV("port", item.second.nPort); rec.pushKV("score", item.second.nScore); localAddresses.push_back(rec); @@ -699,9 +702,7 @@ static RPCHelpMan setban() throw std::runtime_error(help.ToString()); } NodeContext& node = EnsureAnyNodeContext(request.context); - if (!node.banman) { - throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); - } + BanMan& banman = EnsureBanman(node); CSubNet subNet; CNetAddr netAddr; @@ -723,7 +724,7 @@ static RPCHelpMan setban() if (strCommand == "add") { - if (isSubnet ? node.banman->IsBanned(subNet) : node.banman->IsBanned(netAddr)) { + if (isSubnet ? banman.IsBanned(subNet) : banman.IsBanned(netAddr)) { throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP/Subnet already banned"); } @@ -733,13 +734,17 @@ static RPCHelpMan setban() const bool absolute{request.params[3].isNull() ? false : request.params[3].get_bool()}; + if (absolute && banTime < GetTime()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Absolute timestamp is in the past"); + } + if (isSubnet) { - node.banman->Ban(subNet, banTime, absolute); + banman.Ban(subNet, banTime, absolute); if (node.connman) { node.connman->DisconnectNode(subNet); } } else { - node.banman->Ban(netAddr, banTime, absolute); + banman.Ban(netAddr, banTime, absolute); if (node.connman) { node.connman->DisconnectNode(netAddr); } @@ -747,7 +752,7 @@ static RPCHelpMan setban() } else if(strCommand == "remove") { - if (!( isSubnet ? node.banman->Unban(subNet) : node.banman->Unban(netAddr) )) { + if (!( isSubnet ? banman.Unban(subNet) : banman.Unban(netAddr) )) { throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET, "Error: Unban failed. Requested address/subnet was not previously manually banned."); } } @@ -778,13 +783,10 @@ static RPCHelpMan listbanned() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - NodeContext& node = EnsureAnyNodeContext(request.context); - if(!node.banman) { - throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); - } + BanMan& banman = EnsureAnyBanman(request.context); banmap_t banMap; - node.banman->GetBanned(banMap); + banman.GetBanned(banMap); const int64_t current_time{GetTime()}; UniValue bannedAddresses(UniValue::VARR); @@ -818,12 +820,9 @@ static RPCHelpMan clearbanned() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - NodeContext& node = EnsureAnyNodeContext(request.context); - if (!node.banman) { - throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); - } + BanMan& banman = EnsureAnyBanman(request.context); - node.banman->ClearBanned(); + banman.ClearBanned(); return UniValue::VNULL; }, @@ -902,7 +901,7 @@ static RPCHelpMan getnodeaddresses() UniValue obj(UniValue::VOBJ); obj.pushKV("time", int64_t{TicksSinceEpoch<std::chrono::seconds>(addr.nTime)}); obj.pushKV("services", (uint64_t)addr.nServices); - obj.pushKV("address", addr.ToStringIP()); + obj.pushKV("address", addr.ToStringAddr()); obj.pushKV("port", addr.GetPort()); obj.pushKV("network", GetNetworkName(addr.GetNetClass())); ret.push_back(obj); diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp index 605ebc15a7..5918bc6e38 100644 --- a/src/rpc/node.cpp +++ b/src/rpc/node.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2021 The Bitcoin Core developers +// Copyright (c) 2009-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -12,6 +12,7 @@ #include <interfaces/echo.h> #include <interfaces/init.h> #include <interfaces/ipc.h> +#include <kernel/cs_main.h> #include <node/context.h> #include <rpc/server.h> #include <rpc/server_util.h> @@ -52,7 +53,6 @@ static RPCHelpMan setmocktime() // ensure all call sites of GetTime() are accessing this safely. LOCK(cs_main); - RPCTypeCheck(request.params, {UniValue::VNUM}); const int64_t time{request.params[0].getInt<int64_t>()}; if (time < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime cannot be negative: %s.", time)); @@ -106,8 +106,6 @@ static RPCHelpMan mockscheduler() throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only"); } - // check params are valid values - RPCTypeCheck(request.params, {UniValue::VNUM}); int64_t delta_seconds = request.params[0].getInt<int64_t>(); if (delta_seconds <= 0 || delta_seconds > 3600) { throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)"); @@ -242,11 +240,11 @@ static RPCHelpMan logging() " - \"none\", \"0\" : even if other logging categories are specified, ignore all of them.\n" , { - {"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to add to debug logging", + {"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "The categories to add to debug logging", { {"include_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"}, }}, - {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to remove from debug logging", + {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "The categories to remove from debug logging", { {"exclude_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"}, }}, @@ -295,18 +293,18 @@ static RPCHelpMan echo(const std::string& name) "\nIt will return an internal bug report when arg9='trigger_internal_bug' is passed.\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.", - { - {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - }, + { + {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, + {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, + {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, + {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, + {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, + {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, + {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, + {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, + {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, + {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, + }, RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"}, RPCExamples{""}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue @@ -378,7 +376,7 @@ static RPCHelpMan getindexinfo() return RPCHelpMan{"getindexinfo", "\nReturns the status of one or all available indices currently running in the node.\n", { - {"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Filter results for an index with a specific name."}, + {"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Filter results for an index with a specific name."}, }, RPCResult{ RPCResult::Type::OBJ_DYN, "", "", { diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp index 2ac6d6d76f..990ec3ab0c 100644 --- a/src/rpc/output_script.cpp +++ b/src/rpc/output_script.cpp @@ -162,7 +162,7 @@ static RPCHelpMan createmultisig() // Only warns if the user has explicitly chosen an address type we cannot generate warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present."); } - if (!warnings.empty()) result.pushKV("warnings", warnings); + PushWarnings(warnings, result); return result; }, @@ -195,8 +195,6 @@ static RPCHelpMan getdescriptorinfo() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR}); - FlatSigningProvider provider; std::string error; auto desc = Parse(request.params[0].get_str(), provider, error); @@ -232,7 +230,7 @@ static RPCHelpMan deriveaddresses() "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n"}, { {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."}, - {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."}, + {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."}, }, RPCResult{ RPCResult::Type::ARR, "", "", @@ -247,7 +245,6 @@ static RPCHelpMan deriveaddresses() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later const std::string desc_str = request.params[0].get_str(); int64_t range_begin = 0; diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 400f2f1507..21d49fda9d 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2021 The Bitcoin Core developers +// Copyright (c) 2009-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -32,6 +32,7 @@ #include <script/signingprovider.h> #include <script/standard.h> #include <uint256.h> +#include <undo.h> #include <util/bip32.h> #include <util/check.h> #include <util/strencodings.h> @@ -50,15 +51,20 @@ using node::FindCoins; using node::GetTransaction; using node::NodeContext; using node::PSBTAnalysis; +using node::ReadBlockFromDisk; +using node::UndoReadFromDisk; -static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, Chainstate& active_chainstate) +static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, + Chainstate& active_chainstate, const CTxUndo* txundo = nullptr, + TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS) { + CHECK_NONFATAL(verbosity >= TxVerbosity::SHOW_DETAILS); // Call into TxToUniv() in bitcoin-common to decode the transaction hex. // // Blockchain contextual information (confirmations and blocktime) is not // available to code in bitcoin-common, so we query them here and push the // data into the returned UniValue. - TxToUniv(tx, /*block_hash=*/uint256(), entry, /*include_hex=*/true, RPCSerializationFlags()); + TxToUniv(tx, /*block_hash=*/uint256(), entry, /*include_hex=*/true, RPCSerializationFlags(), txundo, verbosity); if (!hashBlock.IsNull()) { LOCK(cs_main); @@ -77,6 +83,17 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& } } +static std::vector<RPCResult> ScriptPubKeyDoc() { + return + { + {RPCResult::Type::STR, "asm", "Disassembly of the public key script"}, + {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"}, + {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, + {RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"}, + }; +} + static std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc) { return { @@ -112,14 +129,7 @@ static std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc) { {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT}, {RPCResult::Type::NUM, "n", "index"}, - {RPCResult::Type::OBJ, "scriptPubKey", "", - { - {RPCResult::Type::STR, "asm", "Disassembly of the public key script"}, - {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"}, - {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"}, - {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, - {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, - }}, + {RPCResult::Type::OBJ, "scriptPubKey", "", ScriptPubKeyDoc()}, }}, }}, }; @@ -155,7 +165,7 @@ static std::vector<RPCArg> CreateTxDoc() }, }, }, - }, + RPCArgOptions{.skip_type_check = true}}, {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"}, {"replaceable", RPCArg::Type::BOOL, RPCArg::Default{true}, "Marks this transaction as BIP125-replaceable.\n" "Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."}, @@ -166,26 +176,27 @@ static RPCHelpMan getrawtransaction() { return RPCHelpMan{ "getrawtransaction", - "Return the raw transaction data.\n" - "\nBy default, this call only returns a transaction if it is in the mempool. If -txindex is enabled\n" + "By default, this call only returns a transaction if it is in the mempool. If -txindex is enabled\n" "and no blockhash argument is passed, it will return the transaction if it is in the mempool or any block.\n" "If a blockhash argument is passed, it will return the transaction if\n" - "the specified block is available and the transaction is in that block.\n" - "\nHint: Use gettransaction for wallet transactions.\n" + "the specified block is available and the transaction is in that block.\n\n" + "Hint: Use gettransaction for wallet transactions.\n\n" - "\nIf verbose is 'true', returns an Object with information about 'txid'.\n" - "If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.", + "If verbosity is 0 or omitted, returns the serialized transaction as a hex-encoded string.\n" + "If verbosity is 1, returns a JSON Object with information about the transaction.\n" + "If verbosity is 2, returns a JSON Object with information about the transaction, including fee and prevout information.", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, - {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If false, return a string, otherwise return a json object"}, - {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "The block in which to look for the transaction"}, + {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{0}, "0 for hex-encoded data, 1 for a JSON object, and 2 for JSON object with fee and prevout", + RPCArgOptions{.skip_type_check = true}}, + {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The block in which to look for the transaction"}, }, { - RPCResult{"if verbose is not set or set to false", - RPCResult::Type::STR, "data", "The serialized, hex-encoded data for 'txid'" + RPCResult{"if verbosity is not set or set to 0", + RPCResult::Type::STR, "data", "The serialized transaction as a hex-encoded string for 'txid'" }, - RPCResult{"if verbose is set to true", + RPCResult{"if verbosity is set to 1", RPCResult::Type::OBJ, "", "", Cat<std::vector<RPCResult>>( { @@ -198,20 +209,40 @@ static RPCHelpMan getrawtransaction() }, DecodeTxDoc(/*txid_field_doc=*/"The transaction id (same as provided)")), }, + RPCResult{"for verbosity = 2", + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::ELISION, "", "Same output as verbosity = 1"}, + {RPCResult::Type::NUM, "fee", /*optional=*/true, "transaction fee in " + CURRENCY_UNIT + ", omitted if block undo data is not available"}, + {RPCResult::Type::ARR, "vin", "", + { + {RPCResult::Type::OBJ, "", "utxo being spent", + { + {RPCResult::Type::ELISION, "", "Same output as verbosity = 1"}, + {RPCResult::Type::OBJ, "prevout", /*optional=*/true, "The previous output, omitted if block undo data is not available", + { + {RPCResult::Type::BOOL, "generated", "Coinbase or not"}, + {RPCResult::Type::NUM, "height", "The height of the prevout"}, + {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT}, + {RPCResult::Type::OBJ, "scriptPubKey", "", ScriptPubKeyDoc()}, + }}, + }}, + }}, + }}, }, RPCExamples{ HelpExampleCli("getrawtransaction", "\"mytxid\"") - + HelpExampleCli("getrawtransaction", "\"mytxid\" true") - + HelpExampleRpc("getrawtransaction", "\"mytxid\", true") - + HelpExampleCli("getrawtransaction", "\"mytxid\" false \"myblockhash\"") - + HelpExampleCli("getrawtransaction", "\"mytxid\" true \"myblockhash\"") + + HelpExampleCli("getrawtransaction", "\"mytxid\" 1") + + HelpExampleRpc("getrawtransaction", "\"mytxid\", 1") + + HelpExampleCli("getrawtransaction", "\"mytxid\" 0 \"myblockhash\"") + + HelpExampleCli("getrawtransaction", "\"mytxid\" 1 \"myblockhash\"") + + HelpExampleCli("getrawtransaction", "\"mytxid\" 2 \"myblockhash\"") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const NodeContext& node = EnsureAnyNodeContext(request.context); ChainstateManager& chainman = EnsureChainman(node); - bool in_active_chain = true; uint256 hash = ParseHashV(request.params[0], "parameter 1"); const CBlockIndex* blockindex = nullptr; @@ -220,10 +251,14 @@ static RPCHelpMan getrawtransaction() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The genesis block coinbase is not considered an ordinary transaction and cannot be retrieved"); } - // Accept either a bool (true) or a num (>=1) to indicate verbose output. - bool fVerbose = false; + // Accept either a bool (true) or a num (>=0) to indicate verbosity. + int verbosity{0}; if (!request.params[1].isNull()) { - fVerbose = request.params[1].isNum() ? (request.params[1].getInt<int>() != 0) : request.params[1].get_bool(); + if (request.params[1].isBool()) { + verbosity = request.params[1].get_bool(); + } else { + verbosity = request.params[1].getInt<int>(); + } } if (!request.params[2].isNull()) { @@ -234,7 +269,6 @@ static RPCHelpMan getrawtransaction() if (!blockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block hash not found"); } - in_active_chain = chainman.ActiveChain().Contains(blockindex); } bool f_txindex_ready = false; @@ -262,13 +296,43 @@ static RPCHelpMan getrawtransaction() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, errmsg + ". Use gettransaction for wallet transactions."); } - if (!fVerbose) { + if (verbosity <= 0) { return EncodeHexTx(*tx, RPCSerializationFlags()); } UniValue result(UniValue::VOBJ); - if (blockindex) result.pushKV("in_active_chain", in_active_chain); - TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate()); + if (blockindex) { + LOCK(cs_main); + result.pushKV("in_active_chain", chainman.ActiveChain().Contains(blockindex)); + } + // If request is verbosity >= 1 but no blockhash was given, then look up the blockindex + if (request.params[2].isNull()) { + LOCK(cs_main); + blockindex = chainman.m_blockman.LookupBlockIndex(hash_block); + } + if (verbosity == 1) { + TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate()); + return result; + } + + CBlockUndo blockUndo; + CBlock block; + const bool is_block_pruned{WITH_LOCK(cs_main, return chainman.m_blockman.IsBlockPruned(blockindex))}; + + if (tx->IsCoinBase() || + !blockindex || is_block_pruned || + !(UndoReadFromDisk(blockUndo, blockindex) && ReadBlockFromDisk(block, blockindex, Params().GetConsensus()))) { + TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate()); + return result; + } + + CTxUndo* undoTX {nullptr}; + auto it = std::find_if(block.vtx.begin(), block.vtx.end(), [tx](CTransactionRef t){ return *t == *tx; }); + if (it != block.vtx.end()) { + // -1 as blockundo does not have coinbase tx + undoTX = &blockUndo.vtxundo.at(it - block.vtx.begin() - 1); + } + TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate(), undoTX, TxVerbosity::SHOW_DETAILS_AND_PREVOUT); return result; }, }; @@ -294,14 +358,6 @@ static RPCHelpMan createrawtransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, { - UniValue::VARR, - UniValueType(), // ARR or OBJ, checked later - UniValue::VNUM, - UniValue::VBOOL - }, true - ); - std::optional<bool> rbf; if (!request.params[3].isNull()) { rbf = request.params[3].get_bool(); @@ -337,8 +393,6 @@ static RPCHelpMan decoderawtransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}); - CMutableTransaction mtx; bool try_witness = request.params[1].isNull() ? true : request.params[1].get_bool(); @@ -391,8 +445,6 @@ static RPCHelpMan decodescript() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR}); - UniValue r(UniValue::VOBJ); CScript script; if (request.params[0].get_str().size() > 0){ @@ -470,15 +522,17 @@ static RPCHelpMan decodescript() if (can_wrap_P2WSH) { UniValue sr(UniValue::VOBJ); CScript segwitScr; + FlatSigningProvider provider; if (which_type == TxoutType::PUBKEY) { segwitScr = GetScriptForDestination(WitnessV0KeyHash(Hash160(solutions_data[0]))); } else if (which_type == TxoutType::PUBKEYHASH) { segwitScr = GetScriptForDestination(WitnessV0KeyHash(uint160{solutions_data[0]})); } else { // Scripts that are not fit for P2WPKH are encoded as P2WSH. + provider.scripts[CScriptID(script)] = script; segwitScr = GetScriptForDestination(WitnessV0ScriptHash(script)); } - ScriptToUniv(segwitScr, /*out=*/sr, /*include_hex=*/true, /*include_address=*/true); + ScriptToUniv(segwitScr, /*out=*/sr, /*include_hex=*/true, /*include_address=*/true, /*provider=*/&provider); sr.pushKV("p2sh-segwit", EncodeDestination(ScriptHash(segwitScr))); r.pushKV("segwit", sr); } @@ -590,7 +644,7 @@ static RPCHelpMan signrawtransactionwithkey() {"privatekey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "private key in base58-encoding"}, }, }, - {"prevtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The previous dependent transaction outputs", + {"prevtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "The previous dependent transaction outputs", { {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", { @@ -642,8 +696,6 @@ static RPCHelpMan signrawtransactionwithkey() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); - CMutableTransaction mtx; if (!DecodeHexTx(mtx, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input."); @@ -921,8 +973,6 @@ static RPCHelpMan decodepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR}); - // Unserialize the transactions PartiallySignedTransaction psbtx; std::string error; @@ -1335,8 +1385,6 @@ static RPCHelpMan combinepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VARR}, true); - // Unserialize the transactions std::vector<PartiallySignedTransaction> psbtxs; UniValue txs = request.params[0].get_array(); @@ -1390,8 +1438,6 @@ static RPCHelpMan finalizepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true); - // Unserialize the transactions PartiallySignedTransaction psbtx; std::string error; @@ -1439,14 +1485,6 @@ static RPCHelpMan createpsbt() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, { - UniValue::VARR, - UniValueType(), // ARR or OBJ, checked later - UniValue::VNUM, - UniValue::VBOOL, - }, true - ); - std::optional<bool> rbf; if (!request.params[3].isNull()) { rbf = request.params[3].get_bool(); @@ -1500,8 +1538,6 @@ static RPCHelpMan converttopsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true); - // parse hex string from parameter CMutableTransaction tx; bool permitsigdata = request.params[1].isNull() ? false : request.params[1].get_bool(); @@ -1547,7 +1583,7 @@ static RPCHelpMan utxoupdatepsbt() "\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set or the mempool.\n", { {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"}, - {"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "An array of either strings or objects", { + {"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of either strings or objects", { {"", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"}, {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with an output descriptor and extra information", { {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"}, @@ -1563,8 +1599,6 @@ static RPCHelpMan utxoupdatepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true); - // Unserialize the transactions PartiallySignedTransaction psbtx; std::string error; @@ -1654,8 +1688,6 @@ static RPCHelpMan joinpsbts() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VARR}, true); - // Unserialize the transactions std::vector<PartiallySignedTransaction> psbtxs; UniValue txs = request.params[0].get_array(); @@ -1782,8 +1814,6 @@ static RPCHelpMan analyzepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR}); - // Unserialize the transaction PartiallySignedTransaction psbtx; std::string error; diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index b078ee8b29..3ba930f84f 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2021 The Bitcoin Core developers +// Copyright (c) 2009-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -21,12 +21,8 @@ #include <util/strencodings.h> #include <util/translation.h> -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf) +void AddInputs(CMutableTransaction& rawTx, const UniValue& inputs_in, std::optional<bool> rbf) { - if (outputs_in.isNull()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null"); - } - UniValue inputs; if (inputs_in.isNull()) { inputs = UniValue::VARR; @@ -34,18 +30,6 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal inputs = inputs_in.get_array(); } - const bool outputs_is_obj = outputs_in.isObject(); - UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array(); - - CMutableTransaction rawTx; - - if (!locktime.isNull()) { - int64_t nLockTime = locktime.getInt<int64_t>(); - if (nLockTime < 0 || nLockTime > LOCKTIME_MAX) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); - rawTx.nLockTime = nLockTime; - } - for (unsigned int idx = 0; idx < inputs.size(); idx++) { const UniValue& input = inputs[idx]; const UniValue& o = input.get_obj(); @@ -84,6 +68,16 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal rawTx.vin.push_back(in); } +} + +void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in) +{ + if (outputs_in.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null"); + } + + const bool outputs_is_obj = outputs_in.isObject(); + UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array(); if (!outputs_is_obj) { // Translate array of key-value pairs into dict @@ -132,6 +126,21 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal rawTx.vout.push_back(out); } } +} + +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf) +{ + CMutableTransaction rawTx; + + if (!locktime.isNull()) { + int64_t nLockTime = locktime.getInt<int64_t>(); + if (nLockTime < 0 || nLockTime > LOCKTIME_MAX) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); + rawTx.nLockTime = nLockTime; + } + + AddInputs(rawTx, inputs_in, rbf); + AddOutputs(rawTx, outputs_in); if (rbf.has_value() && rbf.value() && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option"); diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index 9b5c9f08d4..a863432b7a 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021 The Bitcoin Core developers +// Copyright (c) 2017-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -38,6 +38,13 @@ void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const */ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins); + +/** Normalize univalue-represented inputs and add them to the transaction */ +void AddInputs(CMutableTransaction& rawTx, const UniValue& inputs_in, bool rbf); + +/** Normalize univalue-represented outputs and add them to the transaction */ +void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in); + /** Create a transaction from univalue parameters */ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf); diff --git a/src/rpc/register.h b/src/rpc/register.h index 301f410da3..c88f49ecf0 100644 --- a/src/rpc/register.h +++ b/src/rpc/register.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2021 The Bitcoin Core developers +// Copyright (c) 2009-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp index 8595fa78bb..4c5ccbbdad 100644 --- a/src/rpc/request.cpp +++ b/src/rpc/request.cpp @@ -1,16 +1,17 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2021 The Bitcoin Core developers +// Copyright (c) 2009-2022 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 <rpc/request.h> -#include <fs.h> +#include <util/fs.h> #include <random.h> #include <rpc/protocol.h> -#include <util/system.h> +#include <util/fs_helpers.h> #include <util/strencodings.h> +#include <util/system.h> #include <fstream> #include <stdexcept> @@ -75,7 +76,7 @@ static fs::path GetAuthCookieFile(bool temp=false) if (temp) { arg += ".tmp"; } - return AbsPathForConfigVal(arg); + return AbsPathForConfigVal(gArgs, arg); } bool GenerateAuthCookie(std::string *cookie_out) @@ -86,7 +87,7 @@ bool GenerateAuthCookie(std::string *cookie_out) std::string cookie = COOKIEAUTH_USER + ":" + HexStr(rand_pwd); /** the umask determines what permissions are used to create this file - - * these are set to 077 in init.cpp unless overridden with -sysperms. + * these are set to 0077 in util/system.cpp. */ std::ofstream file; fs::path filepath_tmp = GetAuthCookieFile(true); diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 80a22ff8ca..392238a1fd 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2021 The Bitcoin Core developers +// Copyright (c) 2009-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -83,6 +83,7 @@ std::string CRPCTable::help(const std::string& strCommand, const JSONRPCRequest& std::string category; std::set<intptr_t> setDone; std::vector<std::pair<std::string, const CRPCCommand*> > vCommands; + vCommands.reserve(mapCommands.size()); for (const auto& entry : mapCommands) vCommands.push_back(make_pair(entry.second.front()->category + entry.first, entry.second.front())); @@ -168,7 +169,7 @@ static RPCHelpMan stop() // to the client (intended for testing) "\nRequest a graceful shutdown of " PACKAGE_NAME ".", { - {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", RPCArgOptions{.hidden=true}}, + {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "how long to wait in ms", RPCArgOptions{.hidden=true}}, }, RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"}, RPCExamples{""}, @@ -399,7 +400,10 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c const std::vector<UniValue>& values = in.params.getValues(); std::unordered_map<std::string, const UniValue*> argsIn; for (size_t i=0; i<keys.size(); ++i) { - argsIn[keys[i]] = &values[i]; + auto [_, inserted] = argsIn.emplace(keys[i], &values[i]); + if (!inserted) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + keys[i] + " specified multiple times"); + } } // Process expected parameters. If any parameters were left unspecified in // the request before a parameter that was specified, null values need to be @@ -510,6 +514,7 @@ static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& req std::vector<std::string> CRPCTable::listCommands() const { std::vector<std::string> commandList; + commandList.reserve(mapCommands.size()); for (const auto& i : mapCommands) commandList.emplace_back(i.first); return commandList; } diff --git a/src/rpc/server_util.cpp b/src/rpc/server_util.cpp index 6a1b41f066..7a708ec813 100644 --- a/src/rpc/server_util.cpp +++ b/src/rpc/server_util.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021 The Bitcoin Core developers +// Copyright (c) 2021-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -39,6 +39,20 @@ CTxMemPool& EnsureAnyMemPool(const std::any& context) return EnsureMemPool(EnsureAnyNodeContext(context)); } + +BanMan& EnsureBanman(const NodeContext& node) +{ + if (!node.banman) { + throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); + } + return *node.banman; +} + +BanMan& EnsureAnyBanman(const std::any& context) +{ + return EnsureBanman(EnsureAnyNodeContext(context)); +} + ArgsManager& EnsureArgsman(const NodeContext& node) { if (!node.args) { diff --git a/src/rpc/server_util.h b/src/rpc/server_util.h index 2cc710a803..9af9572431 100644 --- a/src/rpc/server_util.h +++ b/src/rpc/server_util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021 The Bitcoin Core developers +// Copyright (c) 2021-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -13,6 +13,7 @@ class CConnman; class CTxMemPool; class ChainstateManager; class PeerManager; +class BanMan; namespace node { struct NodeContext; } // namespace node @@ -20,6 +21,8 @@ struct NodeContext; node::NodeContext& EnsureAnyNodeContext(const std::any& context); CTxMemPool& EnsureMemPool(const node::NodeContext& node); CTxMemPool& EnsureAnyMemPool(const std::any& context); +BanMan& EnsureBanman(const node::NodeContext& node); +BanMan& EnsureAnyBanman(const std::any& context); ArgsManager& EnsureArgsman(const node::NodeContext& node); ArgsManager& EnsureAnyArgsman(const std::any& context); ChainstateManager& EnsureChainman(const node::NodeContext& node); diff --git a/src/rpc/txoutproof.cpp b/src/rpc/txoutproof.cpp index 8c5468634d..24b5d04115 100644 --- a/src/rpc/txoutproof.cpp +++ b/src/rpc/txoutproof.cpp @@ -34,7 +34,7 @@ static RPCHelpMan gettxoutproof() {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"}, }, }, - {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "If specified, looks for txid in the block with this hash"}, + {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "If specified, looks for txid in the block with this hash"}, }, RPCResult{ RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof." @@ -112,7 +112,7 @@ static RPCHelpMan gettxoutproof() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block"); } - CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); + DataStream ssMB{}; CMerkleBlock mb(block, setTxids); ssMB << mb; std::string strHex = HexStr(ssMB); @@ -138,7 +138,7 @@ static RPCHelpMan verifytxoutproof() RPCExamples{""}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); + DataStream ssMB{ParseHexV(request.params[0], "proof")}; CMerkleBlock merkleBlock; ssMB >> merkleBlock; diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index dd5739faf7..f95ac4cb4b 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -1,7 +1,8 @@ -// Copyright (c) 2017-2021 The Bitcoin Core developers +// Copyright (c) 2017-2022 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 <clientversion.h> #include <consensus/amount.h> #include <key_io.h> #include <outputtype.h> @@ -30,31 +31,6 @@ std::string GetAllOutputTypes() return Join(ret, ", "); } -void RPCTypeCheck(const UniValue& params, - const std::list<UniValueType>& typesExpected, - bool fAllowNull) -{ - unsigned int i = 0; - for (const UniValueType& t : typesExpected) { - if (params.size() <= i) - break; - - const UniValue& v = params[i]; - if (!(fAllowNull && v.isNull())) { - RPCTypeCheckArgument(v, t); - } - i++; - } -} - -void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected) -{ - if (!typeExpected.typeAny && value.type() != typeExpected.type) { - throw JSONRPCError(RPC_TYPE_ERROR, - strprintf("JSON value of type %s is not of expected type %s", uvTypeName(value.type()), uvTypeName(typeExpected.type))); - } -} - void RPCTypeCheckObj(const UniValue& o, const std::map<std::string, UniValueType>& typesExpected, bool fAllowNull, @@ -405,6 +381,7 @@ struct Sections { const auto indent = std::string(current_indent, ' '); const auto indent_next = std::string(current_indent + 2, ' '); const bool push_name{outer_type == OuterType::OBJ}; // Dictionary keys must have a name + const bool is_top_level_arg{outer_type == OuterType::NONE}; // True on the first recursion switch (arg.m_type) { case RPCArg::Type::STR_HEX: @@ -413,7 +390,7 @@ struct Sections { case RPCArg::Type::AMOUNT: case RPCArg::Type::RANGE: case RPCArg::Type::BOOL: { - if (outer_type == OuterType::NONE) return; // Nothing more to do for non-recursive types on first recursion + if (is_top_level_arg) return; // Nothing more to do for non-recursive types on first recursion auto left = indent; if (arg.m_opts.type_str.size() != 0 && push_name) { left += "\"" + arg.GetName() + "\": " + arg.m_opts.type_str.at(0); @@ -421,12 +398,12 @@ struct Sections { left += push_name ? arg.ToStringObj(/*oneline=*/false) : arg.ToString(/*oneline=*/false); } left += ","; - PushSection({left, arg.ToDescriptionString()}); + PushSection({left, arg.ToDescriptionString(/*is_named_arg=*/push_name)}); break; } case RPCArg::Type::OBJ: case RPCArg::Type::OBJ_USER_KEYS: { - const auto right = outer_type == OuterType::NONE ? "" : arg.ToDescriptionString(); + const auto right = is_top_level_arg ? "" : arg.ToDescriptionString(/*is_named_arg=*/push_name); PushSection({indent + (push_name ? "\"" + arg.GetName() + "\": " : "") + "{", right}); for (const auto& arg_inner : arg.m_inner) { Push(arg_inner, current_indent + 2, OuterType::OBJ); @@ -434,20 +411,20 @@ struct Sections { if (arg.m_type != RPCArg::Type::OBJ) { PushSection({indent_next + "...", ""}); } - PushSection({indent + "}" + (outer_type != OuterType::NONE ? "," : ""), ""}); + PushSection({indent + "}" + (is_top_level_arg ? "" : ","), ""}); break; } case RPCArg::Type::ARR: { auto left = indent; left += push_name ? "\"" + arg.GetName() + "\": " : ""; left += "["; - const auto right = outer_type == OuterType::NONE ? "" : arg.ToDescriptionString(); + const auto right = is_top_level_arg ? "" : arg.ToDescriptionString(/*is_named_arg=*/push_name); 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::NONE ? "," : ""), ""}); + PushSection({indent + "]" + (is_top_level_arg ? "" : ","), ""}); break; } } // no default case, so the compiler can warn about missing cases @@ -579,9 +556,39 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const if (request.mode == JSONRPCRequest::GET_HELP || !IsValidNumArgs(request.params.size())) { throw std::runtime_error(ToString()); } - const UniValue ret = m_fun(*this, request); + UniValue arg_mismatch{UniValue::VOBJ}; + for (size_t i{0}; i < m_args.size(); ++i) { + const auto& arg{m_args.at(i)}; + UniValue match{arg.MatchesType(request.params[i])}; + if (!match.isTrue()) { + arg_mismatch.pushKV(strprintf("Position %s (%s)", i + 1, arg.m_names), std::move(match)); + } + } + if (!arg_mismatch.empty()) { + throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Wrong type passed:\n%s", arg_mismatch.write(4))); + } + UniValue ret = m_fun(*this, request); if (gArgs.GetBoolArg("-rpcdoccheck", DEFAULT_RPC_DOC_CHECK)) { - CHECK_NONFATAL(std::any_of(m_results.m_results.begin(), m_results.m_results.end(), [&ret](const RPCResult& res) { return res.MatchesType(ret); })); + UniValue mismatch{UniValue::VARR}; + for (const auto& res : m_results.m_results) { + UniValue match{res.MatchesType(ret)}; + if (match.isTrue()) { + mismatch.setNull(); + break; + } + mismatch.push_back(match); + } + if (!mismatch.isNull()) { + std::string explain{ + mismatch.empty() ? "no possible results defined" : + mismatch.size() == 1 ? mismatch[0].write(4) : + mismatch.write(4)}; + throw std::runtime_error{ + strprintf("Internal bug detected: RPC call \"%s\" returned incorrect type:\n%s\n%s %s\nPlease report this issue here: %s\n", + m_name, explain, + PACKAGE_NAME, FormatFullVersion(), + PACKAGE_BUGREPORT)}; + } } return ret; } @@ -601,6 +608,7 @@ bool RPCHelpMan::IsValidNumArgs(size_t num_args) const std::vector<std::string> RPCHelpMan::GetArgNames() const { std::vector<std::string> ret; + ret.reserve(m_args.size()); for (const auto& arg : m_args) { ret.emplace_back(arg.m_names); } @@ -641,7 +649,7 @@ std::string RPCHelpMan::ToString() const if (i == 0) ret += "\nArguments:\n"; // Push named argument name and description - sections.m_sections.emplace_back(::ToString(i + 1) + ". " + arg.GetFirstName(), arg.ToDescriptionString()); + sections.m_sections.emplace_back(::ToString(i + 1) + ". " + arg.GetFirstName(), arg.ToDescriptionString(/*is_named_arg=*/true)); sections.m_max_pad = std::max(sections.m_max_pad, sections.m_sections.back().m_left.size()); // Recursively push nested args @@ -677,14 +685,60 @@ UniValue RPCHelpMan::GetArgMap() const return arr; } +static std::optional<UniValue::VType> ExpectedType(RPCArg::Type type) +{ + using Type = RPCArg::Type; + switch (type) { + case Type::STR_HEX: + case Type::STR: { + return UniValue::VSTR; + } + case Type::NUM: { + return UniValue::VNUM; + } + case Type::AMOUNT: { + // VNUM or VSTR, checked inside AmountFromValue() + return std::nullopt; + } + case Type::RANGE: { + // VNUM or VARR, checked inside ParseRange() + return std::nullopt; + } + case Type::BOOL: { + return UniValue::VBOOL; + } + case Type::OBJ: + case Type::OBJ_USER_KEYS: { + return UniValue::VOBJ; + } + case Type::ARR: { + return UniValue::VARR; + } + } // no default case, so the compiler can warn about missing cases + NONFATAL_UNREACHABLE(); +} + +UniValue RPCArg::MatchesType(const UniValue& request) const +{ + if (m_opts.skip_type_check) return true; + if (IsOptional() && request.isNull()) return true; + const auto exp_type{ExpectedType(m_type)}; + if (!exp_type) return true; // nothing to check + + if (*exp_type != request.getType()) { + return strprintf("JSON value of type %s is not of expected type %s", uvTypeName(request.getType()), uvTypeName(*exp_type)); + } + return true; +} + std::string RPCArg::GetFirstName() const { - return m_names.substr(0, m_names.find("|")); + return m_names.substr(0, m_names.find('|')); } std::string RPCArg::GetName() const { - CHECK_NONFATAL(std::string::npos == m_names.find("|")); + CHECK_NONFATAL(std::string::npos == m_names.find('|')); return m_names; } @@ -697,7 +751,7 @@ bool RPCArg::IsOptional() const } } -std::string RPCArg::ToDescriptionString() const +std::string RPCArg::ToDescriptionString(bool is_named_arg) const { std::string ret; ret += "("; @@ -744,13 +798,10 @@ std::string RPCArg::ToDescriptionString() const } else { switch (std::get<RPCArg::Optional>(m_fallback)) { case RPCArg::Optional::OMITTED: { + if (is_named_arg) ret += ", optional"; // Default value is "null" in dicts. Otherwise, // nothing to do. Element is treated as if not present and has no default value break; } - case RPCArg::Optional::OMITTED_NAMED_ARG: { - ret += ", optional"; // Default value is "null" - break; - } case RPCArg::Optional::NO: { ret += ", required"; break; @@ -860,53 +911,77 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const NONFATAL_UNREACHABLE(); } -bool RPCResult::MatchesType(const UniValue& result) const +static std::optional<UniValue::VType> ExpectedType(RPCResult::Type type) { - if (m_skip_type_check) { - return true; - } - switch (m_type) { + using Type = RPCResult::Type; + switch (type) { case Type::ELISION: case Type::ANY: { - return true; + return std::nullopt; } case Type::NONE: { - return UniValue::VNULL == result.getType(); + return UniValue::VNULL; } case Type::STR: case Type::STR_HEX: { - return UniValue::VSTR == result.getType(); + return UniValue::VSTR; } case Type::NUM: case Type::STR_AMOUNT: case Type::NUM_TIME: { - return UniValue::VNUM == result.getType(); + return UniValue::VNUM; } case Type::BOOL: { - return UniValue::VBOOL == result.getType(); + return UniValue::VBOOL; } case Type::ARR_FIXED: case Type::ARR: { - if (UniValue::VARR != result.getType()) return false; + return UniValue::VARR; + } + case Type::OBJ_DYN: + case Type::OBJ: { + return UniValue::VOBJ; + } + } // no default case, so the compiler can warn about missing cases + NONFATAL_UNREACHABLE(); +} + +UniValue RPCResult::MatchesType(const UniValue& result) const +{ + if (m_skip_type_check) { + return true; + } + + const auto exp_type = ExpectedType(m_type); + if (!exp_type) return true; // can be any type, so nothing to check + + if (*exp_type != result.getType()) { + return strprintf("returned type is %s, but declared as %s in doc", uvTypeName(result.getType()), uvTypeName(*exp_type)); + } + + if (UniValue::VARR == result.getType()) { + UniValue errors(UniValue::VOBJ); for (size_t i{0}; i < result.get_array().size(); ++i) { // If there are more results than documented, re-use the last doc_inner. const RPCResult& doc_inner{m_inner.at(std::min(m_inner.size() - 1, i))}; - if (!doc_inner.MatchesType(result.get_array()[i])) return false; + UniValue match{doc_inner.MatchesType(result.get_array()[i])}; + if (!match.isTrue()) errors.pushKV(strprintf("%d", i), match); } - return true; // empty result array is valid + if (errors.empty()) return true; // empty result array is valid + return errors; } - case Type::OBJ_DYN: - case Type::OBJ: { - if (UniValue::VOBJ != result.getType()) return false; + + if (UniValue::VOBJ == result.getType()) { if (!m_inner.empty() && m_inner.at(0).m_type == Type::ELISION) return true; + UniValue errors(UniValue::VOBJ); if (m_type == Type::OBJ_DYN) { const RPCResult& doc_inner{m_inner.at(0)}; // Assume all types are the same, randomly pick the first for (size_t i{0}; i < result.get_obj().size(); ++i) { - if (!doc_inner.MatchesType(result.get_obj()[i])) { - return false; - } + UniValue match{doc_inner.MatchesType(result.get_obj()[i])}; + if (!match.isTrue()) errors.pushKV(result.getKeys()[i], match); } - return true; // empty result obj is valid + if (errors.empty()) return true; // empty result obj is valid + return errors; } std::set<std::string> doc_keys; for (const auto& doc_entry : m_inner) { @@ -916,7 +991,7 @@ bool RPCResult::MatchesType(const UniValue& result) const result.getObjMap(result_obj); for (const auto& result_entry : result_obj) { if (doc_keys.find(result_entry.first) == doc_keys.end()) { - return false; // missing documentation + errors.pushKV(result_entry.first, "key returned that was not in doc"); } } @@ -924,18 +999,18 @@ bool RPCResult::MatchesType(const UniValue& result) const const auto result_it{result_obj.find(doc_entry.m_key_name)}; if (result_it == result_obj.end()) { if (!doc_entry.m_optional) { - return false; // result is missing a required key + errors.pushKV(doc_entry.m_key_name, "key missing, despite not being optional in doc"); } continue; } - if (!doc_entry.MatchesType(result_it->second)) { - return false; // wrong type - } + UniValue match{doc_entry.MatchesType(result_it->second)}; + if (!match.isTrue()) errors.pushKV(doc_entry.m_key_name, match); } - return true; + if (errors.empty()) return true; + return errors; } - } // no default case, so the compiler can warn about missing cases - NONFATAL_UNREACHABLE(); + + return true; } void RPCResult::CheckInnerDoc() const @@ -1099,3 +1174,26 @@ UniValue GetServicesNames(ServiceFlags services) return servicesNames; } + +/** Convert a vector of bilingual strings to a UniValue::VARR containing their original untranslated values. */ +[[nodiscard]] static UniValue BilingualStringsToUniValue(const std::vector<bilingual_str>& bilingual_strings) +{ + CHECK_NONFATAL(!bilingual_strings.empty()); + UniValue result{UniValue::VARR}; + for (const auto& s : bilingual_strings) { + result.push_back(s.original); + } + return result; +} + +void PushWarnings(const UniValue& warnings, UniValue& obj) +{ + if (warnings.empty()) return; + obj.pushKV("warnings", warnings); +} + +void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj) +{ + if (warnings.empty()) return; + obj.pushKV("warnings", BilingualStringsToUniValue(warnings)); +} diff --git a/src/rpc/util.h b/src/rpc/util.h index 9aa5df00b1..bb5c30a2f4 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021 The Bitcoin Core developers +// Copyright (c) 2017-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -62,18 +62,6 @@ struct UniValueType { UniValue::VType type; }; -/** - * Type-check arguments; throws JSONRPCError if wrong type given. Does not check that - * the right number of arguments are passed, just that any passed are the correct type. - */ -void RPCTypeCheck(const UniValue& params, - const std::list<UniValueType>& typesExpected, bool fAllowNull=false); - -/** - * Type-check one argument; throws JSONRPCError if wrong type given. - */ -void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected); - /* Check for expected keys/value types in an Object. */ @@ -138,6 +126,7 @@ enum class OuterType { }; struct RPCArgOptions { + bool skip_type_check{false}; std::string oneline_description{}; //!< Should be empty unless it is supposed to override the auto-generated summary line std::vector<std::string> type_str{}; //!< Should be empty unless it is supposed to override the auto-generated type strings. Vector length is either 0 or 2, m_opts.type_str.at(0) will override the type of the value in a key-value pair, m_opts.type_str.at(1) will override the type in the argument description. bool hidden{false}; //!< For testing only @@ -160,21 +149,20 @@ struct RPCArg { /** Required arg */ NO, /** - * Optional arg that is a named argument and has a default value of - * `null`. When possible, the default value should be specified. - */ - OMITTED_NAMED_ARG, - /** - * Optional argument with default value omitted because they are - * implicitly clear. That is, elements in an array or object may not - * exist by default. + * Optional argument for which the default value is omitted from + * help text for one of two reasons: + * - It's a named argument and has a default value of `null`. + * - Its default value is implicitly clear. That is, elements in an + * array may not exist by default. * When possible, the default value should be specified. */ OMITTED, }; + /** Hint for default value */ using DefaultHint = std::string; + /** Default constant value */ using Default = UniValue; - using Fallback = std::variant<Optional, /* hint for default value */ DefaultHint, /* default constant value */ Default>; + using Fallback = std::variant<Optional, DefaultHint, Default>; const std::string m_names; //!< The name of the arg (can be empty for inner args, can contain multiple aliases separated by | for named request arguments) const Type m_type; @@ -184,10 +172,10 @@ struct RPCArg { const RPCArgOptions m_opts; RPCArg( - const std::string name, - const Type type, - const Fallback fallback, - const std::string description, + std::string name, + Type type, + Fallback fallback, + std::string description, RPCArgOptions opts = {}) : m_names{std::move(name)}, m_type{std::move(type)}, @@ -199,11 +187,11 @@ struct RPCArg { } RPCArg( - const std::string name, - const Type type, - const Fallback fallback, - const std::string description, - const std::vector<RPCArg> inner, + std::string name, + Type type, + Fallback fallback, + std::string description, + std::vector<RPCArg> inner, RPCArgOptions opts = {}) : m_names{std::move(name)}, m_type{std::move(type)}, @@ -217,6 +205,12 @@ struct RPCArg { bool IsOptional() const; + /** + * Check whether the request JSON type matches. + * Returns true if type matches, or object describing error(s) if not. + */ + UniValue MatchesType(const UniValue& request) const; + /** Return the first of all aliases */ std::string GetFirstName() const; @@ -237,7 +231,7 @@ struct RPCArg { * Return the description string, including the argument type and whether * the argument is required. */ - std::string ToDescriptionString() const; + std::string ToDescriptionString(bool is_named_arg) const; }; struct RPCResult { @@ -266,12 +260,12 @@ struct RPCResult { const std::string m_cond; RPCResult( - const std::string cond, - const Type type, - const std::string m_key_name, - const bool optional, - const std::string description, - const std::vector<RPCResult> inner = {}) + std::string cond, + Type type, + std::string m_key_name, + bool optional, + std::string description, + std::vector<RPCResult> inner = {}) : m_type{std::move(type)}, m_key_name{std::move(m_key_name)}, m_inner{std::move(inner)}, @@ -285,19 +279,19 @@ struct RPCResult { } RPCResult( - const std::string cond, - const Type type, - const std::string m_key_name, - const std::string description, - const std::vector<RPCResult> inner = {}) - : RPCResult{cond, type, m_key_name, false, description, inner} {} + std::string cond, + Type type, + std::string m_key_name, + std::string description, + std::vector<RPCResult> inner = {}) + : RPCResult{std::move(cond), type, std::move(m_key_name), /*optional=*/false, std::move(description), std::move(inner)} {} RPCResult( - const Type type, - const std::string m_key_name, - const bool optional, - const std::string description, - const std::vector<RPCResult> inner = {}, + Type type, + std::string m_key_name, + bool optional, + std::string description, + std::vector<RPCResult> inner = {}, bool skip_type_check = false) : m_type{std::move(type)}, m_key_name{std::move(m_key_name)}, @@ -311,12 +305,12 @@ struct RPCResult { } RPCResult( - const Type type, - const std::string m_key_name, - const std::string description, - const std::vector<RPCResult> inner = {}, + Type type, + std::string m_key_name, + std::string description, + std::vector<RPCResult> inner = {}, bool skip_type_check = false) - : RPCResult{type, m_key_name, false, description, inner, skip_type_check} {} + : RPCResult{type, std::move(m_key_name), /*optional=*/false, std::move(description), std::move(inner), skip_type_check} {} /** Append the sections of the result. */ void ToSections(Sections& sections, OuterType outer_type = OuterType::NONE, const int current_indent = 0) const; @@ -324,8 +318,10 @@ struct RPCResult { std::string ToStringObj() const; /** Return the description string, including the result type. */ std::string ToDescriptionString() const; - /** Check whether the result JSON type matches. */ - bool MatchesType(const UniValue& result) const; + /** Check whether the result JSON type matches. + * Returns true if type matches, or object describing error(s) if not. + */ + UniValue MatchesType(const UniValue& result) const; private: void CheckInnerDoc() const; @@ -385,4 +381,13 @@ private: const RPCExamples m_examples; }; +/** + * Push warning messages to an RPC "warnings" field as a JSON array of strings. + * + * @param[in] warnings Warning messages to push. + * @param[out] obj UniValue object to push the warnings array object to. + */ +void PushWarnings(const UniValue& warnings, UniValue& obj); +void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj); + #endif // BITCOIN_RPC_UTIL_H |