diff options
Diffstat (limited to 'src/rpc')
-rw-r--r-- | src/rpc/blockchain.cpp | 88 | ||||
-rw-r--r-- | src/rpc/client.cpp | 7 | ||||
-rw-r--r-- | src/rpc/external_signer.cpp | 2 | ||||
-rw-r--r-- | src/rpc/fees.cpp | 10 | ||||
-rw-r--r-- | src/rpc/mempool.cpp | 52 | ||||
-rw-r--r-- | src/rpc/mining.cpp | 26 | ||||
-rw-r--r-- | src/rpc/net.cpp | 42 | ||||
-rw-r--r-- | src/rpc/node.cpp | 11 | ||||
-rw-r--r-- | src/rpc/output_script.cpp | 6 | ||||
-rw-r--r-- | src/rpc/register.h | 2 | ||||
-rw-r--r-- | src/rpc/server.cpp | 2 | ||||
-rw-r--r-- | src/rpc/signmessage.cpp | 6 | ||||
-rw-r--r-- | src/rpc/util.cpp | 53 | ||||
-rw-r--r-- | src/rpc/util.h | 71 |
14 files changed, 272 insertions, 106 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 34d308211b..c9997ae063 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -17,6 +17,7 @@ #include <core_io.h> #include <deploymentinfo.h> #include <deploymentstatus.h> +#include <flatfile.h> #include <hash.h> #include <index/blockfilterindex.h> #include <index/coinstatsindex.h> @@ -46,7 +47,6 @@ #include <validation.h> #include <validationinterface.h> #include <versionbits.h> -#include <warnings.h> #include <stdint.h> @@ -395,7 +395,8 @@ static RPCHelpMan syncwithvalidationinterfacequeue() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - SyncWithValidationInterfaceQueue(); + NodeContext& node = EnsureAnyNodeContext(request.context); + CHECK_NONFATAL(node.validation_signals)->SyncWithValidationInterfaceQueue(); return UniValue::VNULL; }, }; @@ -594,6 +595,28 @@ static CBlock GetBlockChecked(BlockManager& blockman, const CBlockIndex& blockin return block; } +static std::vector<uint8_t> GetRawBlockChecked(BlockManager& blockman, const CBlockIndex& blockindex) +{ + std::vector<uint8_t> data{}; + FlatFilePos pos{}; + { + LOCK(cs_main); + if (blockman.IsBlockPruned(blockindex)) { + throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)"); + } + pos = blockindex.GetBlockPos(); + } + + if (!blockman.ReadRawBlockFromDisk(data, pos)) { + // Block not found on disk. This could be because we have the block + // header in our index but not yet have the block or did not accept the + // block. Or if the block was pruned right after we released the lock above. + throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk"); + } + + return data; +} + static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex& blockindex) { CBlockUndo blockUndo; @@ -734,15 +757,16 @@ static RPCHelpMan getblock() } } - const CBlock block{GetBlockChecked(chainman.m_blockman, *pblockindex)}; + const std::vector<uint8_t> block_data{GetRawBlockChecked(chainman.m_blockman, *pblockindex)}; if (verbosity <= 0) { - DataStream ssBlock; - ssBlock << TX_WITH_WITNESS(block); - std::string strHex = HexStr(ssBlock); - return strHex; + return HexStr(block_data); } + DataStream block_stream{block_data}; + CBlock block{}; + block_stream >> TX_WITH_WITNESS(block); + TxVerbosity tx_verbosity; if (verbosity == 1) { tx_verbosity = TxVerbosity::SHOW_TXID; @@ -1235,7 +1259,14 @@ RPCHelpMan getblockchaininfo() {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "height of the last block pruned, plus one (only present if pruning is enabled)"}, {RPCResult::Type::BOOL, "automatic_pruning", /*optional=*/true, "whether automatic pruning is enabled (only present if pruning is enabled)"}, {RPCResult::Type::NUM, "prune_target_size", /*optional=*/true, "the target size used by pruning (only present if automatic pruning is enabled)"}, - {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"}, + (IsDeprecatedRPCEnabled("warnings") ? + RPCResult{RPCResult::Type::STR, "warnings", "any network and blockchain warnings (DEPRECATED)"} : + RPCResult{RPCResult::Type::ARR, "warnings", "any network and blockchain warnings (run with `-deprecatedrpc=warnings` to return the latest warning as a single string)", + { + {RPCResult::Type::STR, "", "warning"}, + } + } + ), }}, RPCExamples{ HelpExampleCli("getblockchaininfo", "") @@ -1273,7 +1304,7 @@ RPCHelpMan getblockchaininfo() } } - obj.pushKV("warnings", GetWarnings(false).original); + obj.pushKV("warnings", GetNodeWarnings(IsDeprecatedRPCEnabled("warnings"))); return obj; }, }; @@ -2149,7 +2180,8 @@ static RPCHelpMan scantxoutset() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { UniValue result(UniValue::VOBJ); - if (request.params[0].get_str() == "status") { + const auto action{self.Arg<std::string>("action")}; + if (action == "status") { CoinsViewScanReserver reserver; if (reserver.reserve()) { // no scan in progress @@ -2157,7 +2189,7 @@ static RPCHelpMan scantxoutset() } result.pushKV("progress", g_scan_progress.load()); return result; - } else if (request.params[0].get_str() == "abort") { + } else if (action == "abort") { CoinsViewScanReserver reserver; if (reserver.reserve()) { // reserve was possible which means no scan was running @@ -2166,7 +2198,7 @@ static RPCHelpMan scantxoutset() // set the abort flag g_should_abort_scan = true; return true; - } else if (request.params[0].get_str() == "start") { + } else if (action == "start") { CoinsViewScanReserver reserver; if (!reserver.reserve()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\""); @@ -2235,7 +2267,7 @@ static RPCHelpMan scantxoutset() result.pushKV("unspents", unspents); result.pushKV("total_amount", ValueFromAmount(total_in)); } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", request.params[0].get_str())); + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", action)); } return result; }, @@ -2571,7 +2603,7 @@ static RPCHelpMan dumptxoutset() { return RPCHelpMan{ "dumptxoutset", - "Write the serialized UTXO set to disk.", + "Write the serialized UTXO set to a file.", { {"path", RPCArg::Type::STR, RPCArg::Optional::NO, "Path to the output file. If relative, will be prefixed by datadir."}, }, @@ -2699,7 +2731,7 @@ static RPCHelpMan loadtxoutset() { return RPCHelpMan{ "loadtxoutset", - "Load the serialized UTXO set from disk.\n" + "Load the serialized UTXO set from a file.\n" "Once this snapshot is loaded, its contents will be " "deserialized into a second chainstate data structure, which is then used to sync to " "the network's tip. " @@ -2753,34 +2785,14 @@ static RPCHelpMan loadtxoutset() throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to load UTXO snapshot, " "assumeutxo block hash in snapshot metadata not recognized (%s)", base_blockhash.ToString())); } - int max_secs_to_wait_for_headers = 60 * 10; - CBlockIndex* snapshot_start_block = nullptr; - - LogPrintf("[snapshot] waiting to see blockheader %s in headers chain before snapshot activation\n", - base_blockhash.ToString()); - - while (max_secs_to_wait_for_headers > 0) { - snapshot_start_block = WITH_LOCK(::cs_main, + CBlockIndex* snapshot_start_block = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(base_blockhash)); - max_secs_to_wait_for_headers -= 1; - - if (!IsRPCRunning()) { - throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down"); - } - - if (!snapshot_start_block) { - std::this_thread::sleep_for(std::chrono::seconds(1)); - } else { - break; - } - } if (!snapshot_start_block) { - LogPrintf("[snapshot] timed out waiting for snapshot start blockheader %s\n", - base_blockhash.ToString()); throw JSONRPCError( RPC_INTERNAL_ERROR, - "Timed out waiting for base block header to appear in headers chain"); + strprintf("The base block header (%s) must appear in the headers chain. Make sure all headers are syncing, and call this RPC again.", + base_blockhash.ToString())); } if (!chainman.ActivateSnapshot(afile, metadata, false)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to load UTXO snapshot " + fs::PathToString(path)); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 5825efdf82..b8dc148eae 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -128,6 +128,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "testmempoolaccept", 0, "rawtxs" }, { "testmempoolaccept", 1, "maxfeerate" }, { "submitpackage", 0, "package" }, + { "submitpackage", 1, "maxfeerate" }, + { "submitpackage", 2, "maxburnamount" }, { "combinerawtransaction", 0, "txs" }, { "fundrawtransaction", 1, "options" }, { "fundrawtransaction", 1, "add_inputs"}, @@ -275,6 +277,11 @@ static const CRPCConvertParam vRPCConvertParams[] = { "logging", 1, "exclude" }, { "disconnectnode", 1, "nodeid" }, { "upgradewallet", 0, "version" }, + { "gethdkeys", 0, "active_only" }, + { "gethdkeys", 0, "options" }, + { "gethdkeys", 0, "private" }, + { "createwalletdescriptor", 1, "options" }, + { "createwalletdescriptor", 1, "internal" }, // Echo with conversion (For testing only) { "echojson", 0, "arg0" }, { "echojson", 1, "arg1" }, diff --git a/src/rpc/external_signer.cpp b/src/rpc/external_signer.cpp index 310eec5f15..27c7394909 100644 --- a/src/rpc/external_signer.cpp +++ b/src/rpc/external_signer.cpp @@ -2,6 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <config/bitcoin-config.h> // IWYU pragma: keep + #include <common/args.h> #include <common/system.h> #include <external_signer.h> diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp index 57ba486ed9..f3345b4f1c 100644 --- a/src/rpc/fees.cpp +++ b/src/rpc/fees.cpp @@ -4,6 +4,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <core_io.h> +#include <node/context.h> #include <policy/feerate.h> #include <policy/fees.h> #include <rpc/protocol.h> @@ -21,10 +22,6 @@ #include <cmath> #include <string> -namespace node { -struct NodeContext; -} - using node::NodeContext; static RPCHelpMan estimatesmartfee() @@ -68,7 +65,7 @@ static RPCHelpMan estimatesmartfee() const NodeContext& node = EnsureAnyNodeContext(request.context); const CTxMemPool& mempool = EnsureMemPool(node); - SyncWithValidationInterfaceQueue(); + CHECK_NONFATAL(mempool.m_signals)->SyncWithValidationInterfaceQueue(); unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); bool conservative = true; @@ -156,8 +153,9 @@ static RPCHelpMan estimaterawfee() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); + const NodeContext& node = EnsureAnyNodeContext(request.context); - SyncWithValidationInterfaceQueue(); + CHECK_NONFATAL(node.validation_signals)->SyncWithValidationInterfaceQueue(); unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); double threshold = 0.95; diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index b04e7c6542..0672ec34b3 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -28,6 +28,7 @@ using kernel::DumpMempool; +using node::DEFAULT_MAX_BURN_AMOUNT; using node::DEFAULT_MAX_RAW_TX_FEE_RATE; using node::MempoolPath; using node::NodeContext; @@ -45,8 +46,8 @@ 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."}, - {"maxburnamount", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(0)}, + "/kvB.\nFee rates larger than 1BTC/kvB are rejected.\nSet to 0 to accept any fee rate."}, + {"maxburnamount", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_BURN_AMOUNT)}, "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"}, @@ -81,9 +82,7 @@ static RPCHelpMan sendrawtransaction() CTransactionRef tx(MakeTransactionRef(std::move(mtx))); - const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ? - DEFAULT_MAX_RAW_TX_FEE_RATE : - CFeeRate(AmountFromValue(request.params[1])); + const CFeeRate max_raw_tx_fee_rate{ParseFeeRate(self.Arg<UniValue>(1))}; int64_t virtual_size = GetVirtualTransactionSize(*tx); CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); @@ -117,7 +116,8 @@ static RPCHelpMan testmempoolaccept() }, }, {"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\n"}, + "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + + "/kvB.\nFee rates larger than 1BTC/kvB are rejected.\nSet to 0 to accept any fee rate."}, }, RPCResult{ RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n" @@ -162,9 +162,7 @@ static RPCHelpMan testmempoolaccept() "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions."); } - const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ? - DEFAULT_MAX_RAW_TX_FEE_RATE : - CFeeRate(AmountFromValue(request.params[1])); + const CFeeRate max_raw_tx_fee_rate{ParseFeeRate(self.Arg<UniValue>(1))}; std::vector<CTransactionRef> txns; txns.reserve(raw_transactions.size()); @@ -183,7 +181,7 @@ static RPCHelpMan testmempoolaccept() Chainstate& chainstate = chainman.ActiveChainstate(); const PackageMempoolAcceptResult package_result = [&] { LOCK(::cs_main); - if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true); + if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true, /*client_maxfeerate=*/{}); return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(), chainman.ProcessTransaction(txns[0], /*test_accept=*/true)); }(); @@ -827,6 +825,14 @@ static RPCHelpMan submitpackage() {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""}, }, }, + {"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.\nFee rates larger than 1BTC/kvB are rejected.\nSet to 0 to accept any fee rate."}, + {"maxburnamount", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_BURN_AMOUNT)}, + "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::OBJ, "", "", @@ -866,6 +872,17 @@ static RPCHelpMan submitpackage() "Array must contain between 2 and " + ToString(MAX_PACKAGE_COUNT) + " transactions."); } + // Fee check needs to be run with chainstate and package context + const CFeeRate max_raw_tx_fee_rate = ParseFeeRate(self.Arg<UniValue>(1)); + std::optional<CFeeRate> client_maxfeerate{max_raw_tx_fee_rate}; + // 0-value is special; it's mapped to no sanity check + if (max_raw_tx_fee_rate == CFeeRate(0)) { + client_maxfeerate = std::nullopt; + } + + // Burn sanity check is run with no context + const CAmount max_burn_amount = request.params[2].isNull() ? 0 : AmountFromValue(request.params[2]); + std::vector<CTransactionRef> txns; txns.reserve(raw_transactions.size()); for (const auto& rawtx : raw_transactions.getValues()) { @@ -874,6 +891,13 @@ static RPCHelpMan submitpackage() throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed: " + rawtx.get_str() + " 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); + } + } + txns.emplace_back(MakeTransactionRef(std::move(mtx))); } if (!IsChildWithParentsTree(txns)) { @@ -883,7 +907,7 @@ static RPCHelpMan submitpackage() NodeContext& node = EnsureAnyNodeContext(request.context); CTxMemPool& mempool = EnsureMemPool(node); Chainstate& chainstate = EnsureChainman(node).ActiveChainstate(); - const auto package_result = WITH_LOCK(::cs_main, return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/ false)); + const auto package_result = WITH_LOCK(::cs_main, return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/ false, client_maxfeerate)); std::string package_msg = "success"; @@ -971,10 +995,8 @@ static RPCHelpMan submitpackage() 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()) { - replaced_txids.insert(ptx->GetHash()); - } + for (const auto& ptx : it->second.m_replaced_transactions) { + replaced_txids.insert(ptx->GetHash()); } break; } diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index f1abfb6396..73987669a5 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -3,6 +3,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <config/bitcoin-config.h> // IWYU pragma: keep + #include <chain.h> #include <chainparams.h> #include <common/system.h> @@ -35,7 +37,6 @@ #include <util/translation.h> #include <validation.h> #include <validationinterface.h> -#include <warnings.h> #include <memory> #include <stdint.h> @@ -118,7 +119,7 @@ static RPCHelpMan getnetworkhashps() { ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - return GetNetworkHashPS(self.Arg<int>(0), self.Arg<int>(1), chainman.ActiveChain()); + return GetNetworkHashPS(self.Arg<int>("nblocks"), self.Arg<int>("height"), chainman.ActiveChain()); }, }; } @@ -225,12 +226,12 @@ static RPCHelpMan generatetodescriptor() "\nGenerate 11 blocks to mydesc\n" + HelpExampleCli("generatetodescriptor", "11 \"mydesc\"")}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const auto num_blocks{self.Arg<int>(0)}; - const auto max_tries{self.Arg<uint64_t>(2)}; + const auto num_blocks{self.Arg<int>("num_blocks")}; + const auto max_tries{self.Arg<uint64_t>("maxtries")}; CScript coinbase_script; std::string error; - if (!getScriptFromDescriptor(self.Arg<std::string>(1), coinbase_script, error)) { + if (!getScriptFromDescriptor(self.Arg<std::string>("descriptor"), coinbase_script, error)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } @@ -422,7 +423,14 @@ static RPCHelpMan getmininginfo() {RPCResult::Type::NUM, "networkhashps", "The network hashes per second"}, {RPCResult::Type::NUM, "pooledtx", "The size of the mempool"}, {RPCResult::Type::STR, "chain", "current network name (main, test, signet, regtest)"}, - {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"}, + (IsDeprecatedRPCEnabled("warnings") ? + RPCResult{RPCResult::Type::STR, "warnings", "any network and blockchain warnings (DEPRECATED)"} : + RPCResult{RPCResult::Type::ARR, "warnings", "any network and blockchain warnings (run with `-deprecatedrpc=warnings` to return the latest warning as a single string)", + { + {RPCResult::Type::STR, "", "warning"}, + } + } + ), }}, RPCExamples{ HelpExampleCli("getmininginfo", "") @@ -444,7 +452,7 @@ static RPCHelpMan getmininginfo() obj.pushKV("networkhashps", getnetworkhashps().HandleRequest(request)); obj.pushKV("pooledtx", (uint64_t)mempool.size()); obj.pushKV("chain", chainman.GetParams().GetChainTypeString()); - obj.pushKV("warnings", GetWarnings(false).original); + obj.pushKV("warnings", GetNodeWarnings(IsDeprecatedRPCEnabled("warnings"))); return obj; }, }; @@ -1038,9 +1046,9 @@ static RPCHelpMan submitblock() bool new_block; auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash()); - RegisterSharedValidationInterface(sc); + CHECK_NONFATAL(chainman.m_options.signals)->RegisterSharedValidationInterface(sc); bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block); - UnregisterSharedValidationInterface(sc); + CHECK_NONFATAL(chainman.m_options.signals)->UnregisterSharedValidationInterface(sc); if (!new_block && accepted) { return "duplicate"; } diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 5e6f42b596..04f9410b32 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -23,14 +23,12 @@ #include <rpc/server_util.h> #include <rpc/util.h> #include <sync.h> -#include <timedata.h> #include <util/chaintype.h> #include <util/strencodings.h> #include <util/string.h> #include <util/time.h> #include <util/translation.h> #include <validation.h> -#include <warnings.h> #include <optional> @@ -239,7 +237,7 @@ static RPCHelpMan getpeerinfo() obj.pushKV("bytessent", stats.nSendBytes); obj.pushKV("bytesrecv", stats.nRecvBytes); obj.pushKV("conntime", count_seconds(stats.m_connected)); - obj.pushKV("timeoffset", stats.nTimeOffset); + obj.pushKV("timeoffset", Ticks<std::chrono::seconds>(statestats.time_offset)); if (stats.m_last_ping_time > 0us) { obj.pushKV("pingtime", Ticks<SecondsDouble>(stats.m_last_ping_time)); } @@ -322,7 +320,7 @@ static RPCHelpMan addnode() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const std::string command{request.params[1].get_str()}; + const auto command{self.Arg<std::string>("command")}; if (command != "onetry" && command != "add" && command != "remove") { throw std::runtime_error( self.ToString()); @@ -331,9 +329,9 @@ static RPCHelpMan addnode() NodeContext& node = EnsureAnyNodeContext(request.context); CConnman& connman = EnsureConnman(node); - const std::string node_arg{request.params[0].get_str()}; + const auto node_arg{self.Arg<std::string>("node")}; bool node_v2transport = connman.GetLocalServices() & NODE_P2P_V2; - bool use_v2transport = self.MaybeArg<bool>(2).value_or(node_v2transport); + bool use_v2transport = self.MaybeArg<bool>("v2transport").value_or(node_v2transport); if (use_v2transport && !node_v2transport) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: v2transport requested but not enabled (see -v2transport)"); @@ -607,8 +605,8 @@ static UniValue GetNetworksInfo() obj.pushKV("name", GetNetworkName(network)); obj.pushKV("limited", !g_reachable_nets.Contains(network)); obj.pushKV("reachable", g_reachable_nets.Contains(network)); - obj.pushKV("proxy", proxy.IsValid() ? proxy.proxy.ToStringAddrPort() : std::string()); - obj.pushKV("proxy_randomize_credentials", proxy.randomize_credentials); + obj.pushKV("proxy", proxy.IsValid() ? proxy.ToString() : std::string()); + obj.pushKV("proxy_randomize_credentials", proxy.m_randomize_credentials); networks.push_back(obj); } return networks; @@ -658,7 +656,14 @@ static RPCHelpMan getnetworkinfo() {RPCResult::Type::NUM, "score", "relative score"}, }}, }}, - {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"}, + (IsDeprecatedRPCEnabled("warnings") ? + RPCResult{RPCResult::Type::STR, "warnings", "any network and blockchain warnings (DEPRECATED)"} : + RPCResult{RPCResult::Type::ARR, "warnings", "any network and blockchain warnings (run with `-deprecatedrpc=warnings` to return the latest warning as a single string)", + { + {RPCResult::Type::STR, "", "warning"}, + } + } + ), } }, RPCExamples{ @@ -679,9 +684,10 @@ static RPCHelpMan getnetworkinfo() obj.pushKV("localservicesnames", GetServicesNames(services)); } if (node.peerman) { - obj.pushKV("localrelay", !node.peerman->IgnoresIncomingTxs()); + auto peerman_info{node.peerman->GetInfo()}; + obj.pushKV("localrelay", !peerman_info.ignores_incoming_txs); + obj.pushKV("timeoffset", Ticks<std::chrono::seconds>(peerman_info.median_outbound_time_offset)); } - obj.pushKV("timeoffset", GetTimeOffset()); if (node.connman) { obj.pushKV("networkactive", node.connman->GetNetworkActive()); obj.pushKV("connections", node.connman->GetNodeCount(ConnectionDirection::Both)); @@ -707,7 +713,7 @@ static RPCHelpMan getnetworkinfo() } } obj.pushKV("localaddresses", localAddresses); - obj.pushKV("warnings", GetWarnings(false).original); + obj.pushKV("warnings", GetNodeWarnings(IsDeprecatedRPCEnabled("warnings"))); return obj; }, }; @@ -951,7 +957,7 @@ static RPCHelpMan getnodeaddresses() static RPCHelpMan addpeeraddress() { return RPCHelpMan{"addpeeraddress", - "\nAdd the address of a potential peer to the address manager. This RPC is for testing only.\n", + "Add the address of a potential peer to an address manager table. This RPC is for testing only.", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The IP address of the peer"}, {"port", RPCArg::Type::NUM, RPCArg::Optional::NO, "The port of the peer"}, @@ -960,7 +966,8 @@ static RPCHelpMan addpeeraddress() RPCResult{ RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::BOOL, "success", "whether the peer address was successfully added to the address manager"}, + {RPCResult::Type::BOOL, "success", "whether the peer address was successfully added to the address manager table"}, + {RPCResult::Type::STR, "error", /*optional=*/true, "error description, if the address could not be added"}, }, }, RPCExamples{ @@ -989,8 +996,13 @@ static RPCHelpMan addpeeraddress() success = true; if (tried) { // Attempt to move the address to the tried addresses table. - addrman.Good(address); + if (!addrman.Good(address)) { + success = false; + obj.pushKV("error", "failed-adding-to-tried"); + } } + } else { + obj.pushKV("error", "failed-adding-to-new"); } } diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp index b085828215..447be2cf64 100644 --- a/src/rpc/node.cpp +++ b/src/rpc/node.cpp @@ -3,6 +3,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <config/bitcoin-config.h> // IWYU pragma: keep + #include <chainparams.h> #include <httpserver.h> #include <index/blockfilterindex.h> @@ -22,6 +24,7 @@ #include <univalue.h> #include <util/any.h> #include <util/check.h> +#include <util/time.h> #include <stdint.h> #ifdef HAVE_MALLOC_INFO @@ -54,9 +57,11 @@ static RPCHelpMan setmocktime() LOCK(cs_main); 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)); + constexpr int64_t max_time{Ticks<std::chrono::seconds>(std::chrono::nanoseconds::max())}; + if (time < 0 || time > max_time) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime must be in the range [0, %s], not %s.", max_time, time)); } + SetMockTime(time); const NodeContext& node_context{EnsureAnyNodeContext(request.context)}; for (const auto& chain_client : node_context.chain_clients) { @@ -90,7 +95,7 @@ static RPCHelpMan mockscheduler() const NodeContext& node_context{EnsureAnyNodeContext(request.context)}; CHECK_NONFATAL(node_context.scheduler)->MockForward(std::chrono::seconds{delta_seconds}); - SyncWithValidationInterfaceQueue(); + CHECK_NONFATAL(node_context.validation_signals)->SyncWithValidationInterfaceQueue(); for (const auto& chain_client : node_context.chain_clients) { chain_client->schedulerMockForward(std::chrono::seconds(delta_seconds)); } diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp index f9343f48a8..474d9076be 100644 --- a/src/rpc/output_script.cpp +++ b/src/rpc/output_script.cpp @@ -124,11 +124,7 @@ static RPCHelpMan createmultisig() const UniValue& keys = request.params[1].get_array(); std::vector<CPubKey> pubkeys; for (unsigned int i = 0; i < keys.size(); ++i) { - if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) { - pubkeys.push_back(HexToPubKey(keys[i].get_str())); - } else { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n.", keys[i].get_str())); - } + pubkeys.push_back(HexToPubKey(keys[i].get_str())); } // Get the output type diff --git a/src/rpc/register.h b/src/rpc/register.h index c88f49ecf0..65fd29ff08 100644 --- a/src/rpc/register.h +++ b/src/rpc/register.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_RPC_REGISTER_H #define BITCOIN_RPC_REGISTER_H +#include <config/bitcoin-config.h> // IWYU pragma: keep + /** These are in one header file to avoid creating tons of single-function * headers for everything under src/rpc/ */ class CRPCTable; diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index e1ac510cba..a800451f4a 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -3,6 +3,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <config/bitcoin-config.h> // IWYU pragma: keep + #include <rpc/server.h> #include <common/args.h> diff --git a/src/rpc/signmessage.cpp b/src/rpc/signmessage.cpp index 8c752ba1fd..9f3c24efcf 100644 --- a/src/rpc/signmessage.cpp +++ b/src/rpc/signmessage.cpp @@ -38,9 +38,9 @@ static RPCHelpMan verifymessage() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::string strAddress = request.params[0].get_str(); - std::string strSign = request.params[1].get_str(); - std::string strMessage = request.params[2].get_str(); + std::string strAddress = self.Arg<std::string>("address"); + std::string strSign = self.Arg<std::string>("signature"); + std::string strMessage = self.Arg<std::string>("message"); switch (MessageVerify(strAddress, strSign, strMessage)) { case MessageVerificationResult::ERR_INVALID_ADDRESS: diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index cf48ee11e7..9a7c731afe 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -2,6 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <config/bitcoin-config.h> // IWYU pragma: keep + #include <clientversion.h> #include <core_io.h> #include <common/args.h> @@ -14,14 +16,19 @@ #include <script/signingprovider.h> #include <script/solver.h> #include <tinyformat.h> +#include <univalue.h> #include <util/check.h> #include <util/result.h> #include <util/strencodings.h> #include <util/string.h> #include <util/translation.h> +#include <warnings.h> +#include <algorithm> +#include <iterator> #include <string_view> #include <tuple> +#include <utility> const std::string UNIX_EPOCH_TIME = "UNIX epoch time"; const std::string EXAMPLE_ADDRESS[2] = {"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl", "bc1q02ad21edsxd23d32dfgqqsz4vv4nmtfzuklhy3"}; @@ -75,6 +82,13 @@ CAmount AmountFromValue(const UniValue& value, int decimals) return amount; } +CFeeRate ParseFeeRate(const UniValue& json) +{ + CAmount val{AmountFromValue(json)}; + if (val >= COIN) throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee rates larger than or equal to 1BTC/kvB are not accepted"); + return CFeeRate{val}; +} + uint256 ParseHashV(const UniValue& v, std::string_view name) { const std::string& strHex(v.get_str()); @@ -180,11 +194,14 @@ std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList& CPubKey HexToPubKey(const std::string& hex_in) { if (!IsHex(hex_in)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + hex_in); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + hex_in + "\" must be a hex string"); + } + if (hex_in.length() != 66 && hex_in.length() != 130) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + hex_in + "\" must have a length of either 33 or 65 bytes"); } CPubKey vchPubKey(ParseHex(hex_in)); if (!vchPubKey.IsFullyValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + hex_in); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + hex_in + "\" must be cryptographically valid."); } return vchPubKey; } @@ -403,6 +420,7 @@ struct Sections { /** * Recursive helper to translate an RPCArg into sections */ + // NOLINTNEXTLINE(misc-no-recursion) void Push(const RPCArg& arg, const size_t current_indent = 5, const OuterType outer_type = OuterType::NONE) { const auto indent = std::string(current_indent, ' '); @@ -678,11 +696,13 @@ static void CheckRequiredOrDefault(const RPCArg& param) void force_semicolon(ret_type) // Optional arg (without default). Can also be called on required args, if needed. +TMPL_INST(nullptr, const UniValue*, maybe_arg;); TMPL_INST(nullptr, std::optional<double>, maybe_arg ? std::optional{maybe_arg->get_real()} : std::nullopt;); TMPL_INST(nullptr, std::optional<bool>, maybe_arg ? std::optional{maybe_arg->get_bool()} : std::nullopt;); TMPL_INST(nullptr, const std::string*, maybe_arg ? &maybe_arg->get_str() : nullptr;); // Required arg or optional arg with default value. +TMPL_INST(CheckRequiredOrDefault, const UniValue&, *CHECK_NONFATAL(maybe_arg);); TMPL_INST(CheckRequiredOrDefault, bool, CHECK_NONFATAL(maybe_arg)->get_bool();); TMPL_INST(CheckRequiredOrDefault, int, CHECK_NONFATAL(maybe_arg)->getInt<int>();); TMPL_INST(CheckRequiredOrDefault, uint64_t, CHECK_NONFATAL(maybe_arg)->getInt<uint64_t>();); @@ -715,6 +735,16 @@ std::vector<std::pair<std::string, bool>> RPCHelpMan::GetArgNames() const return ret; } +size_t RPCHelpMan::GetParamIndex(std::string_view key) const +{ + auto it{std::find_if( + m_args.begin(), m_args.end(), [&key](const auto& arg) { return arg.GetName() == key;} + )}; + + CHECK_NONFATAL(it != m_args.end()); // TODO: ideally this is checked at compile time + return std::distance(m_args.begin(), it); +} + std::string RPCHelpMan::ToString() const { std::string ret; @@ -940,6 +970,7 @@ std::string RPCArg::ToDescriptionString(bool is_named_arg) const return ret; } +// NOLINTNEXTLINE(misc-no-recursion) void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const int current_indent) const { // Indentation @@ -1073,6 +1104,7 @@ static std::optional<UniValue::VType> ExpectedType(RPCResult::Type type) NONFATAL_UNREACHABLE(); } +// NOLINTNEXTLINE(misc-no-recursion) UniValue RPCResult::MatchesType(const UniValue& result) const { if (m_skip_type_check) { @@ -1151,6 +1183,7 @@ void RPCResult::CheckInnerDoc() const CHECK_NONFATAL(inner_needed != m_inner.empty()); } +// NOLINTNEXTLINE(misc-no-recursion) std::string RPCArg::ToStringObj(const bool oneline) const { std::string res; @@ -1189,6 +1222,7 @@ std::string RPCArg::ToStringObj(const bool oneline) const NONFATAL_UNREACHABLE(); } +// NOLINTNEXTLINE(misc-no-recursion) std::string RPCArg::ToString(const bool oneline) const { if (oneline && !m_opts.oneline_description.empty()) { @@ -1215,6 +1249,7 @@ std::string RPCArg::ToString(const bool oneline) const case Type::OBJ: case Type::OBJ_NAMED_PARAMS: case Type::OBJ_USER_KEYS: { + // NOLINTNEXTLINE(misc-no-recursion) const std::string res = Join(m_inner, ",", [&](const RPCArg& i) { return i.ToStringObj(oneline); }); if (m_type == Type::OBJ) { return "{" + res + "}"; @@ -1326,3 +1361,17 @@ void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj) if (warnings.empty()) return; obj.pushKV("warnings", BilingualStringsToUniValue(warnings)); } + +UniValue GetNodeWarnings(bool use_deprecated) +{ + if (use_deprecated) { + const auto all_warnings{GetWarnings()}; + return all_warnings.empty() ? "" : all_warnings.back().original; + } + + UniValue warnings{UniValue::VARR}; + for (auto&& warning : GetWarnings()) { + warnings.push_back(std::move(warning.original)); + } + return warnings; +} diff --git a/src/rpc/util.h b/src/rpc/util.h index e2d5ed333c..0e4dcc27b5 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -103,6 +103,11 @@ std::vector<unsigned char> ParseHexO(const UniValue& o, std::string_view strKey) * @returns a CAmount if the various checks pass. */ CAmount AmountFromValue(const UniValue& value, int decimals = 8); +/** + * Parse a json number or string, denoting BTC/kvB, into a CFeeRate (sat/kvB). + * Reject negative values or rates larger than 1BTC/kvB. + */ +CFeeRate ParseFeeRate(const UniValue& json); using RPCArgList = std::vector<std::pair<std::string, UniValue>>; std::string HelpExampleCli(const std::string& methodname, const std::string& args); @@ -157,6 +162,7 @@ struct RPCArgOptions { //!< methods set the also_positional flag and read values from both positions. }; +// NOLINTNEXTLINE(misc-no-recursion) struct RPCArg { enum class Type { OBJ, @@ -266,6 +272,7 @@ struct RPCArg { std::string ToDescriptionString(bool is_named_arg) const; }; +// NOLINTNEXTLINE(misc-no-recursion) struct RPCResult { enum class Type { OBJ, @@ -397,18 +404,25 @@ public: UniValue HandleRequest(const JSONRPCRequest& request) const; /** - * Helper to get a request argument. - * This function only works during m_fun(), i.e. it should only be used in - * RPC method implementations. The helper internally checks whether the - * user-passed argument isNull() and parses (from JSON) and returns the - * user-passed argument, or the default value derived from the RPCArg - * documentation, or a falsy value if no default was given. + * @brief Helper to get a required or default-valued request argument. + * + * Use this function when the argument is required or when it has a default value. If the + * argument is optional and may not be provided, use MaybeArg instead. + * + * This function only works during m_fun(), i.e., it should only be used in + * RPC method implementations. It internally checks whether the user-passed + * argument isNull() and parses (from JSON) and returns the user-passed argument, + * or the default value derived from the RPCArg documentation. + * + * There are two overloads of this function: + * - Use Arg<Type>(size_t i) to get the argument (or the default value) by index. + * - Use Arg<Type>(const std::string& key) to get the argument (or the default value) by key. * - * Use Arg<Type>(i) to get the argument or its default value. Otherwise, - * use MaybeArg<Type>(i) to get the optional argument or a falsy value. + * The Type passed to this helper must match the corresponding RPCArg::Type. * - * The Type passed to this helper must match the corresponding - * RPCArg::Type. + * @return The value of the RPC argument (or the default value) cast to type Type. + * + * @see MaybeArg for handling optional arguments without default values. */ template <typename R> auto Arg(size_t i) const @@ -422,6 +436,34 @@ public: return ArgValue<const R&>(i); } } + template<typename R> + auto Arg(std::string_view key) const + { + return Arg<R>(GetParamIndex(key)); + } + /** + * @brief Helper to get an optional request argument. + * + * Use this function when the argument is optional and does not have a default value. If the + * argument is required or has a default value, use Arg instead. + * + * This function only works during m_fun(), i.e., it should only be used in + * RPC method implementations. It internally checks whether the user-passed + * argument isNull() and parses (from JSON) and returns the user-passed argument, + * or a falsy value if no argument was passed. + * + * There are two overloads of this function: + * - Use MaybeArg<Type>(size_t i) to get the optional argument by index. + * - Use MaybeArg<Type>(const std::string& key) to get the optional argument by key. + * + * The Type passed to this helper must match the corresponding RPCArg::Type. + * + * @return For integral and floating-point types, a std::optional<Type> is returned. + * For other types, a Type* pointer to the argument is returned. If the + * argument is not provided, std::nullopt or a null pointer is returned. + * + * @see Arg for handling arguments that are required or have a default value. + */ template <typename R> auto MaybeArg(size_t i) const { @@ -434,6 +476,11 @@ public: return ArgValue<const R*>(i); } } + template<typename R> + auto MaybeArg(std::string_view key) const + { + return MaybeArg<R>(GetParamIndex(key)); + } std::string ToString() const; /** Return the named args that need to be converted from string to another JSON type */ UniValue GetArgMap() const; @@ -453,6 +500,8 @@ private: mutable const JSONRPCRequest* m_req{nullptr}; // A pointer to the request for the duration of m_fun() template <typename R> R ArgValue(size_t i) const; + //! Return positional index of a parameter using its name as key. + size_t GetParamIndex(std::string_view key) const; }; /** @@ -464,4 +513,6 @@ private: void PushWarnings(const UniValue& warnings, UniValue& obj); void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj); +UniValue GetNodeWarnings(bool use_deprecated); + #endif // BITCOIN_RPC_UTIL_H |