diff options
Diffstat (limited to 'src/rpc')
-rw-r--r-- | src/rpc/blockchain.cpp | 34 | ||||
-rw-r--r-- | src/rpc/blockchain.h | 2 | ||||
-rw-r--r-- | src/rpc/client.cpp | 35 | ||||
-rw-r--r-- | src/rpc/client.h | 5 | ||||
-rw-r--r-- | src/rpc/external_signer.cpp | 4 | ||||
-rw-r--r-- | src/rpc/mempool.cpp | 25 | ||||
-rw-r--r-- | src/rpc/mining.cpp | 35 | ||||
-rw-r--r-- | src/rpc/net.cpp | 36 | ||||
-rw-r--r-- | src/rpc/output_script.cpp | 2 | ||||
-rw-r--r-- | src/rpc/rawtransaction.cpp | 158 | ||||
-rw-r--r-- | src/rpc/rawtransaction_util.cpp | 43 | ||||
-rw-r--r-- | src/rpc/rawtransaction_util.h | 7 | ||||
-rw-r--r-- | src/rpc/request.cpp | 10 | ||||
-rw-r--r-- | src/rpc/server.cpp | 4 | ||||
-rw-r--r-- | src/rpc/server_util.cpp | 15 | ||||
-rw-r--r-- | src/rpc/server_util.h | 3 | ||||
-rw-r--r-- | src/rpc/txoutproof.cpp | 4 | ||||
-rw-r--r-- | src/rpc/util.cpp | 90 | ||||
-rw-r--r-- | src/rpc/util.h | 21 |
19 files changed, 341 insertions, 192 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 9d8e0ceb61..11484a9d8d 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -9,13 +9,13 @@ #include <chain.h> #include <chainparams.h> #include <coins.h> +#include <common/args.h> #include <consensus/amount.h> #include <consensus/params.h> #include <consensus/validation.h> #include <core_io.h> #include <deploymentinfo.h> #include <deploymentstatus.h> -#include <fs.h> #include <hash.h> #include <index/blockfilterindex.h> #include <index/coinstatsindex.h> @@ -39,8 +39,8 @@ #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> #include <validation.h> #include <validationinterface.h> @@ -432,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"}, @@ -460,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"); } @@ -565,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; @@ -775,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; @@ -1109,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", "") @@ -1124,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; }, }; } @@ -1247,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(); @@ -1266,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()); } } @@ -2105,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" diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index 9ccb87b78a..0a86085db0 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -7,9 +7,9 @@ #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> diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 5fe914f0a1..f3c19003ff 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -3,11 +3,14 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <common/args.h> #include <rpc/client.h> -#include <util/system.h> +#include <tinyformat.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" }, @@ -114,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" }, @@ -228,15 +233,15 @@ public: CRPCConvertTable(); /** Return arg_value as UniValue, and first parse it if it is a non-string parameter */ - UniValue ArgToUniValue(const std::string& arg_value, const std::string& method, int param_idx) + UniValue ArgToUniValue(std::string_view arg_value, const std::string& method, int param_idx) { - return members.count(std::make_pair(method, param_idx)) > 0 ? ParseNonRFCJSONValue(arg_value) : arg_value; + return members.count({method, param_idx}) > 0 ? ParseNonRFCJSONValue(arg_value) : arg_value; } /** Return arg_value as UniValue, and first parse it if it is a non-string parameter */ - UniValue ArgToUniValue(const std::string& arg_value, const std::string& method, const std::string& param_name) + UniValue ArgToUniValue(std::string_view arg_value, const std::string& method, const std::string& param_name) { - return membersByName.count(std::make_pair(method, param_name)) > 0 ? ParseNonRFCJSONValue(arg_value) : arg_value; + return membersByName.count({method, param_name}) > 0 ? ParseNonRFCJSONValue(arg_value) : arg_value; } }; @@ -253,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) @@ -267,8 +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]; - params.push_back(rpcCvtTable.ArgToUniValue(strVal, strMethod, idx)); + std::string_view value{strParams[idx]}; + params.push_back(rpcCvtTable.ArgToUniValue(value, strMethod, idx)); } return params; @@ -279,15 +282,15 @@ 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.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)}; // Intentionally overwrite earlier named values with later ones as a // convenience for scripts and command line users that want to merge 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 f5a6913572..1e139c9990 100644 --- a/src/rpc/external_signer.cpp +++ b/src/rpc/external_signer.cpp @@ -3,11 +3,13 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chainparamsbase.h> +#include <common/args.h> #include <external_signer.h> +#include <rpc/protocol.h> #include <rpc/server.h> #include <rpc/util.h> #include <util/strencodings.h> -#include <rpc/protocol.h> +#include <util/system.h> #include <string> #include <vector> diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 44f7435a26..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,8 +17,10 @@ #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> @@ -44,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" @@ -61,10 +66,19 @@ static RPCHelpMan sendrawtransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + 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() ? @@ -839,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}; diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 8753f845a5..d55e20ba5e 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -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; }, }; diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index f0e5b90509..7ffa777ef4 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -196,7 +196,7 @@ static RPCHelpMan getpeerinfo() 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); @@ -496,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); } @@ -571,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); } @@ -664,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); @@ -702,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; @@ -726,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"); } @@ -741,12 +739,12 @@ static RPCHelpMan setban() } 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); } @@ -754,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."); } } @@ -785,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); @@ -825,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; }, @@ -909,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/output_script.cpp b/src/rpc/output_script.cpp index bb04f58424..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; }, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 39f3260ca6..4a918cbd42 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -54,8 +54,11 @@ using node::PSBTAnalysis; using node::ReadBlockFromDisk; using node::UndoReadFromDisk; -static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, Chainstate& active_chainstate, const CTxUndo* txundo = nullptr, TxVerbosity verbosity = TxVerbosity::SHOW_TXID) +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 @@ -169,6 +172,91 @@ static std::vector<RPCArg> CreateTxDoc() }; } +// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors +PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider) +{ + // Unserialize the transactions + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodeBase64PSBT(psbtx, psbt_string, error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); + } + + if (g_txindex) g_txindex->BlockUntilSyncedToCurrentChain(); + const NodeContext& node = EnsureAnyNodeContext(context); + + // If we can't find the corresponding full transaction for all of our inputs, + // this will be used to find just the utxos for the segwit inputs for which + // the full transaction isn't found + std::map<COutPoint, Coin> coins; + + // Fetch previous transactions: + // First, look in the txindex and the mempool + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInput& psbt_input = psbtx.inputs.at(i); + const CTxIn& tx_in = psbtx.tx->vin.at(i); + + // The `non_witness_utxo` is the whole previous transaction + if (psbt_input.non_witness_utxo) continue; + + CTransactionRef tx; + + // Look in the txindex + if (g_txindex) { + uint256 block_hash; + g_txindex->FindTx(tx_in.prevout.hash, block_hash, tx); + } + // If we still don't have it look in the mempool + if (!tx) { + tx = node.mempool->get(tx_in.prevout.hash); + } + if (tx) { + psbt_input.non_witness_utxo = tx; + } else { + coins[tx_in.prevout]; // Create empty map entry keyed by prevout + } + } + + // If we still haven't found all of the inputs, look for the missing ones in the utxo set + if (!coins.empty()) { + FindCoins(node, coins); + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInput& input = psbtx.inputs.at(i); + + // If there are still missing utxos, add them if they were found in the utxo set + if (!input.non_witness_utxo) { + const CTxIn& tx_in = psbtx.tx->vin.at(i); + const Coin& coin = coins.at(tx_in.prevout); + if (!coin.out.IsNull() && IsSegWitOutput(provider, coin.out.scriptPubKey)) { + input.witness_utxo = coin.out; + } + } + } + } + + const PrecomputedTransactionData& txdata = PrecomputePSBTData(psbtx); + + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + if (PSBTInputSigned(psbtx.inputs.at(i))) { + continue; + } + + // Update script/keypath information using descriptor data. + // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures + // we don't actually care about those here, in fact. + SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, /*sighash=*/1); + } + + // Update script/keypath information using descriptor data. + for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { + UpdatePSBTOutput(provider, psbtx, i); + } + + RemoveUnnecessaryTransactions(psbtx, /*sighash_type=*/1); + + return psbtx; +} + static RPCHelpMan getrawtransaction() { return RPCHelpMan{ @@ -213,10 +301,10 @@ static RPCHelpMan getrawtransaction() {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, omitted if block undo data is not available", + {RPCResult::Type::OBJ, "", "utxo being spent", { {RPCResult::Type::ELISION, "", "Same output as verbosity = 1"}, - {RPCResult::Type::OBJ, "prevout", /*optional=*/true, "Only if undo information is available)", + {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"}, @@ -519,15 +607,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); } @@ -1575,7 +1665,7 @@ static RPCHelpMan converttopsbt() static RPCHelpMan utxoupdatepsbt() { return RPCHelpMan{"utxoupdatepsbt", - "\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set or the mempool.\n", + "\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set, txindex, or the mempool.\n", { {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"}, {"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of either strings or objects", { @@ -1594,13 +1684,6 @@ static RPCHelpMan utxoupdatepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - // Unserialize the transactions - PartiallySignedTransaction psbtx; - std::string error; - if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); - } - // Parse descriptors, if any. FlatSigningProvider provider; if (!request.params[1].isNull()) { @@ -1609,53 +1692,12 @@ static RPCHelpMan utxoupdatepsbt() EvalDescriptorStringOrObject(descs[i], provider); } } - // We don't actually need private keys further on; hide them as a precaution. - HidingSigningProvider public_provider(&provider, /*hide_secret=*/true, /*hide_origin=*/false); - - // Fetch previous transactions (inputs): - CCoinsView viewDummy; - CCoinsViewCache view(&viewDummy); - { - NodeContext& node = EnsureAnyNodeContext(request.context); - const CTxMemPool& mempool = EnsureMemPool(node); - ChainstateManager& chainman = EnsureChainman(node); - LOCK2(cs_main, mempool.cs); - CCoinsViewCache &viewChain = chainman.ActiveChainstate().CoinsTip(); - CCoinsViewMemPool viewMempool(&viewChain, mempool); - view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view - - for (const CTxIn& txin : psbtx.tx->vin) { - view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail. - } - - view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long - } - - // Fill the inputs - const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); - for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - PSBTInput& input = psbtx.inputs.at(i); - if (input.non_witness_utxo || !input.witness_utxo.IsNull()) { - continue; - } - - const Coin& coin = view.AccessCoin(psbtx.tx->vin[i].prevout); - - if (IsSegWitOutput(provider, coin.out.scriptPubKey)) { - input.witness_utxo = coin.out; - } - - // Update script/keypath information using descriptor data. - // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures - // we don't actually care about those here, in fact. - SignPSBTInput(public_provider, psbtx, i, &txdata, /*sighash=*/1); - } - - // Update script/keypath information using descriptor data. - for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { - UpdatePSBTOutput(public_provider, psbtx, i); - } + // We don't actually need private keys further on; hide them as a precaution. + const PartiallySignedTransaction& psbtx = ProcessPSBT( + request.params[0].get_str(), + request.context, + HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false)); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 15b8e1dcd0..3ba930f84f 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -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 0c3823bc1e..a863432b7a 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -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/request.cpp b/src/rpc/request.cpp index 0bb5533d71..ad91ed0f23 100644 --- a/src/rpc/request.cpp +++ b/src/rpc/request.cpp @@ -5,11 +5,13 @@ #include <rpc/request.h> -#include <fs.h> +#include <util/fs.h> +#include <common/args.h> +#include <logging.h> #include <random.h> #include <rpc/protocol.h> -#include <util/system.h> +#include <util/fs_helpers.h> #include <util/strencodings.h> #include <fstream> @@ -75,7 +77,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 +88,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 44d7e2676b..354f949002 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -5,6 +5,8 @@ #include <rpc/server.h> +#include <common/args.h> +#include <logging.h> #include <rpc/util.h> #include <shutdown.h> #include <sync.h> @@ -83,6 +85,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())); @@ -513,6 +516,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 50f9ce7b3c..13d007b496 100644 --- a/src/rpc/server_util.cpp +++ b/src/rpc/server_util.cpp @@ -4,6 +4,7 @@ #include <rpc/server_util.h> +#include <common/args.h> #include <net_processing.h> #include <node/context.h> #include <policy/fees.h> @@ -39,6 +40,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 fa008a8155..9af9572431 100644 --- a/src/rpc/server_util.h +++ b/src/rpc/server_util.h @@ -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 8eae2ef884..24b5d04115 100644 --- a/src/rpc/txoutproof.cpp +++ b/src/rpc/txoutproof.cpp @@ -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 12877e94df..1f3f37d0a0 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <clientversion.h> +#include <common/args.h> #include <consensus/amount.h> #include <key_io.h> #include <outputtype.h> @@ -13,7 +14,6 @@ #include <util/check.h> #include <util/strencodings.h> #include <util/string.h> -#include <util/system.h> #include <util/translation.h> #include <tuple> @@ -31,14 +31,6 @@ std::string GetAllOutputTypes() return Join(ret, ", "); } -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, @@ -564,8 +556,16 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const if (request.mode == JSONRPCRequest::GET_HELP || !IsValidNumArgs(request.params.size())) { throw std::runtime_error(ToString()); } + UniValue arg_mismatch{UniValue::VOBJ}; for (size_t i{0}; i < m_args.size(); ++i) { - m_args.at(i).MatchesType(request.params[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)) { @@ -608,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); } @@ -684,52 +685,60 @@ UniValue RPCHelpMan::GetArgMap() const return arr; } -void RPCArg::MatchesType(const UniValue& request) const +static std::optional<UniValue::VType> ExpectedType(RPCArg::Type type) { - if (m_opts.skip_type_check) return; - if (IsOptional() && request.isNull()) return; - switch (m_type) { + using Type = RPCArg::Type; + switch (type) { case Type::STR_HEX: case Type::STR: { - RPCTypeCheckArgument(request, UniValue::VSTR); - return; + return UniValue::VSTR; } case Type::NUM: { - RPCTypeCheckArgument(request, UniValue::VNUM); - return; + return UniValue::VNUM; } case Type::AMOUNT: { // VNUM or VSTR, checked inside AmountFromValue() - return; + return std::nullopt; } case Type::RANGE: { // VNUM or VARR, checked inside ParseRange() - return; + return std::nullopt; } case Type::BOOL: { - RPCTypeCheckArgument(request, UniValue::VBOOL); - return; + return UniValue::VBOOL; } case Type::OBJ: case Type::OBJ_USER_KEYS: { - RPCTypeCheckArgument(request, UniValue::VOBJ); - return; + return UniValue::VOBJ; } case Type::ARR: { - RPCTypeCheckArgument(request, UniValue::VARR); - return; + 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; } @@ -902,7 +911,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const NONFATAL_UNREACHABLE(); } -static const std::optional<UniValue::VType> ExpectedType(RPCResult::Type type) +static std::optional<UniValue::VType> ExpectedType(RPCResult::Type type) { using Type = RPCResult::Type; switch (type) { @@ -1165,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 49d98c8365..bb5c30a2f4 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -62,11 +62,6 @@ struct UniValueType { UniValue::VType type; }; -/** - * 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. */ @@ -210,8 +205,11 @@ struct RPCArg { bool IsOptional() const; - /** Check whether the request JSON type matches. */ - void MatchesType(const UniValue& request) 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; @@ -383,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 |