diff options
Diffstat (limited to 'src/rpc')
-rw-r--r-- | src/rpc/blockchain.cpp | 729 | ||||
-rw-r--r-- | src/rpc/blockchain.h | 33 | ||||
-rw-r--r-- | src/rpc/client.cpp | 12 | ||||
-rw-r--r-- | src/rpc/external_signer.cpp | 7 | ||||
-rw-r--r-- | src/rpc/mining.cpp | 98 | ||||
-rw-r--r-- | src/rpc/misc.cpp | 131 | ||||
-rw-r--r-- | src/rpc/net.cpp | 209 | ||||
-rw-r--r-- | src/rpc/net.h | 15 | ||||
-rw-r--r-- | src/rpc/protocol.h | 3 | ||||
-rw-r--r-- | src/rpc/rawtransaction.cpp | 594 | ||||
-rw-r--r-- | src/rpc/rawtransaction_util.cpp | 14 | ||||
-rw-r--r-- | src/rpc/rawtransaction_util.h | 5 | ||||
-rw-r--r-- | src/rpc/register.h | 2 | ||||
-rw-r--r-- | src/rpc/request.cpp | 19 | ||||
-rw-r--r-- | src/rpc/request.h | 2 | ||||
-rw-r--r-- | src/rpc/server.cpp | 6 | ||||
-rw-r--r-- | src/rpc/server.h | 3 | ||||
-rw-r--r-- | src/rpc/server_util.cpp | 95 | ||||
-rw-r--r-- | src/rpc/server_util.h | 32 | ||||
-rw-r--r-- | src/rpc/util.cpp | 34 | ||||
-rw-r--r-- | src/rpc/util.h | 16 |
21 files changed, 1336 insertions, 723 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index c4a89c9772..c066f6f569 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1,23 +1,27 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2020 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/blockchain.h> -#include <amount.h> #include <blockfilter.h> #include <chain.h> #include <chainparams.h> #include <coins.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> +#include <logging/timer.h> +#include <net.h> +#include <net_processing.h> #include <node/blockstorage.h> #include <node/coinstats.h> #include <node/context.h> @@ -28,6 +32,7 @@ #include <policy/rbf.h> #include <primitives/transaction.h> #include <rpc/server.h> +#include <rpc/server_util.h> #include <rpc/util.h> #include <script/descriptor.h> #include <streams.h> @@ -36,7 +41,7 @@ #include <txmempool.h> #include <undo.h> #include <util/strencodings.h> -#include <util/system.h> +#include <util/string.h> #include <util/translation.h> #include <validation.h> #include <validationinterface.h> @@ -51,6 +56,16 @@ #include <memory> #include <mutex> +using node::BlockManager; +using node::CCoinsStats; +using node::CoinStatsHashType; +using node::GetUTXOStats; +using node::IsBlockPruned; +using node::NodeContext; +using node::ReadBlockFromDisk; +using node::SnapshotMetadata; +using node::UndoReadFromDisk; + struct CUpdatedBlock { uint256 hash; @@ -61,54 +76,6 @@ static Mutex cs_blockchange; static std::condition_variable cond_blockchange; static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange); -NodeContext& EnsureAnyNodeContext(const std::any& context) -{ - auto node_context = util::AnyPtr<NodeContext>(context); - if (!node_context) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "Node context not found"); - } - return *node_context; -} - -CTxMemPool& EnsureMemPool(const NodeContext& node) -{ - if (!node.mempool) { - throw JSONRPCError(RPC_CLIENT_MEMPOOL_DISABLED, "Mempool disabled or instance not found"); - } - return *node.mempool; -} - -CTxMemPool& EnsureAnyMemPool(const std::any& context) -{ - return EnsureMemPool(EnsureAnyNodeContext(context)); -} - -ChainstateManager& EnsureChainman(const NodeContext& node) -{ - if (!node.chainman) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "Node chainman not found"); - } - return *node.chainman; -} - -ChainstateManager& EnsureAnyChainman(const std::any& context) -{ - return EnsureChainman(EnsureAnyNodeContext(context)); -} - -CBlockPolicyEstimator& EnsureFeeEstimator(const NodeContext& node) -{ - if (!node.fee_estimator) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "Fee estimation disabled"); - } - return *node.fee_estimator; -} - -CBlockPolicyEstimator& EnsureAnyFeeEstimator(const std::any& context) -{ - return EnsureFeeEstimator(EnsureAnyNodeContext(context)); -} - /* Calculate the difficulty for a given block index. */ double GetDifficulty(const CBlockIndex* blockindex) @@ -199,7 +166,7 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex return result; } -UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, bool txDetails) +UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity) { UniValue result = blockheaderToJSON(tip, blockindex); @@ -207,22 +174,30 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION)); result.pushKV("weight", (int)::GetBlockWeight(block)); UniValue txs(UniValue::VARR); - if (txDetails) { - CBlockUndo blockUndo; - const bool have_undo = !IsBlockPruned(blockindex) && UndoReadFromDisk(blockUndo, blockindex); - for (size_t i = 0; i < block.vtx.size(); ++i) { - const CTransactionRef& tx = block.vtx.at(i); - // coinbase transaction (i == 0) doesn't have undo data - const CTxUndo* txundo = (have_undo && i) ? &blockUndo.vtxundo.at(i - 1) : nullptr; - UniValue objTx(UniValue::VOBJ); - TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags(), txundo); - txs.push_back(objTx); - } - } else { - for (const CTransactionRef& tx : block.vtx) { - txs.push_back(tx->GetHash().GetHex()); - } + + switch (verbosity) { + case TxVerbosity::SHOW_TXID: + for (const CTransactionRef& tx : block.vtx) { + txs.push_back(tx->GetHash().GetHex()); + } + break; + + case TxVerbosity::SHOW_DETAILS: + case TxVerbosity::SHOW_DETAILS_AND_PREVOUT: + CBlockUndo blockUndo; + const bool have_undo{WITH_LOCK(::cs_main, return !IsBlockPruned(blockindex) && UndoReadFromDisk(blockUndo, blockindex))}; + + for (size_t i = 0; i < block.vtx.size(); ++i) { + const CTransactionRef& tx = block.vtx.at(i); + // coinbase transaction (i.e. i == 0) doesn't have undo data + const CTxUndo* txundo = (have_undo && i > 0) ? &blockUndo.vtxundo.at(i - 1) : nullptr; + UniValue objTx(UniValue::VOBJ); + TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags(), txundo, verbosity); + txs.push_back(objTx); + } + break; } + result.pushKV("tx", txs); return result; @@ -454,23 +429,30 @@ static RPCHelpMan getdifficulty() static std::vector<RPCResult> MempoolEntryDescription() { return { RPCResult{RPCResult::Type::NUM, "vsize", "virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."}, RPCResult{RPCResult::Type::NUM, "weight", "transaction weight as defined in BIP 141."}, - RPCResult{RPCResult::Type::STR_AMOUNT, "fee", "transaction fee in " + CURRENCY_UNIT + " (DEPRECATED)"}, - RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee", "transaction fee with fee deltas used for mining priority (DEPRECATED)"}, + RPCResult{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, + "transaction fee, denominated in " + CURRENCY_UNIT + " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"}, + RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee", /*optional=*/true, + "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT + + " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"}, RPCResult{RPCResult::Type::NUM_TIME, "time", "local time transaction entered pool in seconds since 1 Jan 1970 GMT"}, RPCResult{RPCResult::Type::NUM, "height", "block height when transaction entered pool"}, RPCResult{RPCResult::Type::NUM, "descendantcount", "number of in-mempool descendant transactions (including this one)"}, RPCResult{RPCResult::Type::NUM, "descendantsize", "virtual transaction size of in-mempool descendants (including this one)"}, - RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees", "modified fees (see above) of in-mempool descendants (including this one) (DEPRECATED)"}, + RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees", /*optional=*/true, + "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " + + CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"}, RPCResult{RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"}, RPCResult{RPCResult::Type::NUM, "ancestorsize", "virtual transaction size of in-mempool ancestors (including this one)"}, - RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees", "modified fees (see above) of in-mempool ancestors (including this one) (DEPRECATED)"}, + RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees", /*optional=*/true, + "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " + + CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"}, RPCResult{RPCResult::Type::STR_HEX, "wtxid", "hash of serialized transaction, including witness data"}, RPCResult{RPCResult::Type::OBJ, "fees", "", { - RPCResult{RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT}, - RPCResult{RPCResult::Type::STR_AMOUNT, "modified", "transaction fee with fee deltas used for mining priority in " + CURRENCY_UNIT}, - RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor", "modified fees (see above) of in-mempool ancestors (including this one) in " + CURRENCY_UNIT}, - RPCResult{RPCResult::Type::STR_AMOUNT, "descendant", "modified fees (see above) of in-mempool descendants (including this one) in " + CURRENCY_UNIT}, + RPCResult{RPCResult::Type::STR_AMOUNT, "base", "transaction fee, denominated in " + CURRENCY_UNIT}, + RPCResult{RPCResult::Type::STR_AMOUNT, "modified", "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT}, + RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor", "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT}, + RPCResult{RPCResult::Type::STR_AMOUNT, "descendant", "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT}, }}, RPCResult{RPCResult::Type::ARR, "depends", "unconfirmed transactions used as inputs for this transaction", {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "parent transaction id"}}}, @@ -484,31 +466,40 @@ static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPool { AssertLockHeld(pool.cs); - UniValue fees(UniValue::VOBJ); - fees.pushKV("base", ValueFromAmount(e.GetFee())); - fees.pushKV("modified", ValueFromAmount(e.GetModifiedFee())); - fees.pushKV("ancestor", ValueFromAmount(e.GetModFeesWithAncestors())); - fees.pushKV("descendant", ValueFromAmount(e.GetModFeesWithDescendants())); - info.pushKV("fees", fees); - info.pushKV("vsize", (int)e.GetTxSize()); info.pushKV("weight", (int)e.GetTxWeight()); - info.pushKV("fee", ValueFromAmount(e.GetFee())); - info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee())); + // TODO: top-level fee fields are deprecated. deprecated_fee_fields_enabled blocks should be removed in v24 + const bool deprecated_fee_fields_enabled{IsDeprecatedRPCEnabled("fees")}; + if (deprecated_fee_fields_enabled) { + info.pushKV("fee", ValueFromAmount(e.GetFee())); + info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee())); + } info.pushKV("time", count_seconds(e.GetTime())); info.pushKV("height", (int)e.GetHeight()); info.pushKV("descendantcount", e.GetCountWithDescendants()); info.pushKV("descendantsize", e.GetSizeWithDescendants()); - info.pushKV("descendantfees", e.GetModFeesWithDescendants()); + if (deprecated_fee_fields_enabled) { + info.pushKV("descendantfees", e.GetModFeesWithDescendants()); + } info.pushKV("ancestorcount", e.GetCountWithAncestors()); info.pushKV("ancestorsize", e.GetSizeWithAncestors()); - info.pushKV("ancestorfees", e.GetModFeesWithAncestors()); + if (deprecated_fee_fields_enabled) { + info.pushKV("ancestorfees", e.GetModFeesWithAncestors()); + } info.pushKV("wtxid", pool.vTxHashes[e.vTxHashesIdx].first.ToString()); + + UniValue fees(UniValue::VOBJ); + fees.pushKV("base", ValueFromAmount(e.GetFee())); + fees.pushKV("modified", ValueFromAmount(e.GetModifiedFee())); + fees.pushKV("ancestor", ValueFromAmount(e.GetModFeesWithAncestors())); + fees.pushKV("descendant", ValueFromAmount(e.GetModFeesWithDescendants())); + info.pushKV("fees", fees); + const CTransaction& tx = e.GetTx(); std::set<std::string> setDepends; for (const CTxIn& txin : tx.vin) { - if (pool.exists(txin.prevout.hash)) + if (pool.exists(GenTxid::Txid(txin.prevout.hash))) setDepends.insert(txin.prevout.hash.ToString()); } @@ -795,6 +786,51 @@ static RPCHelpMan getmempoolentry() }; } +static RPCHelpMan getblockfrompeer() +{ + return RPCHelpMan{ + "getblockfrompeer", + "Attempt to fetch block from a given peer.\n\n" + "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\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"}, + {"peer_id", RPCArg::Type::NUM, RPCArg::Optional::NO, "The peer to fetch it from (see getpeerinfo for peer IDs)"}, + }, + RPCResult{RPCResult::Type::OBJ, "", /*optional=*/false, "", {}}, + RPCExamples{ + HelpExampleCli("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0") + + HelpExampleRpc("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + const NodeContext& node = EnsureAnyNodeContext(request.context); + ChainstateManager& chainman = EnsureChainman(node); + PeerManager& peerman = EnsurePeerman(node); + + const uint256& block_hash{ParseHashV(request.params[0], "blockhash")}; + const NodeId peer_id{request.params[1].get_int64()}; + + const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash);); + + if (!index) { + throw JSONRPCError(RPC_MISC_ERROR, "Block header missing"); + } + + const bool block_has_data = WITH_LOCK(::cs_main, return index->nStatus & BLOCK_HAVE_DATA); + if (block_has_data) { + throw JSONRPCError(RPC_MISC_ERROR, "Block already downloaded"); + } + + if (const auto err{peerman.FetchBlock(peer_id, *index)}) { + throw JSONRPCError(RPC_MISC_ERROR, err.value()); + } + return UniValue::VOBJ; +}, + }; +} + static RPCHelpMan getblockhash() { return RPCHelpMan{"getblockhash", @@ -850,8 +886,8 @@ static RPCHelpMan getblockheader() {RPCResult::Type::NUM, "difficulty", "The difficulty"}, {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the current chain"}, {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"}, - {RPCResult::Type::STR_HEX, "previousblockhash", /* optional */ true, "The hash of the previous block (if available)"}, - {RPCResult::Type::STR_HEX, "nextblockhash", /* optional */ true, "The hash of the next block (if available)"}, + {RPCResult::Type::STR_HEX, "previousblockhash", /*optional=*/true, "The hash of the previous block (if available)"}, + {RPCResult::Type::STR_HEX, "nextblockhash", /*optional=*/true, "The hash of the next block (if available)"}, }}, RPCResult{"for verbose=false", RPCResult::Type::STR_HEX, "", "A string that is serialized, hex-encoded data for block 'hash'"}, @@ -894,8 +930,9 @@ static RPCHelpMan getblockheader() }; } -static CBlock GetBlockChecked(const CBlockIndex* pblockindex) +static CBlock GetBlockChecked(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + AssertLockHeld(::cs_main); CBlock block; if (IsBlockPruned(pblockindex)) { throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)"); @@ -911,8 +948,9 @@ static CBlock GetBlockChecked(const CBlockIndex* pblockindex) return block; } -static CBlockUndo GetUndoChecked(const CBlockIndex* pblockindex) +static CBlockUndo GetUndoChecked(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + AssertLockHeld(::cs_main); CBlockUndo blockUndo; if (IsBlockPruned(pblockindex)) { throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available (pruned data)"); @@ -930,10 +968,11 @@ static RPCHelpMan getblock() return RPCHelpMan{"getblock", "\nIf verbosity is 0, returns a string that is serialized, hex-encoded data for block 'hash'.\n" "If verbosity is 1, returns an Object with information about block <hash>.\n" - "If verbosity is 2, returns an Object with information about block <hash> and information about each transaction. \n", + "If verbosity is 2, returns an Object with information about block <hash> and information about each transaction.\n" + "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, and 2 for json object with transaction data"}, + {"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"}, }, { RPCResult{"for verbosity = 0", @@ -959,8 +998,8 @@ static RPCHelpMan getblock() {RPCResult::Type::NUM, "difficulty", "The difficulty"}, {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the chain up to this block (in hex)"}, {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"}, - {RPCResult::Type::STR_HEX, "previousblockhash", /* optional */ true, "The hash of the previous block (if available)"}, - {RPCResult::Type::STR_HEX, "nextblockhash", /* optional */ true, "The hash of the next block (if available)"}, + {RPCResult::Type::STR_HEX, "previousblockhash", /*optional=*/true, "The hash of the previous block (if available)"}, + {RPCResult::Type::STR_HEX, "nextblockhash", /*optional=*/true, "The hash of the next block (if available)"}, }}, RPCResult{"for verbosity = 2", RPCResult::Type::OBJ, "", "", @@ -975,6 +1014,37 @@ static RPCHelpMan getblock() }}, }}, }}, + RPCResult{"for verbosity = 3", + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::ELISION, "", "Same output as verbosity = 2"}, + {RPCResult::Type::ARR, "tx", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::ARR, "vin", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::ELISION, "", "The same output as verbosity = 2"}, + {RPCResult::Type::OBJ, "prevout", "(Only if undo information is available)", + { + {RPCResult::Type::BOOL, "generated", "Coinbase or not"}, + {RPCResult::Type::NUM, "height", "The height of the prevout"}, + {RPCResult::Type::NUM, "value", "The value in " + CURRENCY_UNIT}, + {RPCResult::Type::OBJ, "scriptPubKey", "", + { + {RPCResult::Type::STR, "asm", "The asm"}, + {RPCResult::Type::STR, "hex", "The hex"}, + {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() + ")"}, + }}, + }}, + }}, + }}, + }}, + }}, + }}, }, RPCExamples{ HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") @@ -1017,7 +1087,16 @@ static RPCHelpMan getblock() return strHex; } - return blockToJSON(block, tip, pblockindex, verbosity >= 2); + TxVerbosity tx_verbosity; + if (verbosity == 1) { + tx_verbosity = TxVerbosity::SHOW_TXID; + } else if (verbosity == 2) { + tx_verbosity = TxVerbosity::SHOW_DETAILS; + } else { + tx_verbosity = TxVerbosity::SHOW_DETAILS_AND_PREVOUT; + } + + return blockToJSON(block, tip, pblockindex, tx_verbosity); }, }; } @@ -1037,7 +1116,7 @@ static RPCHelpMan pruneblockchain() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - if (!fPruneMode) + if (!node::fPruneMode) throw JSONRPCError(RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode."); ChainstateManager& chainman = EnsureAnyChainman(request.context); @@ -1091,7 +1170,7 @@ CoinStatsHashType ParseHashType(const std::string& hash_type_input) } else if (hash_type_input == "none") { return CoinStatsHashType::NONE; } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s is not a valid hash_type", hash_type_input)); + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("'%s' is not a valid hash_type", hash_type_input)); } } @@ -1112,21 +1191,21 @@ static RPCHelpMan gettxoutsetinfo() {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at which these statistics are calculated"}, {RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"}, {RPCResult::Type::NUM, "bogosize", "Database-independent, meaningless metric indicating the UTXO set size"}, - {RPCResult::Type::STR_HEX, "hash_serialized_2", /* optional */ true, "The serialized hash (only present if 'hash_serialized_2' hash_type is chosen)"}, - {RPCResult::Type::STR_HEX, "muhash", /* optional */ true, "The serialized hash (only present if 'muhash' hash_type is chosen)"}, - {RPCResult::Type::NUM, "transactions", "The number of transactions with unspent outputs (not available when coinstatsindex is used)"}, - {RPCResult::Type::NUM, "disk_size", "The estimated size of the chainstate on disk (not available when coinstatsindex is used)"}, + {RPCResult::Type::STR_HEX, "hash_serialized_2", /*optional=*/true, "The serialized hash (only present if 'hash_serialized_2' hash_type is chosen)"}, + {RPCResult::Type::STR_HEX, "muhash", /*optional=*/true, "The serialized hash (only present if 'muhash' hash_type is chosen)"}, + {RPCResult::Type::NUM, "transactions", /*optional=*/true, "The number of transactions with unspent outputs (not available when coinstatsindex is used)"}, + {RPCResult::Type::NUM, "disk_size", /*optional=*/true, "The estimated size of the chainstate on disk (not available when coinstatsindex is used)"}, {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of coins in the UTXO set"}, - {RPCResult::Type::STR_AMOUNT, "total_unspendable_amount", "The total amount of coins permanently excluded from the UTXO set (only available if coinstatsindex is used)"}, - {RPCResult::Type::OBJ, "block_info", "Info on amounts in the block at this block height (only available if coinstatsindex is used)", + {RPCResult::Type::STR_AMOUNT, "total_unspendable_amount", /*optional=*/true, "The total amount of coins permanently excluded from the UTXO set (only available if coinstatsindex is used)"}, + {RPCResult::Type::OBJ, "block_info", /*optional=*/true, "Info on amounts in the block at this block height (only available if coinstatsindex is used)", { - {RPCResult::Type::STR_AMOUNT, "prevout_spent", ""}, - {RPCResult::Type::STR_AMOUNT, "coinbase", ""}, - {RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", ""}, - {RPCResult::Type::STR_AMOUNT, "unspendable", ""}, + {RPCResult::Type::STR_AMOUNT, "prevout_spent", "Total amount of all prevouts spent in this block"}, + {RPCResult::Type::STR_AMOUNT, "coinbase", "Coinbase subsidy amount of this block"}, + {RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", "Total amount of new outputs created by this block"}, + {RPCResult::Type::STR_AMOUNT, "unspendable", "Total amount of unspendable outputs created in this block"}, {RPCResult::Type::OBJ, "unspendables", "Detailed view of the unspendable categories", { - {RPCResult::Type::STR_AMOUNT, "genesis_block", ""}, + {RPCResult::Type::STR_AMOUNT, "genesis_block", "The unspendable amount of the Genesis block subsidy"}, {RPCResult::Type::STR_AMOUNT, "bip30", "Transactions overridden by duplicates (no longer possible with BIP30)"}, {RPCResult::Type::STR_AMOUNT, "scripts", "Amounts sent to scripts that are unspendable (for example OP_RETURN outputs)"}, {RPCResult::Type::STR_AMOUNT, "unclaimed_rewards", "Fee rewards that miners did not claim in their coinbase transaction"}, @@ -1178,6 +1257,18 @@ static RPCHelpMan gettxoutsetinfo() pindex = ParseHashOrHeight(request.params[1], chainman); } + if (stats.index_requested && g_coin_stats_index) { + if (!g_coin_stats_index->BlockUntilSyncedToCurrentChain()) { + const IndexSummary summary{g_coin_stats_index->GetSummary()}; + + // If a specific block was requested and the index has already synced past that height, we can return the + // data already even though the index is not fully synced yet. + if (pindex->nHeight > summary.best_block_height) { + throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to get data because coinstatsindex is still syncing. Current height: %d", summary.best_block_height)); + } + } + } + if (GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point, pindex)) { ret.pushKV("height", (int64_t)stats.nHeight); ret.pushKV("bestblock", stats.hashBlock.GetHex()); @@ -1187,14 +1278,15 @@ static RPCHelpMan gettxoutsetinfo() ret.pushKV("hash_serialized_2", stats.hashSerialized.GetHex()); } if (hash_type == CoinStatsHashType::MUHASH) { - ret.pushKV("muhash", stats.hashSerialized.GetHex()); + ret.pushKV("muhash", stats.hashSerialized.GetHex()); } - ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount)); + CHECK_NONFATAL(stats.total_amount.has_value()); + ret.pushKV("total_amount", ValueFromAmount(stats.total_amount.value())); if (!stats.index_used) { ret.pushKV("transactions", static_cast<int64_t>(stats.nTransactions)); ret.pushKV("disk_size", stats.nDiskSize); } else { - ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.block_unspendable_amount)); + ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.total_unspendable_amount)); CCoinsStats prev_stats{hash_type}; @@ -1203,28 +1295,21 @@ static RPCHelpMan gettxoutsetinfo() } UniValue block_info(UniValue::VOBJ); - block_info.pushKV("prevout_spent", ValueFromAmount(stats.block_prevout_spent_amount - prev_stats.block_prevout_spent_amount)); - block_info.pushKV("coinbase", ValueFromAmount(stats.block_coinbase_amount - prev_stats.block_coinbase_amount)); - block_info.pushKV("new_outputs_ex_coinbase", ValueFromAmount(stats.block_new_outputs_ex_coinbase_amount - prev_stats.block_new_outputs_ex_coinbase_amount)); - block_info.pushKV("unspendable", ValueFromAmount(stats.block_unspendable_amount - prev_stats.block_unspendable_amount)); + block_info.pushKV("prevout_spent", ValueFromAmount(stats.total_prevout_spent_amount - prev_stats.total_prevout_spent_amount)); + block_info.pushKV("coinbase", ValueFromAmount(stats.total_coinbase_amount - prev_stats.total_coinbase_amount)); + block_info.pushKV("new_outputs_ex_coinbase", ValueFromAmount(stats.total_new_outputs_ex_coinbase_amount - prev_stats.total_new_outputs_ex_coinbase_amount)); + block_info.pushKV("unspendable", ValueFromAmount(stats.total_unspendable_amount - prev_stats.total_unspendable_amount)); UniValue unspendables(UniValue::VOBJ); - unspendables.pushKV("genesis_block", ValueFromAmount(stats.unspendables_genesis_block - prev_stats.unspendables_genesis_block)); - unspendables.pushKV("bip30", ValueFromAmount(stats.unspendables_bip30 - prev_stats.unspendables_bip30)); - unspendables.pushKV("scripts", ValueFromAmount(stats.unspendables_scripts - prev_stats.unspendables_scripts)); - unspendables.pushKV("unclaimed_rewards", ValueFromAmount(stats.unspendables_unclaimed_rewards - prev_stats.unspendables_unclaimed_rewards)); + unspendables.pushKV("genesis_block", ValueFromAmount(stats.total_unspendables_genesis_block - prev_stats.total_unspendables_genesis_block)); + unspendables.pushKV("bip30", ValueFromAmount(stats.total_unspendables_bip30 - prev_stats.total_unspendables_bip30)); + unspendables.pushKV("scripts", ValueFromAmount(stats.total_unspendables_scripts - prev_stats.total_unspendables_scripts)); + unspendables.pushKV("unclaimed_rewards", ValueFromAmount(stats.total_unspendables_unclaimed_rewards - prev_stats.total_unspendables_unclaimed_rewards)); block_info.pushKV("unspendables", unspendables); ret.pushKV("block_info", block_info); } } else { - if (g_coin_stats_index) { - const IndexSummary summary{g_coin_stats_index->GetSummary()}; - - if (!summary.synced) { - throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to read UTXO set because coinstatsindex is still syncing. Current height: %d", summary.best_block_height)); - } - } throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } return ret; @@ -1249,12 +1334,10 @@ static RPCHelpMan gettxout() {RPCResult::Type::STR_AMOUNT, "value", "The transaction value in " + CURRENCY_UNIT}, {RPCResult::Type::OBJ, "scriptPubKey", "", { {RPCResult::Type::STR, "asm", ""}, + {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"}, {RPCResult::Type::STR_HEX, "hex", ""}, - {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"}, {RPCResult::Type::STR, "type", "The type, eg pubkeyhash"}, - {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"}, - {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses", - {{RPCResult::Type::STR, "address", "bitcoin address"}}}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, }}, {RPCResult::Type::BOOL, "coinbase", "Coinbase or not"}, }}, @@ -1323,7 +1406,7 @@ static RPCHelpMan verifychain() "\nVerifies blockchain database.\n", { {"checklevel", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL)}, - strprintf("How thorough the block verification is:\n - %s", Join(CHECKLEVEL_DOC, "\n- "))}, + strprintf("How thorough the block verification is:\n%s", MakeUnorderedList(CHECKLEVEL_DOC))}, {"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS)}, "The number of blocks to check."}, }, RPCResult{ @@ -1334,7 +1417,7 @@ static RPCHelpMan verifychain() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const int check_level(request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].get_int()); + const int check_level{request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].get_int()}; const int check_depth{request.params[1].isNull() ? DEFAULT_CHECKBLOCKS : request.params[1].get_int()}; ChainstateManager& chainman = EnsureAnyChainman(request.context); @@ -1342,12 +1425,12 @@ static RPCHelpMan verifychain() CChainState& active_chainstate = chainman.ActiveChainstate(); return CVerifyDB().VerifyDB( - active_chainstate, Params(), active_chainstate.CoinsTip(), check_level, check_depth); + active_chainstate, Params().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth); }, }; } -static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue& softforks, const Consensus::Params& params, Consensus::BuriedDeployment dep) +static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const Consensus::Params& params, Consensus::BuriedDeployment dep) { // For buried deployments. @@ -1355,62 +1438,94 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue& UniValue rv(UniValue::VOBJ); rv.pushKV("type", "buried"); - // getblockchaininfo reports the softfork as active from when the chain height is + // getdeploymentinfo reports the softfork as active from when the chain height is // one below the activation height - rv.pushKV("active", DeploymentActiveAfter(active_chain_tip, params, dep)); + rv.pushKV("active", DeploymentActiveAfter(blockindex, params, dep)); rv.pushKV("height", params.DeploymentHeight(dep)); softforks.pushKV(DeploymentName(dep), rv); } -static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue& softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) +static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) { // For BIP9 deployments. if (!DeploymentEnabled(consensusParams, id)) return; + if (blockindex == nullptr) return; + + auto get_state_name = [](const ThresholdState state) -> std::string { + switch (state) { + case ThresholdState::DEFINED: return "defined"; + case ThresholdState::STARTED: return "started"; + case ThresholdState::LOCKED_IN: return "locked_in"; + case ThresholdState::ACTIVE: return "active"; + case ThresholdState::FAILED: return "failed"; + } + return "invalid"; + }; UniValue bip9(UniValue::VOBJ); - const ThresholdState thresholdState = g_versionbitscache.State(active_chain_tip, consensusParams, id); - switch (thresholdState) { - case ThresholdState::DEFINED: bip9.pushKV("status", "defined"); break; - case ThresholdState::STARTED: bip9.pushKV("status", "started"); break; - case ThresholdState::LOCKED_IN: bip9.pushKV("status", "locked_in"); break; - case ThresholdState::ACTIVE: bip9.pushKV("status", "active"); break; - case ThresholdState::FAILED: bip9.pushKV("status", "failed"); break; - } - if (ThresholdState::STARTED == thresholdState) - { + + const ThresholdState next_state = g_versionbitscache.State(blockindex, consensusParams, id); + const ThresholdState current_state = g_versionbitscache.State(blockindex->pprev, consensusParams, id); + + const bool has_signal = (ThresholdState::STARTED == current_state || ThresholdState::LOCKED_IN == current_state); + + // BIP9 parameters + if (has_signal) { bip9.pushKV("bit", consensusParams.vDeployments[id].bit); } bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime); bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); - int64_t since_height = g_versionbitscache.StateSinceHeight(active_chain_tip, consensusParams, id); - bip9.pushKV("since", since_height); - if (ThresholdState::STARTED == thresholdState) - { + bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height); + + // BIP9 status + bip9.pushKV("status", get_state_name(current_state)); + bip9.pushKV("since", g_versionbitscache.StateSinceHeight(blockindex->pprev, consensusParams, id)); + bip9.pushKV("status_next", get_state_name(next_state)); + + // BIP9 signalling status, if applicable + if (has_signal) { UniValue statsUV(UniValue::VOBJ); - BIP9Stats statsStruct = g_versionbitscache.Statistics(active_chain_tip, consensusParams, id); + std::vector<bool> signals; + BIP9Stats statsStruct = g_versionbitscache.Statistics(blockindex, consensusParams, id, &signals); statsUV.pushKV("period", statsStruct.period); - statsUV.pushKV("threshold", statsStruct.threshold); statsUV.pushKV("elapsed", statsStruct.elapsed); statsUV.pushKV("count", statsStruct.count); - statsUV.pushKV("possible", statsStruct.possible); + if (ThresholdState::LOCKED_IN != current_state) { + statsUV.pushKV("threshold", statsStruct.threshold); + statsUV.pushKV("possible", statsStruct.possible); + } bip9.pushKV("statistics", statsUV); + + std::string sig; + sig.reserve(signals.size()); + for (const bool s : signals) { + sig.push_back(s ? '#' : '-'); + } + bip9.pushKV("signalling", sig); } - bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height); UniValue rv(UniValue::VOBJ); rv.pushKV("type", "bip9"); - rv.pushKV("bip9", bip9); - if (ThresholdState::ACTIVE == thresholdState) { - rv.pushKV("height", since_height); + if (ThresholdState::ACTIVE == next_state) { + rv.pushKV("height", g_versionbitscache.StateSinceHeight(blockindex, consensusParams, id)); } - rv.pushKV("active", ThresholdState::ACTIVE == thresholdState); + rv.pushKV("active", ThresholdState::ACTIVE == next_state); + rv.pushKV("bip9", bip9); softforks.pushKV(DeploymentName(id), rv); } +namespace { +/* TODO: when -deprecatedrpc=softforks is removed, drop these */ +UniValue DeploymentInfo(const CBlockIndex* tip, const Consensus::Params& consensusParams); +extern const std::vector<RPCResult> RPCHelpForDeployment; +} + +// used by rest.cpp:rest_chaininfo, so cannot be static RPCHelpMan getblockchaininfo() { + /* TODO: from v24, remove -deprecatedrpc=softforks */ return RPCHelpMan{"getblockchaininfo", "Returns an object containing various state info regarding blockchain processing.\n", {}, @@ -1422,40 +1537,21 @@ RPCHelpMan getblockchaininfo() {RPCResult::Type::NUM, "headers", "the current number of headers we have validated"}, {RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"}, {RPCResult::Type::NUM, "difficulty", "the current difficulty"}, - {RPCResult::Type::NUM, "mediantime", "median time for the current best block"}, + {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME}, + {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::NUM, "verificationprogress", "estimate of verification progress [0..1]"}, {RPCResult::Type::BOOL, "initialblockdownload", "(debug information) estimate of whether this node is in Initial Block Download mode"}, {RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"}, {RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"}, {RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"}, - {RPCResult::Type::NUM, "pruneheight", "lowest-height complete block stored (only present if pruning is enabled)"}, - {RPCResult::Type::BOOL, "automatic_pruning", "whether automatic pruning is enabled (only present if pruning is enabled)"}, - {RPCResult::Type::NUM, "prune_target_size", "the target size used by pruning (only present if automatic pruning is enabled)"}, - {RPCResult::Type::OBJ_DYN, "softforks", "status of softforks", + {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "lowest-height complete block stored (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::OBJ_DYN, "softforks", "(DEPRECATED, returned only if config option -deprecatedrpc=softforks is passed) status of softforks", { {RPCResult::Type::OBJ, "xxxx", "name of the softfork", - { - {RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""}, - {RPCResult::Type::OBJ, "bip9", "status of bip9 softforks (only for \"bip9\" type)", - { - {RPCResult::Type::STR, "status", "one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\""}, - {RPCResult::Type::NUM, "bit", "the bit (0-28) in the block version field used to signal this softfork (only for \"started\" status)"}, - {RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at which the bit gains its meaning"}, - {RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the deployment is considered failed if not yet locked in"}, - {RPCResult::Type::NUM, "since", "height of the first block to which the status applies"}, - {RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"}, - {RPCResult::Type::OBJ, "statistics", "numeric statistics about BIP9 signalling for a softfork (only for \"started\" status)", - { - {RPCResult::Type::NUM, "period", "the length in blocks of the BIP9 signalling period"}, - {RPCResult::Type::NUM, "threshold", "the number of blocks with the version bit set required to activate the feature"}, - {RPCResult::Type::NUM, "elapsed", "the number of blocks elapsed since the beginning of the current period"}, - {RPCResult::Type::NUM, "count", "the number of blocks with the version bit set in the current period"}, - {RPCResult::Type::BOOL, "possible", "returns false if there are not enough blocks left in this period to pass activation threshold"}, - }}, - }}, - {RPCResult::Type::NUM, "height", "height of the first block which the rules are or will be enforced (only for \"buried\" type, or \"bip9\" type with \"active\" status)"}, - {RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and the next block"}, - }}, + RPCHelpForDeployment + }, }}, {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"}, }}, @@ -1465,6 +1561,7 @@ RPCHelpMan getblockchaininfo() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + const ArgsManager& args{EnsureAnyArgsman(request.context)}; ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); CChainState& active_chainstate = chainman.ActiveChainstate(); @@ -1478,13 +1575,14 @@ RPCHelpMan getblockchaininfo() obj.pushKV("headers", pindexBestHeader ? pindexBestHeader->nHeight : -1); obj.pushKV("bestblockhash", tip->GetBlockHash().GetHex()); obj.pushKV("difficulty", (double)GetDifficulty(tip)); + obj.pushKV("time", (int64_t)tip->nTime); obj.pushKV("mediantime", (int64_t)tip->GetMedianTimePast()); obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), tip)); obj.pushKV("initialblockdownload", active_chainstate.IsInitialBlockDownload()); obj.pushKV("chainwork", tip->nChainWork.GetHex()); - obj.pushKV("size_on_disk", CalculateCurrentUsage()); - obj.pushKV("pruned", fPruneMode); - if (fPruneMode) { + obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage()); + obj.pushKV("pruned", node::fPruneMode); + if (node::fPruneMode) { const CBlockIndex* block = tip; CHECK_NONFATAL(block); while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { @@ -1494,23 +1592,17 @@ RPCHelpMan getblockchaininfo() obj.pushKV("pruneheight", block->nHeight); // if 0, execution bypasses the whole if block. - bool automatic_pruning = (gArgs.GetArg("-prune", 0) != 1); + bool automatic_pruning{args.GetIntArg("-prune", 0) != 1}; obj.pushKV("automatic_pruning", automatic_pruning); if (automatic_pruning) { - obj.pushKV("prune_target_size", nPruneTarget); + obj.pushKV("prune_target_size", node::nPruneTarget); } } - const Consensus::Params& consensusParams = Params().GetConsensus(); - UniValue softforks(UniValue::VOBJ); - SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB); - SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_DERSIG); - SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_CLTV); - SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_CSV); - SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_SEGWIT); - SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_TESTDUMMY); - SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_TAPROOT); - obj.pushKV("softforks", softforks); + if (IsDeprecatedRPCEnabled("softforks")) { + const Consensus::Params& consensusParams = Params().GetConsensus(); + obj.pushKV("softforks", DeploymentInfo(tip, consensusParams)); + } obj.pushKV("warnings", GetWarnings(false).original); return obj; @@ -1518,6 +1610,92 @@ RPCHelpMan getblockchaininfo() }; } +namespace { +const std::vector<RPCResult> RPCHelpForDeployment{ + {RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""}, + {RPCResult::Type::NUM, "height", /*optional=*/true, "height of the first block which the rules are or will be enforced (only for \"buried\" type, or \"bip9\" type with \"active\" status)"}, + {RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and the next block"}, + {RPCResult::Type::OBJ, "bip9", /*optional=*/true, "status of bip9 softforks (only for \"bip9\" type)", + { + {RPCResult::Type::NUM, "bit", /*optional=*/true, "the bit (0-28) in the block version field used to signal this softfork (only for \"started\" and \"locked_in\" status)"}, + {RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at which the bit gains its meaning"}, + {RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the deployment is considered failed if not yet locked in"}, + {RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"}, + {RPCResult::Type::STR, "status", "status of deployment at specified block (one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\")"}, + {RPCResult::Type::NUM, "since", "height of the first block to which the status applies"}, + {RPCResult::Type::STR, "status_next", "status of deployment at the next block"}, + {RPCResult::Type::OBJ, "statistics", /*optional=*/true, "numeric statistics about signalling for a softfork (only for \"started\" and \"locked_in\" status)", + { + {RPCResult::Type::NUM, "period", "the length in blocks of the signalling period"}, + {RPCResult::Type::NUM, "threshold", /*optional=*/true, "the number of blocks with the version bit set required to activate the feature (only for \"started\" status)"}, + {RPCResult::Type::NUM, "elapsed", "the number of blocks elapsed since the beginning of the current period"}, + {RPCResult::Type::NUM, "count", "the number of blocks with the version bit set in the current period"}, + {RPCResult::Type::BOOL, "possible", /*optional=*/true, "returns false if there are not enough blocks left in this period to pass activation threshold (only for \"started\" status)"}, + }}, + {RPCResult::Type::STR, "signalling", "indicates blocks that signalled with a # and blocks that did not with a -"}, + }}, +}; + +UniValue DeploymentInfo(const CBlockIndex* blockindex, const Consensus::Params& consensusParams) +{ + UniValue softforks(UniValue::VOBJ); + SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB); + SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_DERSIG); + SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_CLTV); + SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_CSV); + SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_SEGWIT); + SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_TESTDUMMY); + SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_TAPROOT); + return softforks; +} +} // anon namespace + +static RPCHelpMan getdeploymentinfo() +{ + return RPCHelpMan{"getdeploymentinfo", + "Returns an object containing various state info regarding deployments of consensus changes.", + { + {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Default{"hash of current chain tip"}, "The block hash at which to query deployment state"}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::STR, "hash", "requested block hash (or tip)"}, + {RPCResult::Type::NUM, "height", "requested block height (or tip)"}, + {RPCResult::Type::OBJ, "deployments", "", { + {RPCResult::Type::OBJ, "xxxx", "name of the deployment", RPCHelpForDeployment} + }}, + } + }, + RPCExamples{ HelpExampleCli("getdeploymentinfo", "") + HelpExampleRpc("getdeploymentinfo", "") }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + const ChainstateManager& chainman = EnsureAnyChainman(request.context); + LOCK(cs_main); + const CChainState& active_chainstate = chainman.ActiveChainstate(); + + const CBlockIndex* blockindex; + if (request.params[0].isNull()) { + blockindex = active_chainstate.m_chain.Tip(); + CHECK_NONFATAL(blockindex); + } else { + const uint256 hash(ParseHashV(request.params[0], "blockhash")); + blockindex = chainman.m_blockman.LookupBlockIndex(hash); + if (!blockindex) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } + } + + const Consensus::Params& consensusParams = Params().GetConsensus(); + + UniValue deploymentinfo(UniValue::VOBJ); + deploymentinfo.pushKV("hash", blockindex->GetBlockHash().ToString()); + deploymentinfo.pushKV("height", blockindex->nHeight); + deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, consensusParams)); + return deploymentinfo; + }, + }; +} + /** Comparison function for sorting the getchaintips heads. */ struct CompareBlocksByHeight { @@ -1641,7 +1819,7 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool) ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize()); ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage()); ret.pushKV("total_fee", ValueFromAmount(pool.GetTotalFee())); - size_t maxmempool = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; + size_t maxmempool = gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; ret.pushKV("maxmempool", (int64_t) maxmempool); ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK())); ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())); @@ -1661,7 +1839,7 @@ static RPCHelpMan getmempoolinfo() {RPCResult::Type::NUM, "size", "Current tx count"}, {RPCResult::Type::NUM, "bytes", "Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted"}, {RPCResult::Type::NUM, "usage", "Total memory usage for the mempool"}, - {RPCResult::Type::STR_AMOUNT, "total_fee", "Total fees for the mempool in " + CURRENCY_UNIT + ", ignoring modified fees through prioritizetransaction"}, + {RPCResult::Type::STR_AMOUNT, "total_fee", "Total fees for the mempool in " + CURRENCY_UNIT + ", ignoring modified fees through prioritisetransaction"}, {RPCResult::Type::NUM, "maxmempool", "Maximum memory usage for the mempool"}, {RPCResult::Type::STR_AMOUNT, "mempoolminfee", "Minimum fee rate in " + CURRENCY_UNIT + "/kvB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee"}, {RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"}, @@ -1815,9 +1993,9 @@ static RPCHelpMan getchaintxstats() {RPCResult::Type::STR_HEX, "window_final_block_hash", "The hash of the final block in the window"}, {RPCResult::Type::NUM, "window_final_block_height", "The height of the final block in the window."}, {RPCResult::Type::NUM, "window_block_count", "Size of the window in number of blocks"}, - {RPCResult::Type::NUM, "window_tx_count", /* optional */ true, "The number of transactions in the window. Only returned if \"window_block_count\" is > 0"}, - {RPCResult::Type::NUM, "window_interval", /* optional */ true, "The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0"}, - {RPCResult::Type::NUM, "txrate", /* optional */ true, "The average rate of transactions per second in the window. Only returned if \"window_interval\" is > 0"}, + {RPCResult::Type::NUM, "window_tx_count", /*optional=*/true, "The number of transactions in the window. Only returned if \"window_block_count\" is > 0"}, + {RPCResult::Type::NUM, "window_interval", /*optional=*/true, "The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0"}, + {RPCResult::Type::NUM, "txrate", /*optional=*/true, "The average rate of transactions per second in the window. Only returned if \"window_interval\" is > 0"}, }}, RPCExamples{ HelpExampleCli("getchaintxstats", "") @@ -1924,16 +2102,6 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], } } -void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex) -{ - ScriptPubKeyToUniv(scriptPubKey, out, fIncludeHex, IsDeprecatedRPCEnabled("addresses")); -} - -void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex, int serialize_flags, const CTxUndo* txundo) -{ - TxToUniv(tx, hashBlock, IsDeprecatedRPCEnabled("addresses"), entry, include_hex, serialize_flags, txundo); -} - template<typename T> static inline bool SetHasKeys(const std::set<T>& set) {return false;} template<typename T, typename Tk, typename... Args> @@ -1962,11 +2130,11 @@ static RPCHelpMan getblockstats() RPCResult{ RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::NUM, "avgfee", "Average fee in the block"}, - {RPCResult::Type::NUM, "avgfeerate", "Average feerate (in satoshis per virtual byte)"}, - {RPCResult::Type::NUM, "avgtxsize", "Average transaction size"}, - {RPCResult::Type::STR_HEX, "blockhash", "The block hash (to check for potential reorgs)"}, - {RPCResult::Type::ARR_FIXED, "feerate_percentiles", "Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte)", + {RPCResult::Type::NUM, "avgfee", /*optional=*/true, "Average fee in the block"}, + {RPCResult::Type::NUM, "avgfeerate", /*optional=*/true, "Average feerate (in satoshis per virtual byte)"}, + {RPCResult::Type::NUM, "avgtxsize", /*optional=*/true, "Average transaction size"}, + {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The block hash (to check for potential reorgs)"}, + {RPCResult::Type::ARR_FIXED, "feerate_percentiles", /*optional=*/true, "Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte)", { {RPCResult::Type::NUM, "10th_percentile_feerate", "The 10th percentile feerate"}, {RPCResult::Type::NUM, "25th_percentile_feerate", "The 25th percentile feerate"}, @@ -1974,30 +2142,30 @@ static RPCHelpMan getblockstats() {RPCResult::Type::NUM, "75th_percentile_feerate", "The 75th percentile feerate"}, {RPCResult::Type::NUM, "90th_percentile_feerate", "The 90th percentile feerate"}, }}, - {RPCResult::Type::NUM, "height", "The height of the block"}, - {RPCResult::Type::NUM, "ins", "The number of inputs (excluding coinbase)"}, - {RPCResult::Type::NUM, "maxfee", "Maximum fee in the block"}, - {RPCResult::Type::NUM, "maxfeerate", "Maximum feerate (in satoshis per virtual byte)"}, - {RPCResult::Type::NUM, "maxtxsize", "Maximum transaction size"}, - {RPCResult::Type::NUM, "medianfee", "Truncated median fee in the block"}, - {RPCResult::Type::NUM, "mediantime", "The block median time past"}, - {RPCResult::Type::NUM, "mediantxsize", "Truncated median transaction size"}, - {RPCResult::Type::NUM, "minfee", "Minimum fee in the block"}, - {RPCResult::Type::NUM, "minfeerate", "Minimum feerate (in satoshis per virtual byte)"}, - {RPCResult::Type::NUM, "mintxsize", "Minimum transaction size"}, - {RPCResult::Type::NUM, "outs", "The number of outputs"}, - {RPCResult::Type::NUM, "subsidy", "The block subsidy"}, - {RPCResult::Type::NUM, "swtotal_size", "Total size of all segwit transactions"}, - {RPCResult::Type::NUM, "swtotal_weight", "Total weight of all segwit transactions"}, - {RPCResult::Type::NUM, "swtxs", "The number of segwit transactions"}, - {RPCResult::Type::NUM, "time", "The block time"}, - {RPCResult::Type::NUM, "total_out", "Total amount in all outputs (excluding coinbase and thus reward [ie subsidy + totalfee])"}, - {RPCResult::Type::NUM, "total_size", "Total size of all non-coinbase transactions"}, - {RPCResult::Type::NUM, "total_weight", "Total weight of all non-coinbase transactions"}, - {RPCResult::Type::NUM, "totalfee", "The fee total"}, - {RPCResult::Type::NUM, "txs", "The number of transactions (including coinbase)"}, - {RPCResult::Type::NUM, "utxo_increase", "The increase/decrease in the number of unspent outputs"}, - {RPCResult::Type::NUM, "utxo_size_inc", "The increase/decrease in size for the utxo index (not discounting op_return and similar)"}, + {RPCResult::Type::NUM, "height", /*optional=*/true, "The height of the block"}, + {RPCResult::Type::NUM, "ins", /*optional=*/true, "The number of inputs (excluding coinbase)"}, + {RPCResult::Type::NUM, "maxfee", /*optional=*/true, "Maximum fee in the block"}, + {RPCResult::Type::NUM, "maxfeerate", /*optional=*/true, "Maximum feerate (in satoshis per virtual byte)"}, + {RPCResult::Type::NUM, "maxtxsize", /*optional=*/true, "Maximum transaction size"}, + {RPCResult::Type::NUM, "medianfee", /*optional=*/true, "Truncated median fee in the block"}, + {RPCResult::Type::NUM, "mediantime", /*optional=*/true, "The block median time past"}, + {RPCResult::Type::NUM, "mediantxsize", /*optional=*/true, "Truncated median transaction size"}, + {RPCResult::Type::NUM, "minfee", /*optional=*/true, "Minimum fee in the block"}, + {RPCResult::Type::NUM, "minfeerate", /*optional=*/true, "Minimum feerate (in satoshis per virtual byte)"}, + {RPCResult::Type::NUM, "mintxsize", /*optional=*/true, "Minimum transaction size"}, + {RPCResult::Type::NUM, "outs", /*optional=*/true, "The number of outputs"}, + {RPCResult::Type::NUM, "subsidy", /*optional=*/true, "The block subsidy"}, + {RPCResult::Type::NUM, "swtotal_size", /*optional=*/true, "Total size of all segwit transactions"}, + {RPCResult::Type::NUM, "swtotal_weight", /*optional=*/true, "Total weight of all segwit transactions"}, + {RPCResult::Type::NUM, "swtxs", /*optional=*/true, "The number of segwit transactions"}, + {RPCResult::Type::NUM, "time", /*optional=*/true, "The block time"}, + {RPCResult::Type::NUM, "total_out", /*optional=*/true, "Total amount in all outputs (excluding coinbase and thus reward [ie subsidy + totalfee])"}, + {RPCResult::Type::NUM, "total_size", /*optional=*/true, "Total size of all non-coinbase transactions"}, + {RPCResult::Type::NUM, "total_weight", /*optional=*/true, "Total weight of all non-coinbase transactions"}, + {RPCResult::Type::NUM, "totalfee", /*optional=*/true, "The fee total"}, + {RPCResult::Type::NUM, "txs", /*optional=*/true, "The number of transactions (including coinbase)"}, + {RPCResult::Type::NUM, "utxo_increase", /*optional=*/true, "The increase/decrease in the number of unspent outputs"}, + {RPCResult::Type::NUM, "utxo_size_inc", /*optional=*/true, "The increase/decrease in size for the utxo index (not discounting op_return and similar)"}, }}, RPCExamples{ HelpExampleCli("getblockstats", R"('"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"' '["minfeerate","avgfeerate"]')") + @@ -2175,7 +2343,7 @@ static RPCHelpMan getblockstats() for (const std::string& stat : stats) { const UniValue& value = ret_all[stat]; if (value.isNull()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid selected statistic %s", stat)); + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid selected statistic '%s'", stat)); } ret.pushKV(stat, value); } @@ -2189,13 +2357,18 @@ static RPCHelpMan savemempool() return RPCHelpMan{"savemempool", "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n", {}, - RPCResult{RPCResult::Type::NONE, "", ""}, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "filename", "the directory and file where the mempool was saved"}, + }}, RPCExamples{ HelpExampleCli("savemempool", "") + HelpExampleRpc("savemempool", "") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + const ArgsManager& args{EnsureAnyArgsman(request.context)}; const CTxMemPool& mempool = EnsureAnyMemPool(request.context); if (!mempool.IsLoaded()) { @@ -2206,7 +2379,10 @@ static RPCHelpMan savemempool() throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk"); } - return NullUniValue; + UniValue ret(UniValue::VOBJ); + ret.pushKV("filename", fs::path((args.GetDataDirNet() / "mempool.dat")).u8string()); + + return ret; }, }; } @@ -2274,6 +2450,9 @@ public: static RPCHelpMan scantxoutset() { + // scriptPubKey corresponding to mainnet address 12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S + const std::string EXAMPLE_DESCRIPTOR_RAW = "raw(76a91411b366edfc0a8b66feebae5c2e25a7b6a5d1cf3188ac)#fm24fxxy"; + return RPCHelpMan{"scantxoutset", "\nScans the unspent transaction output set for entries that match certain output descriptors.\n" "Examples of output descriptors are:\n" @@ -2331,7 +2510,14 @@ static RPCHelpMan scantxoutset() {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + CURRENCY_UNIT}, }}, }, - RPCExamples{""}, + RPCExamples{ + HelpExampleCli("scantxoutset", "start \'[\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]\'") + + HelpExampleCli("scantxoutset", "status") + + HelpExampleCli("scantxoutset", "abort") + + HelpExampleRpc("scantxoutset", "\"start\", [\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]") + + HelpExampleRpc("scantxoutset", "\"status\"") + + HelpExampleRpc("scantxoutset", "\"abort\"") + }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}); @@ -2519,13 +2705,9 @@ static RPCHelpMan dumptxoutset() { return RPCHelpMan{ "dumptxoutset", - "\nWrite the serialized UTXO set to disk.\n", + "Write the serialized UTXO set to disk.", { - {"path", - RPCArg::Type::STR, - RPCArg::Optional::NO, - /* default_val */ "", - "path to the output file. If relative, will be prefixed by datadir."}, + {"path", RPCArg::Type::STR, RPCArg::Optional::NO, "Path to the output file. If relative, will be prefixed by datadir."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -2534,6 +2716,8 @@ static RPCHelpMan dumptxoutset() {RPCResult::Type::STR_HEX, "base_hash", "the hash of the base of the snapshot"}, {RPCResult::Type::NUM, "base_height", "the height of the base of the snapshot"}, {RPCResult::Type::STR, "path", "the absolute path that the snapshot was written to"}, + {RPCResult::Type::STR_HEX, "txoutset_hash", "the hash of the UTXO set contents"}, + {RPCResult::Type::NUM, "nchaintx", "the number of transactions in the chain up to and including the base block"}, } }, RPCExamples{ @@ -2541,34 +2725,41 @@ static RPCHelpMan dumptxoutset() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const fs::path path = fsbridge::AbsPathJoin(gArgs.GetDataDirNet(), request.params[0].get_str()); + const ArgsManager& args{EnsureAnyArgsman(request.context)}; + const fs::path path = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str())); // Write to a temporary path and then move into `path` on completion // to avoid confusion due to an interruption. - const fs::path temppath = fsbridge::AbsPathJoin(gArgs.GetDataDirNet(), request.params[0].get_str() + ".incomplete"); + const fs::path temppath = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str() + ".incomplete")); if (fs::exists(path)) { throw JSONRPCError( RPC_INVALID_PARAMETER, - path.string() + " already exists. If you are sure this is what you want, " + path.u8string() + " already exists. If you are sure this is what you want, " "move it out of the way first"); } FILE* file{fsbridge::fopen(temppath, "wb")}; CAutoFile afile{file, SER_DISK, CLIENT_VERSION}; NodeContext& node = EnsureAnyNodeContext(request.context); - UniValue result = CreateUTXOSnapshot(node, node.chainman->ActiveChainstate(), afile); + UniValue result = CreateUTXOSnapshot( + node, node.chainman->ActiveChainstate(), afile, path, temppath); fs::rename(temppath, path); - result.pushKV("path", path.string()); + result.pushKV("path", path.u8string()); return result; }, }; } -UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFile& afile) +UniValue CreateUTXOSnapshot( + NodeContext& node, + CChainState& chainstate, + CAutoFile& afile, + const fs::path& path, + const fs::path& temppath) { std::unique_ptr<CCoinsViewCursor> pcursor; - CCoinsStats stats{CoinStatsHashType::NONE}; + CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED}; CBlockIndex* tip; { @@ -2597,6 +2788,10 @@ UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFil CHECK_NONFATAL(tip); } + LOG_TIME_SECONDS(strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)", + tip->nHeight, tip->GetBlockHash().ToString(), + fs::PathToString(path), fs::PathToString(temppath))); + SnapshotMetadata metadata{tip->GetBlockHash(), stats.coins_count, tip->nChainTx}; afile << metadata; @@ -2622,7 +2817,11 @@ UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFil result.pushKV("coins_written", stats.coins_count); result.pushKV("base_hash", tip->GetBlockHash().ToString()); result.pushKV("base_height", tip->nHeight); - + result.pushKV("path", path.u8string()); + result.pushKV("txoutset_hash", stats.hashSerialized.ToString()); + // Cast required because univalue doesn't have serialization specified for + // `unsigned int`, nChainTx's type. + result.pushKV("nchaintx", uint64_t{tip->nChainTx}); return result; } @@ -2638,10 +2837,12 @@ static const CRPCCommand commands[] = { "blockchain", &getbestblockhash, }, { "blockchain", &getblockcount, }, { "blockchain", &getblock, }, + { "blockchain", &getblockfrompeer, }, { "blockchain", &getblockhash, }, { "blockchain", &getblockheader, }, { "blockchain", &getchaintips, }, { "blockchain", &getdifficulty, }, + { "blockchain", &getdeploymentinfo, }, { "blockchain", &getmempoolancestors, }, { "blockchain", &getmempooldescendants, }, { "blockchain", &getmempoolentry, }, diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index ffb6f03b47..1f51d7c1ad 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -1,12 +1,13 @@ -// Copyright (c) 2017-2020 The Bitcoin Core developers +// Copyright (c) 2017-2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_RPC_BLOCKCHAIN_H #define BITCOIN_RPC_BLOCKCHAIN_H -#include <amount.h> +#include <consensus/amount.h> #include <core_io.h> +#include <fs.h> #include <streams.h> #include <sync.h> @@ -18,12 +19,12 @@ extern RecursiveMutex cs_main; class CBlock; class CBlockIndex; -class CBlockPolicyEstimator; class CChainState; class CTxMemPool; -class ChainstateManager; class UniValue; +namespace node { struct NodeContext; +} // namespace node static constexpr int NUM_GETBLOCKSTATS_PERCENTILES = 5; @@ -39,7 +40,7 @@ double GetDifficulty(const CBlockIndex* blockindex); void RPCNotifyBlockChange(const CBlockIndex*); /** Block description to JSON */ -UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, bool txDetails = false) LOCKS_EXCLUDED(cs_main); +UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity) LOCKS_EXCLUDED(cs_main); /** Mempool information to JSON */ UniValue MempoolInfoToJSON(const CTxMemPool& pool); @@ -53,21 +54,15 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex /** Used by getblockstats to get feerates at different percentiles by weight */ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight); -void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex); -void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0, const CTxUndo* txundo = nullptr); - -NodeContext& EnsureAnyNodeContext(const std::any& context); -CTxMemPool& EnsureMemPool(const NodeContext& node); -CTxMemPool& EnsureAnyMemPool(const std::any& context); -ChainstateManager& EnsureChainman(const NodeContext& node); -ChainstateManager& EnsureAnyChainman(const std::any& context); -CBlockPolicyEstimator& EnsureFeeEstimator(const NodeContext& node); -CBlockPolicyEstimator& EnsureAnyFeeEstimator(const std::any& context); - /** * Helper to create UTXO snapshots given a chainstate and a file handle. * @return a UniValue map containing metadata about the snapshot. */ -UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFile& afile); - -#endif +UniValue CreateUTXOSnapshot( + node::NodeContext& node, + CChainState& chainstate, + CAutoFile& afile, + const fs::path& path, + const fs::path& tmppath); + +#endif // BITCOIN_RPC_BLOCKCHAIN_H diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 9c8582c7a3..c480a093a4 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -46,16 +46,21 @@ static const CRPCConvertParam vRPCConvertParams[] = { "settxfee", 0, "amount" }, { "sethdseed", 0, "newkeypool" }, { "getreceivedbyaddress", 1, "minconf" }, + { "getreceivedbyaddress", 2, "include_immature_coinbase" }, { "getreceivedbylabel", 1, "minconf" }, + { "getreceivedbylabel", 2, "include_immature_coinbase" }, { "listreceivedbyaddress", 0, "minconf" }, { "listreceivedbyaddress", 1, "include_empty" }, { "listreceivedbyaddress", 2, "include_watchonly" }, + { "listreceivedbyaddress", 4, "include_immature_coinbase" }, { "listreceivedbylabel", 0, "minconf" }, { "listreceivedbylabel", 1, "include_empty" }, { "listreceivedbylabel", 2, "include_watchonly" }, + { "listreceivedbylabel", 3, "include_immature_coinbase" }, { "getbalance", 1, "minconf" }, { "getbalance", 2, "include_watchonly" }, { "getbalance", 3, "avoid_reuse" }, + { "getblockfrompeer", 1, "peer_id" }, { "getblockhash", 0, "height" }, { "waitforblockheight", 0, "height" }, { "waitforblockheight", 1, "timeout" }, @@ -115,6 +120,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "walletcreatefundedpsbt", 4, "bip32derivs" }, { "walletprocesspsbt", 1, "sign" }, { "walletprocesspsbt", 3, "bip32derivs" }, + { "walletprocesspsbt", 4, "finalize" }, { "createpsbt", 0, "inputs" }, { "createpsbt", 1, "outputs" }, { "createpsbt", 2, "locktime" }, @@ -131,6 +137,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "gettxoutsetinfo", 2, "use_index"}, { "lockunspent", 0, "unlock" }, { "lockunspent", 1, "transactions" }, + { "lockunspent", 2, "persistent" }, { "send", 0, "outputs" }, { "send", 1, "conf_target" }, { "send", 3, "fee_rate"}, @@ -142,6 +149,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "importmulti", 0, "requests" }, { "importmulti", 1, "options" }, { "importdescriptors", 0, "requests" }, + { "listdescriptors", 0, "private" }, { "verifychain", 0, "checklevel" }, { "verifychain", 1, "nblocks" }, { "getblockstats", 0, "hash_or_height" }, @@ -186,10 +194,12 @@ static const CRPCConvertParam vRPCConvertParams[] = { "createwallet", 5, "descriptors"}, { "createwallet", 6, "load_on_startup"}, { "createwallet", 7, "external_signer"}, + { "restorewallet", 2, "load_on_startup"}, { "loadwallet", 1, "load_on_startup"}, { "unloadwallet", 1, "load_on_startup"}, { "getnodeaddresses", 0, "count"}, { "addpeeraddress", 1, "port"}, + { "addpeeraddress", 2, "tried"}, { "stop", 0, "wait" }, }; // clang-format on diff --git a/src/rpc/external_signer.cpp b/src/rpc/external_signer.cpp index 6ec2b1a07f..60ec15e904 100644 --- a/src/rpc/external_signer.cpp +++ b/src/rpc/external_signer.cpp @@ -24,8 +24,11 @@ static RPCHelpMan enumeratesigners() { {RPCResult::Type::ARR, "signers", /* optional */ false, "", { - {RPCResult::Type::STR_HEX, "masterkeyfingerprint", "Master key fingerprint"}, - {RPCResult::Type::STR, "name", "Device name"}, + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "fingerprint", "Master key fingerprint"}, + {RPCResult::Type::STR, "name", "Device name"}, + }}, }, } } diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 692096367c..1d1ae92c58 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1,11 +1,11 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2021 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 <amount.h> #include <chain.h> #include <chainparams.h> +#include <consensus/amount.h> #include <consensus/consensus.h> #include <consensus/params.h> #include <consensus/validation.h> @@ -13,15 +13,15 @@ #include <deploymentinfo.h> #include <deploymentstatus.h> #include <key_io.h> -#include <miner.h> #include <net.h> #include <node/context.h> +#include <node/miner.h> #include <policy/fees.h> #include <pow.h> #include <rpc/blockchain.h> #include <rpc/mining.h> -#include <rpc/net.h> #include <rpc/server.h> +#include <rpc/server_util.h> #include <rpc/util.h> #include <script/descriptor.h> #include <script/script.h> @@ -41,6 +41,13 @@ #include <memory> #include <stdint.h> +using node::BlockAssembler; +using node::CBlockTemplate; +using node::IncrementExtraNonce; +using node::NodeContext; +using node::RegenerateCommitments; +using node::UpdateTime; + /** * Return average network hashes per second based on the last 'lookup' blocks, * or from the last difficulty change if 'lookup' is nonpositive. @@ -184,7 +191,7 @@ static bool getScriptFromDescriptor(const std::string& descriptor, CScript& scri FlatSigningProvider provider; std::vector<CScript> scripts; if (!desc->Expand(0, key_provider, scripts, provider)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys")); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot derive script without private keys"); } // Combo descriptors can have 2 or 4 scripts, so we can't just check scripts.size() == 1 @@ -210,9 +217,9 @@ static RPCHelpMan generatetodescriptor() { return RPCHelpMan{ "generatetodescriptor", - "\nMine blocks immediately to a specified descriptor (before the RPC call returns)\n", + "Mine to a specified descriptor and return the block hashes.", { - {"num_blocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."}, + {"num_blocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated."}, {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor to send the newly generated bitcoin to."}, {"maxtries", RPCArg::Type::NUM, RPCArg::Default{DEFAULT_MAX_TRIES}, "How many iterations to try."}, }, @@ -254,18 +261,18 @@ static RPCHelpMan generate() static RPCHelpMan generatetoaddress() { return RPCHelpMan{"generatetoaddress", - "\nMine blocks immediately to a specified address (before the RPC call returns)\n", - { - {"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."}, - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address to send the newly generated bitcoin to."}, - {"maxtries", RPCArg::Type::NUM, RPCArg::Default{DEFAULT_MAX_TRIES}, "How many iterations to try."}, - }, - RPCResult{ - RPCResult::Type::ARR, "", "hashes of blocks generated", - { - {RPCResult::Type::STR_HEX, "", "blockhash"}, - }}, - RPCExamples{ + "Mine to a specified address and return the block hashes.", + { + {"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated."}, + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address to send the newly generated bitcoin to."}, + {"maxtries", RPCArg::Type::NUM, RPCArg::Default{DEFAULT_MAX_TRIES}, "How many iterations to try."}, + }, + RPCResult{ + RPCResult::Type::ARR, "", "hashes of blocks generated", + { + {RPCResult::Type::STR_HEX, "", "blockhash"}, + }}, + RPCExamples{ "\nGenerate 11 blocks to myaddress\n" + HelpExampleCli("generatetoaddress", "11 \"myaddress\"") + "If you are using the " PACKAGE_NAME " wallet, you can get a new address to send the newly generated bitcoin to with:\n" @@ -295,7 +302,7 @@ static RPCHelpMan generatetoaddress() static RPCHelpMan generateblock() { return RPCHelpMan{"generateblock", - "\nMine a block with a set of ordered transactions immediately to a specified address or descriptor (before the RPC call returns)\n", + "Mine a set of ordered transactions to a specified address or descriptor and return the block hash.", { {"output", RPCArg::Type::STR, RPCArg::Optional::NO, "The address or descriptor to send the newly generated bitcoin to."}, {"transactions", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings which are either txids or raw transactions.\n" @@ -412,8 +419,8 @@ static RPCHelpMan getmininginfo() RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "blocks", "The current block"}, - {RPCResult::Type::NUM, "currentblockweight", /* optional */ true, "The block weight of the last assembled block (only present if a block was ever assembled)"}, - {RPCResult::Type::NUM, "currentblocktx", /* optional */ true, "The number of block transactions of the last assembled block (only present if a block was ever assembled)"}, + {RPCResult::Type::NUM, "currentblockweight", /*optional=*/true, "The block weight of the last assembled block (only present if a block was ever assembled)"}, + {RPCResult::Type::NUM, "currentblocktx", /*optional=*/true, "The number of block transactions of the last assembled block (only present if a block was ever assembled)"}, {RPCResult::Type::NUM, "difficulty", "The current difficulty"}, {RPCResult::Type::NUM, "networkhashps", "The network hashes per second"}, {RPCResult::Type::NUM, "pooledtx", "The size of the mempool"}, @@ -553,6 +560,10 @@ static RPCHelpMan getblocktemplate() { {RPCResult::Type::NUM, "rulename", "identifies the bit number as indicating acceptance and readiness for the named softfork rule"}, }}, + {RPCResult::Type::ARR, "capabilities", "", + { + {RPCResult::Type::STR, "value", "A supported feature, for example 'proposal'"}, + }}, {RPCResult::Type::NUM, "vbrequired", "bit mask of versionbits the server requires set in submissions"}, {RPCResult::Type::STR, "previousblockhash", "The hash of current highest block"}, {RPCResult::Type::ARR, "transactions", "contents of non-coinbase transactions that should be included in the next block", @@ -586,11 +597,12 @@ static RPCHelpMan getblocktemplate() {RPCResult::Type::STR_HEX, "noncerange", "A range of valid nonces"}, {RPCResult::Type::NUM, "sigoplimit", "limit of sigops in blocks"}, {RPCResult::Type::NUM, "sizelimit", "limit of block size"}, - {RPCResult::Type::NUM, "weightlimit", "limit of block weight"}, + {RPCResult::Type::NUM, "weightlimit", /*optional=*/true, "limit of block weight"}, {RPCResult::Type::NUM_TIME, "curtime", "current timestamp in " + UNIX_EPOCH_TIME}, {RPCResult::Type::STR, "bits", "compressed target of next block"}, {RPCResult::Type::NUM, "height", "The height of the next block"}, - {RPCResult::Type::STR, "default_witness_commitment", /* optional */ true, "a valid witness commitment for the unmodified block template"}, + {RPCResult::Type::STR_HEX, "signet_challenge", /*optional=*/true, "Only on signet"}, + {RPCResult::Type::STR_HEX, "default_witness_commitment", /*optional=*/true, "a valid witness commitment for the unmodified block template"}, }}, }, RPCExamples{ @@ -697,7 +709,7 @@ static RPCHelpMan getblocktemplate() std::string lpstr = lpval.get_str(); hashWatchedChain = ParseHashV(lpstr.substr(0, 64), "longpollid"); - nTransactionsUpdatedLastLP = atoi64(lpstr.substr(64)); + nTransactionsUpdatedLastLP = LocaleIndependentAtoi<int64_t>(lpstr.substr(64)); } else { @@ -1005,7 +1017,7 @@ static RPCHelpMan submitblock() bool new_block; auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash()); RegisterSharedValidationInterface(sc); - bool accepted = chainman.ProcessNewBlock(Params(), blockptr, /* fForceProcessing */ true, /* fNewBlock */ &new_block); + bool accepted = chainman.ProcessNewBlock(Params(), blockptr, /*force_processing=*/true, /*new_block=*/&new_block); UnregisterSharedValidationInterface(sc); if (!new_block && accepted) { return "duplicate"; @@ -1077,8 +1089,8 @@ static RPCHelpMan estimatesmartfee() RPCResult{ RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::NUM, "feerate", /* optional */ true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB (only present if no errors were encountered)"}, - {RPCResult::Type::ARR, "errors", /* optional */ true, "Errors encountered during processing (if there are any)", + {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB (only present if no errors were encountered)"}, + {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)", { {RPCResult::Type::STR, "", "error"}, }}, @@ -1089,7 +1101,8 @@ static RPCHelpMan estimatesmartfee() "have been observed to make an estimate for any number of blocks."}, }}, RPCExamples{ - HelpExampleCli("estimatesmartfee", "6") + HelpExampleCli("estimatesmartfee", "6") + + HelpExampleRpc("estimatesmartfee", "6") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { @@ -1097,6 +1110,8 @@ static RPCHelpMan estimatesmartfee() RPCTypeCheckArgument(request.params[0], UniValue::VNUM); CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); + const NodeContext& node = EnsureAnyNodeContext(request.context); + const CTxMemPool& mempool = EnsureMemPool(node); unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); @@ -1112,8 +1127,11 @@ static RPCHelpMan estimatesmartfee() UniValue result(UniValue::VOBJ); UniValue errors(UniValue::VARR); FeeCalculation feeCalc; - CFeeRate feeRate = fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative); + CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)}; if (feeRate != CFeeRate(0)) { + CFeeRate min_mempool_feerate{mempool.GetMinFee(gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000)}; + CFeeRate min_relay_feerate{::minRelayTxFee}; + feeRate = std::max({feeRate, min_mempool_feerate, min_relay_feerate}); result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK())); } else { errors.push_back("Insufficient data or no feerate found"); @@ -1144,12 +1162,12 @@ static RPCHelpMan estimaterawfee() RPCResult{ RPCResult::Type::OBJ, "", "Results are returned for any horizon which tracks blocks up to the confirmation target", { - {RPCResult::Type::OBJ, "short", /* optional */ true, "estimate for short time horizon", + {RPCResult::Type::OBJ, "short", /*optional=*/true, "estimate for short time horizon", { - {RPCResult::Type::NUM, "feerate", /* optional */ true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB"}, + {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB"}, {RPCResult::Type::NUM, "decay", "exponential decay (per block) for historical moving average of confirmation data"}, {RPCResult::Type::NUM, "scale", "The resolution of confirmation targets at this time horizon"}, - {RPCResult::Type::OBJ, "pass", /* optional */ true, "information about the lowest range of feerates to succeed in meeting the threshold", + {RPCResult::Type::OBJ, "pass", /*optional=*/true, "information about the lowest range of feerates to succeed in meeting the threshold", { {RPCResult::Type::NUM, "startrange", "start of feerate range"}, {RPCResult::Type::NUM, "endrange", "end of feerate range"}, @@ -1158,20 +1176,20 @@ static RPCHelpMan estimaterawfee() {RPCResult::Type::NUM, "inmempool", "current number of txs in mempool in the feerate range unconfirmed for at least target blocks"}, {RPCResult::Type::NUM, "leftmempool", "number of txs over history horizon in the feerate range that left mempool unconfirmed after target"}, }}, - {RPCResult::Type::OBJ, "fail", /* optional */ true, "information about the highest range of feerates to fail to meet the threshold", + {RPCResult::Type::OBJ, "fail", /*optional=*/true, "information about the highest range of feerates to fail to meet the threshold", { {RPCResult::Type::ELISION, "", ""}, }}, - {RPCResult::Type::ARR, "errors", /* optional */ true, "Errors encountered during processing (if there are any)", + {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)", { {RPCResult::Type::STR, "error", ""}, }}, }}, - {RPCResult::Type::OBJ, "medium", /* optional */ true, "estimate for medium time horizon", + {RPCResult::Type::OBJ, "medium", /*optional=*/true, "estimate for medium time horizon", { {RPCResult::Type::ELISION, "", ""}, }}, - {RPCResult::Type::OBJ, "long", /* optional */ true, "estimate for long time horizon", + {RPCResult::Type::OBJ, "long", /*optional=*/true, "estimate for long time horizon", { {RPCResult::Type::ELISION, "", ""}, }}, @@ -1260,9 +1278,9 @@ static const CRPCCommand commands[] = { "mining", &submitheader, }, - { "generating", &generatetoaddress, }, - { "generating", &generatetodescriptor, }, - { "generating", &generateblock, }, + { "hidden", &generatetoaddress, }, + { "hidden", &generatetodescriptor, }, + { "hidden", &generateblock, }, { "util", &estimatesmartfee, }, diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 5178ce60e8..8d7b48d697 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -16,14 +16,17 @@ #include <outputtype.h> #include <rpc/blockchain.h> #include <rpc/server.h> +#include <rpc/server_util.h> #include <rpc/util.h> #include <scheduler.h> #include <script/descriptor.h> #include <util/check.h> #include <util/message.h> // For MessageSign(), MessageVerify() #include <util/strencodings.h> +#include <util/syscall_sandbox.h> #include <util/system.h> +#include <optional> #include <stdint.h> #include <tuple> #ifdef HAVE_MALLOC_INFO @@ -32,34 +35,42 @@ #include <univalue.h> +using node::NodeContext; + static RPCHelpMan validateaddress() { - return RPCHelpMan{"validateaddress", - "\nReturn information about the given bitcoin address.\n", - { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to validate"}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", + return RPCHelpMan{ + "validateaddress", + "\nReturn information about the given bitcoin address.\n", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to validate"}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::BOOL, "isvalid", "If the address is valid or not"}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address validated"}, + {RPCResult::Type::STR_HEX, "scriptPubKey", /*optional=*/true, "The hex-encoded scriptPubKey generated by the address"}, + {RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script"}, + {RPCResult::Type::BOOL, "iswitness", /*optional=*/true, "If the address is a witness address"}, + {RPCResult::Type::NUM, "witness_version", /*optional=*/true, "The version number of the witness program"}, + {RPCResult::Type::STR_HEX, "witness_program", /*optional=*/true, "The hex value of the witness program"}, + {RPCResult::Type::STR, "error", /*optional=*/true, "Error message, if any"}, + {RPCResult::Type::ARR, "error_locations", /*optional=*/true, "Indices of likely error locations in address, if known (e.g. Bech32 errors)", { - {RPCResult::Type::BOOL, "isvalid", "If the address is valid or not"}, - {RPCResult::Type::STR, "address", "The bitcoin address validated"}, - {RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded scriptPubKey generated by the address"}, - {RPCResult::Type::BOOL, "isscript", "If the key is a script"}, - {RPCResult::Type::BOOL, "iswitness", "If the address is a witness address"}, - {RPCResult::Type::NUM, "witness_version", /* optional */ true, "The version number of the witness program"}, - {RPCResult::Type::STR_HEX, "witness_program", /* optional */ true, "The hex value of the witness program"}, - {RPCResult::Type::STR, "error", /* optional */ true, "Error message, if any"}, - } - }, - RPCExamples{ - HelpExampleCli("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") + - HelpExampleRpc("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") - }, + {RPCResult::Type::NUM, "index", "index of a potential error"}, + }}, + } + }, + RPCExamples{ + HelpExampleCli("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") + + HelpExampleRpc("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") + }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::string error_msg; - CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg); + std::vector<int> error_locations; + CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg, &error_locations); const bool isValid = IsValidDestination(dest); CHECK_NONFATAL(isValid == error_msg.empty()); @@ -75,6 +86,9 @@ static RPCHelpMan validateaddress() UniValue detail = DescribeAddress(dest); ret.pushKVs(detail); } else { + UniValue error_indices(UniValue::VARR); + for (int i : error_locations) error_indices.push_back(i); + ret.pushKV("error_locations", error_indices); ret.pushKV("error", error_msg); } @@ -102,13 +116,17 @@ static RPCHelpMan createmultisig() {RPCResult::Type::STR, "address", "The value of the new multisig address."}, {RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script."}, {RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"}, + {RPCResult::Type::ARR, "warnings", /* optional */ true, "Any warnings resulting from the creation of this multisig", + { + {RPCResult::Type::STR, "", ""}, + }}, } }, RPCExamples{ "\nCreate a multisig address from 2 public keys\n" + HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") + "\nAs a JSON-RPC call\n" - + HelpExampleRpc("createmultisig", "2, \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") + + HelpExampleRpc("createmultisig", "2, [\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\",\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\"]") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { @@ -128,12 +146,13 @@ static RPCHelpMan createmultisig() // Get the output type OutputType output_type = OutputType::LEGACY; if (!request.params[2].isNull()) { - if (!ParseOutputType(request.params[2].get_str(), output_type)) { + std::optional<OutputType> parsed = ParseOutputType(request.params[2].get_str()); + if (!parsed) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str())); - } - if (output_type == OutputType::BECH32M) { + } else if (parsed.value() == OutputType::BECH32M) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses"); } + output_type = parsed.value(); } // Construct using pay-to-script-hash: @@ -149,6 +168,13 @@ static RPCHelpMan createmultisig() result.pushKV("redeemScript", HexStr(inner)); result.pushKV("descriptor", descriptor->ToString()); + UniValue warnings(UniValue::VARR); + if (!request.params[2].isNull() && OutputTypeFromDestination(dest) != output_type) { + // 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.size()) result.pushKV("warnings", warnings); + return result; }, }; @@ -156,6 +182,8 @@ static RPCHelpMan createmultisig() static RPCHelpMan getdescriptorinfo() { + const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)"; + return RPCHelpMan{"getdescriptorinfo", {"\nAnalyses a descriptor.\n"}, { @@ -173,7 +201,8 @@ static RPCHelpMan getdescriptorinfo() }, RPCExamples{ "Analyse a descriptor\n" + - HelpExampleCli("getdescriptorinfo", "\"wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)\"") + HelpExampleCli("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") + + HelpExampleRpc("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { @@ -199,6 +228,8 @@ static RPCHelpMan getdescriptorinfo() static RPCHelpMan deriveaddresses() { + const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu"; + return RPCHelpMan{"deriveaddresses", {"\nDerives one or more addresses corresponding to an output descriptor.\n" "Examples of output descriptors are:\n" @@ -221,7 +252,8 @@ static RPCHelpMan deriveaddresses() }, RPCExamples{ "First three native segwit receive addresses\n" + - HelpExampleCli("deriveaddresses", "\"wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu\" \"[0,2]\"") + HelpExampleCli("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\" \"[0,2]\"") + + HelpExampleRpc("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\", \"[0,2]\"") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { @@ -256,13 +288,13 @@ static RPCHelpMan deriveaddresses() FlatSigningProvider provider; std::vector<CScript> scripts; if (!desc->Expand(i, key_provider, scripts, provider)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys")); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot derive script without private keys"); } for (const CScript &script : scripts) { CTxDestination dest; if (!ExtractDestination(script, dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Descriptor does not have a corresponding address")); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor does not have a corresponding address"); } addresses.push_back(EncodeDestination(dest)); @@ -282,7 +314,7 @@ static RPCHelpMan deriveaddresses() static RPCHelpMan verifymessage() { return RPCHelpMan{"verifymessage", - "\nVerify a signed message\n", + "Verify a signed message.", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."}, {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."}, @@ -394,7 +426,7 @@ static RPCHelpMan setmocktime() RPCTypeCheck(request.params, {UniValue::VNUM}); const int64_t time{request.params[0].get_int64()}; if (time < 0) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime can not be negative: %s.", time)); + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime cannot be negative: %s.", time)); } SetMockTime(time); auto node_context = util::AnyPtr<NodeContext>(request.context); @@ -409,6 +441,27 @@ static RPCHelpMan setmocktime() }; } +#if defined(USE_SYSCALL_SANDBOX) +static RPCHelpMan invokedisallowedsyscall() +{ + return RPCHelpMan{ + "invokedisallowedsyscall", + "\nInvoke a disallowed syscall to trigger a syscall sandbox violation. Used for testing purposes.\n", + {}, + RPCResult{RPCResult::Type::NONE, "", ""}, + RPCExamples{ + HelpExampleCli("invokedisallowedsyscall", "") + HelpExampleRpc("invokedisallowedsyscall", "")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + if (!Params().IsTestChain()) { + throw std::runtime_error("invokedisallowedsyscall is used for testing only."); + } + TestDisallowedSandboxCall(); + return NullUniValue; + }, + }; +} +#endif // USE_SYSCALL_SANDBOX + static RPCHelpMan mockscheduler() { return RPCHelpMan{"mockscheduler", @@ -520,7 +573,7 @@ static RPCHelpMan getmemoryinfo() #ifdef HAVE_MALLOC_INFO return RPCMallocInfo(); #else - throw JSONRPCError(RPC_INVALID_PARAMETER, "mallocinfo is only available when compiled with glibc 2.10+"); + throw JSONRPCError(RPC_INVALID_PARAMETER, "mallocinfo mode not available"); #endif } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode); @@ -662,8 +715,9 @@ static RPCHelpMan echoipc() RPCExamples{HelpExampleCli("echo", "\"Hello world\"") + HelpExampleRpc("echo", "\"Hello world\"")}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + interfaces::Init& local_init = *EnsureAnyNodeContext(request.context).init; std::unique_ptr<interfaces::Echo> echo; - if (interfaces::Ipc* ipc = Assert(EnsureAnyNodeContext(request.context).init)->ipc()) { + if (interfaces::Ipc* ipc = local_init.ipc()) { // Spawn a new bitcoin-node process and call makeEcho to get a // client pointer to a interfaces::Echo instance running in // that process. This is just for testing. A slightly more @@ -681,7 +735,7 @@ static RPCHelpMan echoipc() // interfaces::Echo object and return it so the `echoipc` RPC // method will work, and the python test calling `echoipc` // can expect the same result. - echo = interfaces::MakeEcho(); + echo = local_init.makeEcho(); } return echo->echo(request.params[0].get_str()); }, @@ -708,7 +762,7 @@ static RPCHelpMan getindexinfo() {"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Filter results for an index with a specific name."}, }, RPCResult{ - RPCResult::Type::OBJ, "", "", { + RPCResult::Type::OBJ_DYN, "", "", { { RPCResult::Type::OBJ, "name", "The name of the index", { @@ -768,6 +822,9 @@ static const CRPCCommand commands[] = { "hidden", &echo, }, { "hidden", &echojson, }, { "hidden", &echoipc, }, +#if defined(USE_SYSCALL_SANDBOX) + { "hidden", &invokedisallowedsyscall, }, +#endif // USE_SYSCALL_SANDBOX }; // clang-format on for (const auto& c : commands) { diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index dba0f971b2..1bde4fccbb 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -1,14 +1,14 @@ -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2021 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/server.h> +#include <addrman.h> #include <banman.h> #include <chainparams.h> #include <clientversion.h> #include <core_io.h> -#include <net.h> #include <net_permissions.h> #include <net_processing.h> #include <net_types.h> // For banmap_t @@ -17,12 +17,12 @@ #include <policy/settings.h> #include <rpc/blockchain.h> #include <rpc/protocol.h> +#include <rpc/server_util.h> #include <rpc/util.h> #include <sync.h> #include <timedata.h> #include <util/strencodings.h> #include <util/string.h> -#include <util/system.h> #include <util/translation.h> #include <validation.h> #include <version.h> @@ -32,6 +32,8 @@ #include <univalue.h> +using node::NodeContext; + const std::vector<std::string> CONNECTION_TYPE_DOC{ "outbound-full-relay (default automatic connections)", "block-relay-only (does not relay transactions or addresses)", @@ -41,22 +43,6 @@ const std::vector<std::string> CONNECTION_TYPE_DOC{ "feeler (short-lived automatic connection for testing addresses)" }; -CConnman& EnsureConnman(const NodeContext& node) -{ - if (!node.connman) { - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - } - return *node.connman; -} - -PeerManager& EnsurePeerman(const NodeContext& node) -{ - if (!node.peerman) { - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - } - return *node.peerman; -} - static RPCHelpMan getconnectioncount() { return RPCHelpMan{"getconnectioncount", @@ -105,79 +91,83 @@ static RPCHelpMan ping() static RPCHelpMan getpeerinfo() { - return RPCHelpMan{"getpeerinfo", - "\nReturns data about each connected network node as a json array of objects.\n", - {}, - RPCResult{ - RPCResult::Type::ARR, "", "", + return RPCHelpMan{ + "getpeerinfo", + "\nReturns data about each connected network node as a json array of objects.\n", + {}, + RPCResult{ + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::OBJ, "", "", + { { - {RPCResult::Type::OBJ, "", "", - { - { - {RPCResult::Type::NUM, "id", "Peer index"}, - {RPCResult::Type::STR, "addr", "(host:port) The IP address and port of the peer"}, - {RPCResult::Type::STR, "addrbind", "(ip:port) Bind address of the connection to the peer"}, - {RPCResult::Type::STR, "addrlocal", "(ip:port) Local address as reported by the peer"}, - {RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/* append_unroutable */ true), ", ") + ")"}, - {RPCResult::Type::NUM, "mapped_as", "The AS in the BGP route to the peer used for diversifying\n" - "peer selection (only available if the asmap config flag is set)"}, - {RPCResult::Type::STR_HEX, "services", "The services offered"}, - {RPCResult::Type::ARR, "servicesnames", "the services offered, in human-readable form", - { - {RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"} - }}, - {RPCResult::Type::BOOL, "relaytxes", "Whether peer has asked us to relay transactions to it"}, - {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"}, - {RPCResult::Type::NUM_TIME, "last_block", "The " + UNIX_EPOCH_TIME + " of the last block received from this peer"}, - {RPCResult::Type::NUM, "bytessent", "The total bytes sent"}, - {RPCResult::Type::NUM, "bytesrecv", "The total bytes received"}, - {RPCResult::Type::NUM_TIME, "conntime", "The " + UNIX_EPOCH_TIME + " of the connection"}, - {RPCResult::Type::NUM, "timeoffset", "The time offset in seconds"}, - {RPCResult::Type::NUM, "pingtime", "ping time (if available)"}, - {RPCResult::Type::NUM, "minping", "minimum observed ping time (if any at all)"}, - {RPCResult::Type::NUM, "pingwait", "ping wait (if non-zero)"}, - {RPCResult::Type::NUM, "version", "The peer version, such as 70001"}, - {RPCResult::Type::STR, "subver", "The string version"}, - {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", "The starting height (block) of the peer"}, - {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::ARR, "permissions", "Any special permissions that have been granted to this peer", - { - {RPCResult::Type::STR, "permission_type", Join(NET_PERMISSIONS_DOC, ",\n") + ".\n"}, - }}, - {RPCResult::Type::NUM, "minfeefilter", "The minimum fee rate for transactions this peer accepts"}, - {RPCResult::Type::OBJ_DYN, "bytessent_per_msg", "", - { - {RPCResult::Type::NUM, "msg", "The total bytes sent aggregated by message type\n" - "When a message type is not listed in this json object, the bytes sent are 0.\n" - "Only known message types can appear as keys in the object."} - }}, - {RPCResult::Type::OBJ_DYN, "bytesrecv_per_msg", "", - { - {RPCResult::Type::NUM, "msg", "The total bytes received aggregated by message type\n" - "When a message type is not listed in this json object, the bytes received are 0.\n" - "Only known message types can appear as keys in the object and all bytes received\n" - "of unknown message types are listed under '"+NET_MESSAGE_COMMAND_OTHER+"'."} - }}, - {RPCResult::Type::STR, "connection_type", "Type of connection: \n" + Join(CONNECTION_TYPE_DOC, ",\n") + ".\n" - "Please note this output is unlikely to be stable in upcoming releases as we iterate to\n" - "best capture connection behaviors."}, - }}, + {RPCResult::Type::NUM, "id", "Peer index"}, + {RPCResult::Type::STR, "addr", "(host:port) The IP address and port of the peer"}, + {RPCResult::Type::STR, "addrbind", /*optional=*/true, "(ip:port) Bind address of the connection to the peer"}, + {RPCResult::Type::STR, "addrlocal", /*optional=*/true, "(ip:port) Local address as reported by the peer"}, + {RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/* append_unroutable */ true), ", ") + ")"}, + {RPCResult::Type::NUM, "mapped_as", /*optional=*/true, "The AS in the BGP route to the peer used for diversifying\n" + "peer selection (only available if the asmap config flag is set)"}, + {RPCResult::Type::STR_HEX, "services", "The services offered"}, + {RPCResult::Type::ARR, "servicesnames", "the services offered, in human-readable form", + { + {RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"} }}, - }, - RPCExamples{ - HelpExampleCli("getpeerinfo", "") + {RPCResult::Type::BOOL, "relaytxes", "Whether peer has asked us to relay transactions to it"}, + {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"}, + {RPCResult::Type::NUM_TIME, "last_block", "The " + UNIX_EPOCH_TIME + " of the last block received from this peer"}, + {RPCResult::Type::NUM, "bytessent", "The total bytes sent"}, + {RPCResult::Type::NUM, "bytesrecv", "The total bytes received"}, + {RPCResult::Type::NUM_TIME, "conntime", "The " + UNIX_EPOCH_TIME + " of the connection"}, + {RPCResult::Type::NUM, "timeoffset", "The time offset in seconds"}, + {RPCResult::Type::NUM, "pingtime", /*optional=*/true, "ping time (if available)"}, + {RPCResult::Type::NUM, "minping", /*optional=*/true, "minimum observed ping time (if any at all)"}, + {RPCResult::Type::NUM, "pingwait", /*optional=*/true, "ping wait (if non-zero)"}, + {RPCResult::Type::NUM, "version", "The peer version, such as 70001"}, + {RPCResult::Type::STR, "subver", "The string version"}, + {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, "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, "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::ARR, "permissions", "Any special permissions that have been granted to this peer", + { + {RPCResult::Type::STR, "permission_type", Join(NET_PERMISSIONS_DOC, ",\n") + ".\n"}, + }}, + {RPCResult::Type::NUM, "minfeefilter", "The minimum fee rate for transactions this peer accepts"}, + {RPCResult::Type::OBJ_DYN, "bytessent_per_msg", "", + { + {RPCResult::Type::NUM, "msg", "The total bytes sent aggregated by message type\n" + "When a message type is not listed in this json object, the bytes sent are 0.\n" + "Only known message types can appear as keys in the object."} + }}, + {RPCResult::Type::OBJ_DYN, "bytesrecv_per_msg", "", + { + {RPCResult::Type::NUM, "msg", "The total bytes received aggregated by message type\n" + "When a message type is not listed in this json object, the bytes received are 0.\n" + "Only known message types can appear as keys in the object and all bytes received\n" + "of unknown message types are listed under '"+NET_MESSAGE_COMMAND_OTHER+"'."} + }}, + {RPCResult::Type::STR, "connection_type", "Type of connection: \n" + Join(CONNECTION_TYPE_DOC, ",\n") + ".\n" + "Please note this output is unlikely to be stable in upcoming releases as we iterate to\n" + "best capture connection behaviors."}, + }}, + }}, + }, + RPCExamples{ + HelpExampleCli("getpeerinfo", "") + HelpExampleRpc("getpeerinfo", "") - }, + }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { NodeContext& node = EnsureAnyNodeContext(request.context); @@ -194,7 +184,7 @@ static RPCHelpMan getpeerinfo() CNodeStateStats statestats; bool fStateStats = peerman.GetNodeStateStats(stats.nodeid, statestats); obj.pushKV("id", stats.nodeid); - obj.pushKV("addr", stats.addrName); + obj.pushKV("addr", stats.m_addr_name); if (stats.addrBind.IsValid()) { obj.pushKV("addrbind", stats.addrBind.ToString()); } @@ -208,13 +198,13 @@ static RPCHelpMan getpeerinfo() obj.pushKV("services", strprintf("%016x", stats.nServices)); obj.pushKV("servicesnames", GetServicesNames(stats.nServices)); obj.pushKV("relaytxes", stats.fRelayTxes); - obj.pushKV("lastsend", stats.nLastSend); - obj.pushKV("lastrecv", stats.nLastRecv); - obj.pushKV("last_transaction", stats.nLastTXTime); - obj.pushKV("last_block", stats.nLastBlockTime); + 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)); + obj.pushKV("last_block", count_seconds(stats.m_last_block_time)); obj.pushKV("bytessent", stats.nSendBytes); obj.pushKV("bytesrecv", stats.nRecvBytes); - obj.pushKV("conntime", stats.nTimeConnected); + obj.pushKV("conntime", count_seconds(stats.m_connected)); obj.pushKV("timeoffset", stats.nTimeOffset); if (stats.m_last_ping_time > 0us) { obj.pushKV("pingtime", CountSecondsDouble(stats.m_last_ping_time)); @@ -242,6 +232,7 @@ static RPCHelpMan getpeerinfo() 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); } @@ -339,7 +330,7 @@ static RPCHelpMan addconnection() "\nOpen an outbound connection to a specified node. This RPC is for testing only.\n", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The IP address and port to attempt connecting to."}, - {"connection_type", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of connection to open (\"outbound-full-relay\", \"block-relay-only\" or \"addr-fetch\")."}, + {"connection_type", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of connection to open (\"outbound-full-relay\", \"block-relay-only\", \"addr-fetch\" or \"feeler\")."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -367,6 +358,8 @@ static RPCHelpMan addconnection() conn_type = ConnectionType::BLOCK_RELAY; } else if (conn_type_in == "addr-fetch") { conn_type = ConnectionType::ADDR_FETCH; + } else if (conn_type_in == "feeler") { + conn_type = ConnectionType::FEELER; } else { throw JSONRPCError(RPC_INVALID_PARAMETER, self.ToString()); } @@ -562,8 +555,8 @@ static UniValue GetNetworksInfo() UniValue networks(UniValue::VARR); for (int n = 0; n < NET_MAX; ++n) { enum Network network = static_cast<enum Network>(n); - if (network == NET_UNROUTABLE || network == NET_CJDNS || network == NET_INTERNAL) continue; - proxyType proxy; + if (network == NET_UNROUTABLE || network == NET_INTERNAL) continue; + Proxy proxy; UniValue obj(UniValue::VOBJ); GetProxy(network, proxy); obj.pushKV("name", GetNetworkName(network)); @@ -655,7 +648,7 @@ static RPCHelpMan getnetworkinfo() obj.pushKV("incrementalfee", ValueFromAmount(::incrementalRelayFee.GetFeePerK())); UniValue localAddresses(UniValue::VARR); { - LOCK(cs_mapLocalHost); + LOCK(g_maplocalhost_mutex); for (const std::pair<const CNetAddr, LocalServiceInfo> &item : mapLocalHost) { UniValue rec(UniValue::VOBJ); @@ -854,7 +847,9 @@ static RPCHelpMan setnetworkactive() static RPCHelpMan getnodeaddresses() { return RPCHelpMan{"getnodeaddresses", - "\nReturn known addresses, which can potentially be used to find new nodes in the network.\n", + "Return known addresses, after filtering for quality and recency.\n" + "These can potentially be used to find new peers in the network.\n" + "The total number of addresses known to the node may be higher.", { {"count", RPCArg::Type::NUM, RPCArg::Default{1}, "The maximum number of addresses to return. Specify 0 to return all known addresses."}, {"network", RPCArg::Type::STR, RPCArg::DefaultHint{"all networks"}, "Return only addresses of the specified network. Can be one of: " + Join(GetNetworkNames(), ", ") + "."}, @@ -917,6 +912,7 @@ static RPCHelpMan addpeeraddress() { {"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"}, + {"tried", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, attempt to add the peer to the tried addresses table"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -925,8 +921,8 @@ static RPCHelpMan addpeeraddress() }, }, RPCExamples{ - HelpExampleCli("addpeeraddress", "\"1.2.3.4\" 8333") - + HelpExampleRpc("addpeeraddress", "\"1.2.3.4\", 8333") + HelpExampleCli("addpeeraddress", "\"1.2.3.4\" 8333 true") + + HelpExampleRpc("addpeeraddress", "\"1.2.3.4\", 8333, true") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { @@ -937,6 +933,7 @@ static RPCHelpMan addpeeraddress() const std::string& addr_string{request.params[0].get_str()}; const uint16_t port{static_cast<uint16_t>(request.params[1].get_int())}; + const bool tried{request.params[2].isTrue()}; UniValue obj(UniValue::VOBJ); CNetAddr net_addr; @@ -947,7 +944,13 @@ static RPCHelpMan addpeeraddress() address.nTime = GetAdjustedTime(); // The source address is set equal to the address. This is equivalent to the peer // announcing itself. - if (node.addrman->Add(address, address)) success = true; + if (node.addrman->Add({address}, address)) { + success = true; + if (tried) { + // Attempt to move the address to the tried addresses table. + node.addrman->Good(address); + } + } } obj.pushKV("success", success); diff --git a/src/rpc/net.h b/src/rpc/net.h deleted file mode 100644 index 7a4d8440ba..0000000000 --- a/src/rpc/net.h +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2021 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_RPC_NET_H -#define BITCOIN_RPC_NET_H - -class CConnman; -class PeerManager; -struct NodeContext; - -CConnman& EnsureConnman(const NodeContext& node); -PeerManager& EnsurePeerman(const NodeContext& node); - -#endif // BITCOIN_RPC_NET_H diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index fc00a1efad..75e42e4c88 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2019 The Bitcoin Core developers +// Copyright (c) 2009-2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -80,6 +80,7 @@ enum RPCErrorCode RPC_WALLET_NOT_FOUND = -18, //!< Invalid wallet specified RPC_WALLET_NOT_SPECIFIED = -19, //!< No wallet specified (error when there are multiple wallets loaded) RPC_WALLET_ALREADY_LOADED = -35, //!< This same wallet is already loaded + RPC_WALLET_ALREADY_EXISTS = -36, //!< There is already a wallet with the same name //! Backwards compatible aliases RPC_WALLET_INVALID_ACCOUNT_NAME = RPC_WALLET_INVALID_LABEL_NAME, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 617dfec98f..d2f817d56e 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1,10 +1,12 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2021 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 <base58.h> #include <chain.h> #include <coins.h> +#include <consensus/amount.h> #include <consensus/validation.h> #include <core_io.h> #include <index/txindex.h> @@ -24,6 +26,7 @@ #include <rpc/blockchain.h> #include <rpc/rawtransaction_util.h> #include <rpc/server.h> +#include <rpc/server_util.h> #include <rpc/util.h> #include <script/script.h> #include <script/sign.h> @@ -42,6 +45,15 @@ #include <univalue.h> +using node::AnalyzePSBT; +using node::BroadcastTransaction; +using node::DEFAULT_MAX_RAW_TX_FEE_RATE; +using node::FindCoins; +using node::GetTransaction; +using node::NodeContext; +using node::PSBTAnalysis; +using node::ReadBlockFromDisk; + static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, CChainState& active_chainstate) { // Call into TxToUniv() in bitcoin-common to decode the transaction hex. @@ -68,18 +80,53 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& } } +static std::vector<RPCArg> CreateTxDoc() +{ + return { + {"inputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The inputs", + { + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", + { + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, + {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, + {"sequence", RPCArg::Type::NUM, RPCArg::DefaultHint{"depends on the value of the 'replaceable' and 'locktime' arguments"}, "The sequence number"}, + }, + }, + }, + }, + {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The outputs (key-value pairs), where none of the keys are duplicated.\n" + "That is, each address can only appear once and there can only be one 'data' object.\n" + "For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" + " accepted as second parameter.", + { + {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "", + { + {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT}, + }, + }, + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", + { + {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, + }, + }, + }, + }, + {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"}, + {"replaceable", RPCArg::Type::BOOL, RPCArg::Default{false}, "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."}, + }; +} + static RPCHelpMan getrawtransaction() { return RPCHelpMan{ "getrawtransaction", "\nReturn the raw transaction data.\n" - "\nBy default this function only works for mempool transactions. When called with a blockhash\n" - "argument, getrawtransaction will return the transaction if the specified block is available and\n" - "the transaction is found in that block. When called without a blockhash argument, getrawtransaction\n" - "will return the transaction if it is in the mempool, or if -txindex is enabled and the transaction\n" - "is in a block in the blockchain.\n" - + "\nBy 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" "\nIf verbose is 'true', returns an Object with information about 'txid'.\n" @@ -96,7 +143,7 @@ static RPCHelpMan getrawtransaction() RPCResult{"if verbose is set to true", RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::BOOL, "in_active_chain", "Whether specified block is in the active chain or not (only present with explicit \"blockhash\" argument)"}, + {RPCResult::Type::BOOL, "in_active_chain", /*optional=*/true, "Whether specified block is in the active chain or not (only present with explicit \"blockhash\" argument)"}, {RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for 'txid'"}, {RPCResult::Type::STR_HEX, "txid", "The transaction id (same as provided)"}, {RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)"}, @@ -117,7 +164,7 @@ static RPCHelpMan getrawtransaction() {RPCResult::Type::STR_HEX, "hex", "hex"}, }}, {RPCResult::Type::NUM, "sequence", "The script sequence number"}, - {RPCResult::Type::ARR, "txinwitness", "", + {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "", { {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"}, }}, @@ -132,21 +179,17 @@ static RPCHelpMan getrawtransaction() {RPCResult::Type::OBJ, "scriptPubKey", "", { {RPCResult::Type::STR, "asm", "the asm"}, + {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"}, {RPCResult::Type::STR, "hex", "the hex"}, - {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"}, {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, - {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"}, - {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses", - { - {RPCResult::Type::STR, "address", "bitcoin address"}, - }}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, }}, }}, }}, - {RPCResult::Type::STR_HEX, "blockhash", "the block hash"}, - {RPCResult::Type::NUM, "confirmations", "The confirmations"}, - {RPCResult::Type::NUM_TIME, "blocktime", "The block time expressed in " + UNIX_EPOCH_TIME}, - {RPCResult::Type::NUM, "time", "Same as \"blocktime\""}, + {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "the block hash"}, + {RPCResult::Type::NUM, "confirmations", /*optional=*/true, "The confirmations"}, + {RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME}, + {RPCResult::Type::NUM, "time", /*optional=*/true, "Same as \"blocktime\""}, } }, }, @@ -198,7 +241,8 @@ static RPCHelpMan getrawtransaction() if (!tx) { std::string errmsg; if (blockindex) { - if (!(blockindex->nStatus & BLOCK_HAVE_DATA)) { + const bool block_has_data = WITH_LOCK(::cs_main, return blockindex->nStatus & BLOCK_HAVE_DATA); + if (!block_has_data) { throw JSONRPCError(RPC_MISC_ERROR, "Block not available"); } errmsg = "No such transaction found in the provided block"; @@ -336,7 +380,7 @@ static RPCHelpMan verifytxoutproof() RPCResult{ RPCResult::Type::ARR, "", "", { - {RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof can not be validated."}, + {RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."}, } }, RPCExamples{""}, @@ -381,39 +425,7 @@ static RPCHelpMan createrawtransaction() "Returns hex-encoded raw transaction.\n" "Note that the transaction's inputs are not signed, and\n" "it is not stored in the wallet or transmitted to the network.\n", - { - {"inputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The inputs", - { - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", - { - {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, - {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, - {"sequence", RPCArg::Type::NUM, RPCArg::DefaultHint{"depends on the value of the 'replaceable' and 'locktime' arguments"}, "The sequence number"}, - }, - }, - }, - }, - {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The outputs (key-value pairs), where none of the keys are duplicated.\n" - "That is, each address can only appear once and there can only be one 'data' object.\n" - "For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" - " accepted as second parameter.", - { - {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "", - { - {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT}, - }, - }, - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", - { - {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, - }, - }, - }, - }, - {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"}, - {"replaceable", RPCArg::Type::BOOL, RPCArg::Default{false}, "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."}, - }, + CreateTxDoc(), RPCResult{ RPCResult::Type::STR_HEX, "transaction", "hex string of the transaction" }, @@ -472,14 +484,15 @@ static RPCHelpMan decoderawtransaction() { {RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::STR_HEX, "txid", "The transaction id"}, - {RPCResult::Type::NUM, "vout", "The output number"}, - {RPCResult::Type::OBJ, "scriptSig", "The script", + {RPCResult::Type::STR_HEX, "coinbase", /*optional=*/true, ""}, + {RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id"}, + {RPCResult::Type::NUM, "vout", /*optional=*/true, "The output number"}, + {RPCResult::Type::OBJ, "scriptSig", /*optional=*/true, "The script", { {RPCResult::Type::STR, "asm", "asm"}, {RPCResult::Type::STR_HEX, "hex", "hex"}, }}, - {RPCResult::Type::ARR, "txinwitness", "", + {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "", { {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"}, }}, @@ -495,14 +508,10 @@ static RPCHelpMan decoderawtransaction() {RPCResult::Type::OBJ, "scriptPubKey", "", { {RPCResult::Type::STR, "asm", "the asm"}, + {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"}, {RPCResult::Type::STR_HEX, "hex", "the hex"}, - {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"}, {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, - {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"}, - {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses", - { - {RPCResult::Type::STR, "address", "bitcoin address"}, - }}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, }}, }}, }}, @@ -533,54 +542,39 @@ static RPCHelpMan decoderawtransaction() }; } -static std::string GetAllOutputTypes() -{ - std::vector<std::string> ret; - using U = std::underlying_type<TxoutType>::type; - for (U i = (U)TxoutType::NONSTANDARD; i <= (U)TxoutType::WITNESS_UNKNOWN; ++i) { - ret.emplace_back(GetTxnOutputType(static_cast<TxoutType>(i))); - } - return Join(ret, ", "); -} - static RPCHelpMan decodescript() { - return RPCHelpMan{"decodescript", - "\nDecode a hex-encoded script.\n", - { - {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded script"}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR, "asm", "Script public key"}, - {RPCResult::Type::STR, "type", "The output type (e.g. "+GetAllOutputTypes()+")"}, - {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"}, - {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"}, - {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses", - { - {RPCResult::Type::STR, "address", "bitcoin address"}, - }}, - {RPCResult::Type::STR, "p2sh", "address of P2SH script wrapping this redeem script (not returned if the script is already a P2SH)"}, - {RPCResult::Type::OBJ, "segwit", "Result of a witness script public key wrapping this redeem script (not returned if the script is a P2SH or witness)", - { - {RPCResult::Type::STR, "asm", "String representation of the script public key"}, - {RPCResult::Type::STR_HEX, "hex", "Hex string of the script public key"}, - {RPCResult::Type::STR, "type", "The type of the script public key (e.g. witness_v0_keyhash or witness_v0_scripthash)"}, - {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"}, - {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"}, - {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses", - { - {RPCResult::Type::STR, "address", "segwit address"}, - }}, - {RPCResult::Type::STR, "p2sh-segwit", "address of the P2SH script wrapping this witness redeem script"}, - }}, - } - }, - RPCExamples{ - HelpExampleCli("decodescript", "\"hexstring\"") - + HelpExampleRpc("decodescript", "\"hexstring\"") - }, + return RPCHelpMan{ + "decodescript", + "\nDecode a hex-encoded script.\n", + { + {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded script"}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "asm", "Script public key"}, + {RPCResult::Type::STR, "desc", "Inferred descriptor for the script"}, + {RPCResult::Type::STR, "type", "The output type (e.g. " + GetAllOutputTypes() + ")"}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, + {RPCResult::Type::STR, "p2sh", /*optional=*/true, + "address of P2SH script wrapping this redeem script (not returned for types that should not be wrapped)"}, + {RPCResult::Type::OBJ, "segwit", /*optional=*/true, + "Result of a witness script public key wrapping this redeem script (not returned for types that should not be wrapped)", + { + {RPCResult::Type::STR, "asm", "String representation of the script public key"}, + {RPCResult::Type::STR_HEX, "hex", "Hex string of the script public key"}, + {RPCResult::Type::STR, "type", "The type of the script public key (e.g. witness_v0_keyhash or witness_v0_scripthash)"}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, + {RPCResult::Type::STR, "desc", "Inferred descriptor for the script"}, + {RPCResult::Type::STR, "p2sh-segwit", "address of the P2SH script wrapping this witness redeem script"}, + }}, + }, + }, + RPCExamples{ + HelpExampleCli("decodescript", "\"hexstring\"") + + HelpExampleRpc("decodescript", "\"hexstring\"") + }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { RPCTypeCheck(request.params, {UniValue::VSTR}); @@ -593,29 +587,73 @@ static RPCHelpMan decodescript() } else { // Empty scripts are valid } - ScriptPubKeyToUniv(script, r, /* fIncludeHex */ false); - - UniValue type; - type = find_value(r, "type"); + ScriptPubKeyToUniv(script, r, /* include_hex */ false); + + std::vector<std::vector<unsigned char>> solutions_data; + const TxoutType which_type{Solver(script, solutions_data)}; + + const bool can_wrap{[&] { + switch (which_type) { + case TxoutType::MULTISIG: + case TxoutType::NONSTANDARD: + case TxoutType::PUBKEY: + case TxoutType::PUBKEYHASH: + case TxoutType::WITNESS_V0_KEYHASH: + case TxoutType::WITNESS_V0_SCRIPTHASH: + // Can be wrapped if the checks below pass + break; + case TxoutType::NULL_DATA: + case TxoutType::SCRIPTHASH: + case TxoutType::WITNESS_UNKNOWN: + case TxoutType::WITNESS_V1_TAPROOT: + // Should not be wrapped + return false; + } // no default case, so the compiler can warn about missing cases + if (!script.HasValidOps() || script.IsUnspendable()) { + return false; + } + for (CScript::const_iterator it{script.begin()}; it != script.end();) { + opcodetype op; + CHECK_NONFATAL(script.GetOp(it, op)); + if (op == OP_CHECKSIGADD || IsOpSuccess(op)) { + return false; + } + } + return true; + }()}; - if (type.isStr() && type.get_str() != "scripthash") { - // P2SH cannot be wrapped in a P2SH. If this script is already a P2SH, - // don't return the address for a P2SH of the P2SH. + if (can_wrap) { r.pushKV("p2sh", EncodeDestination(ScriptHash(script))); // P2SH and witness programs cannot be wrapped in P2WSH, if this script // is a witness program, don't return addresses for a segwit programs. - if (type.get_str() == "pubkey" || type.get_str() == "pubkeyhash" || type.get_str() == "multisig" || type.get_str() == "nonstandard") { - std::vector<std::vector<unsigned char>> solutions_data; - TxoutType which_type = Solver(script, solutions_data); + const bool can_wrap_P2WSH{[&] { + switch (which_type) { + case TxoutType::MULTISIG: + case TxoutType::PUBKEY: // Uncompressed pubkeys cannot be used with segwit checksigs. // If the script contains an uncompressed pubkey, skip encoding of a segwit program. - if ((which_type == TxoutType::PUBKEY) || (which_type == TxoutType::MULTISIG)) { for (const auto& solution : solutions_data) { if ((solution.size() != 1) && !CPubKey(solution).IsCompressed()) { - return r; + return false; } } - } + return true; + case TxoutType::NONSTANDARD: + case TxoutType::PUBKEYHASH: + // Can be P2WSH wrapped + return true; + case TxoutType::NULL_DATA: + case TxoutType::SCRIPTHASH: + case TxoutType::WITNESS_UNKNOWN: + case TxoutType::WITNESS_V0_KEYHASH: + case TxoutType::WITNESS_V0_SCRIPTHASH: + case TxoutType::WITNESS_V1_TAPROOT: + // Should not be wrapped + return false; + } // no default case, so the compiler can warn about missing cases + CHECK_NONFATAL(false); + }()}; + if (can_wrap_P2WSH) { UniValue sr(UniValue::VOBJ); CScript segwitScr; if (which_type == TxoutType::PUBKEY) { @@ -624,10 +662,9 @@ static RPCHelpMan decodescript() segwitScr = GetScriptForDestination(WitnessV0KeyHash(uint160{solutions_data[0]})); } else { // Scripts that are not fit for P2WPKH are encoded as P2WSH. - // Newer segwit program versions should be considered when then become available. segwitScr = GetScriptForDestination(WitnessV0ScriptHash(script)); } - ScriptPubKeyToUniv(segwitScr, sr, /* fIncludeHex */ true); + ScriptPubKeyToUniv(segwitScr, sr, /* include_hex */ true); sr.pushKV("p2sh-segwit", EncodeDestination(ScriptHash(segwitScr))); r.pushKV("segwit", sr); } @@ -753,7 +790,7 @@ static RPCHelpMan signrawtransactionwithkey() }, }, }, - {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT"}, "The signature hash type. Must be one of:\n" + {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT for Taproot, ALL otherwise"}, "The signature hash type. Must be one of:\n" " \"DEFAULT\"\n" " \"ALL\"\n" " \"NONE\"\n" @@ -768,12 +805,16 @@ static RPCHelpMan signrawtransactionwithkey() { {RPCResult::Type::STR_HEX, "hex", "The hex-encoded raw transaction with signature(s)"}, {RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"}, - {RPCResult::Type::ARR, "errors", /* optional */ true, "Script verification errors (if there are any)", + {RPCResult::Type::ARR, "errors", /*optional=*/true, "Script verification errors (if there are any)", { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "txid", "The hash of the referenced, previous transaction"}, {RPCResult::Type::NUM, "vout", "The index of the output to spent and used as input"}, + {RPCResult::Type::ARR, "witness", "", + { + {RPCResult::Type::STR_HEX, "witness", ""}, + }}, {RPCResult::Type::STR_HEX, "scriptSig", "The hex-encoded signature script"}, {RPCResult::Type::NUM, "sequence", "Script sequence number"}, {RPCResult::Type::STR, "error", "Verification or signing error related to the input"}, @@ -905,21 +946,21 @@ static RPCHelpMan testmempoolaccept() RPCResult{ RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n" "Returns results for each transaction in the same order they were passed in.\n" - "It is possible for transactions to not be fully validated ('allowed' unset) if another transaction failed.\n", + "Transactions that cannot be fully validated due to failures in other transactions will not contain an 'allowed' result.\n", { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"}, {RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"}, - {RPCResult::Type::STR, "package-error", "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."}, - {RPCResult::Type::BOOL, "allowed", "Whether this tx would be accepted to the mempool and pass client-specified maxfeerate." + {RPCResult::Type::STR, "package-error", /*optional=*/true, "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."}, + {RPCResult::Type::BOOL, "allowed", /*optional=*/true, "Whether this tx would be accepted to the mempool and pass client-specified maxfeerate. " "If not present, the tx was not fully validated due to a failure in another tx in the list."}, - {RPCResult::Type::NUM, "vsize", "Virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted (only present when 'allowed' is true)"}, - {RPCResult::Type::OBJ, "fees", "Transaction fees (only present if 'allowed' is true)", + {RPCResult::Type::NUM, "vsize", /*optional=*/true, "Virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted (only present when 'allowed' is true)"}, + {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, "reject-reason", "Rejection string (only present when 'allowed' is false)"}, + {RPCResult::Type::STR, "reject-reason", /*optional=*/true, "Rejection string (only present when 'allowed' is false)"}, }}, } }, @@ -962,12 +1003,13 @@ static RPCHelpMan testmempoolaccept() NodeContext& node = EnsureAnyNodeContext(request.context); CTxMemPool& mempool = EnsureMemPool(node); - CChainState& chainstate = EnsureChainman(node).ActiveChainstate(); + ChainstateManager& chainman = EnsureChainman(node); + CChainState& chainstate = chainman.ActiveChainstate(); const PackageMempoolAcceptResult package_result = [&] { LOCK(::cs_main); if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /* test_accept */ true); return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(), - AcceptToMemoryPool(chainstate, mempool, txns[0], /* bypass_limits */ false, /* test_accept*/ true)); + chainman.ProcessTransaction(txns[0], /*test_accept=*/ true)); }(); UniValue rpc_result(UniValue::VARR); @@ -990,10 +1032,12 @@ static RPCHelpMan testmempoolaccept() continue; } const auto& tx_result = it->second; + // Package testmempoolaccept doesn't allow transactions to already be in the mempool. + CHECK_NONFATAL(tx_result.m_result_type != MempoolAcceptResult::ResultType::MEMPOOL_ENTRY); if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) { const CAmount fee = tx_result.m_base_fees.value(); // Check that fee does not exceed maximum fee - const int64_t virtual_size = GetVirtualTransactionSize(*tx); + const int64_t virtual_size = tx_result.m_vsize.value(); const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); if (max_raw_tx_fee && fee > max_raw_tx_fee) { result_inner.pushKV("allowed", false); @@ -1026,8 +1070,9 @@ static RPCHelpMan testmempoolaccept() static RPCHelpMan decodepsbt() { - return RPCHelpMan{"decodepsbt", - "\nReturn a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.\n", + return RPCHelpMan{ + "decodepsbt", + "Return a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.", { {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The PSBT base64 string"}, }, @@ -1038,6 +1083,26 @@ static RPCHelpMan decodepsbt() { {RPCResult::Type::ELISION, "", "The layout is the same as the output of decoderawtransaction."}, }}, + {RPCResult::Type::ARR, "global_xpubs", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "xpub", "The extended public key this path corresponds to"}, + {RPCResult::Type::STR_HEX, "master_fingerprint", "The fingerprint of the master key"}, + {RPCResult::Type::STR, "path", "The path"}, + }}, + }}, + {RPCResult::Type::NUM, "psbt_version", "The PSBT version number. Not to be confused with the unsigned transaction version"}, + {RPCResult::Type::ARR, "proprietary", "The global proprietary map", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"}, + {RPCResult::Type::NUM, "subtype", "The number for the subtype"}, + {RPCResult::Type::STR_HEX, "key", "The hex for the key"}, + {RPCResult::Type::STR_HEX, "value", "The hex for the value"}, + }}, + }}, {RPCResult::Type::OBJ_DYN, "unknown", "The unknown global fields", { {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, @@ -1046,78 +1111,106 @@ static RPCHelpMan decodepsbt() { {RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::OBJ, "non_witness_utxo", /* optional */ true, "Decoded network transaction for non-witness UTXOs", + {RPCResult::Type::OBJ, "non_witness_utxo", /*optional=*/true, "Decoded network transaction for non-witness UTXOs", { {RPCResult::Type::ELISION, "",""}, }}, - {RPCResult::Type::OBJ, "witness_utxo", /* optional */ true, "Transaction output for witness UTXOs", + {RPCResult::Type::OBJ, "witness_utxo", /*optional=*/true, "Transaction output for witness UTXOs", { {RPCResult::Type::NUM, "amount", "The value in " + CURRENCY_UNIT}, {RPCResult::Type::OBJ, "scriptPubKey", "", { {RPCResult::Type::STR, "asm", "The asm"}, + {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"}, {RPCResult::Type::STR_HEX, "hex", "The hex"}, {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, - {RPCResult::Type::STR, "address"," Bitcoin address if there is one"}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, }}, }}, - {RPCResult::Type::OBJ_DYN, "partial_signatures", /* optional */ true, "", + {RPCResult::Type::OBJ_DYN, "partial_signatures", /*optional=*/true, "", { {RPCResult::Type::STR, "pubkey", "The public key and signature that corresponds to it."}, }}, - {RPCResult::Type::STR, "sighash", /* optional */ true, "The sighash type to be used"}, - {RPCResult::Type::OBJ, "redeem_script", /* optional */ true, "", + {RPCResult::Type::STR, "sighash", /*optional=*/true, "The sighash type to be used"}, + {RPCResult::Type::OBJ, "redeem_script", /*optional=*/true, "", { {RPCResult::Type::STR, "asm", "The asm"}, {RPCResult::Type::STR_HEX, "hex", "The hex"}, {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, }}, - {RPCResult::Type::OBJ, "witness_script", /* optional */ true, "", + {RPCResult::Type::OBJ, "witness_script", /*optional=*/true, "", { {RPCResult::Type::STR, "asm", "The asm"}, {RPCResult::Type::STR_HEX, "hex", "The hex"}, {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, }}, - {RPCResult::Type::ARR, "bip32_derivs", /* optional */ true, "", + {RPCResult::Type::ARR, "bip32_derivs", /*optional=*/true, "", { - {RPCResult::Type::OBJ, "pubkey", /* optional */ true, "The public key with the derivation path as the value.", + {RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::STR, "pubkey", "The public key with the derivation path as the value."}, {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"}, {RPCResult::Type::STR, "path", "The path"}, }}, }}, - {RPCResult::Type::OBJ, "final_scriptsig", /* optional */ true, "", + {RPCResult::Type::OBJ, "final_scriptSig", /*optional=*/true, "", { {RPCResult::Type::STR, "asm", "The asm"}, {RPCResult::Type::STR, "hex", "The hex"}, }}, - {RPCResult::Type::ARR, "final_scriptwitness", "", + {RPCResult::Type::ARR, "final_scriptwitness", /*optional=*/true, "", { {RPCResult::Type::STR_HEX, "", "hex-encoded witness data (if any)"}, }}, - {RPCResult::Type::OBJ_DYN, "unknown", "The unknown global fields", + {RPCResult::Type::OBJ_DYN, "ripemd160_preimages", /*optional=*/ true, "", + { + {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, + }}, + {RPCResult::Type::OBJ_DYN, "sha256_preimages", /*optional=*/ true, "", + { + {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, + }}, + {RPCResult::Type::OBJ_DYN, "hash160_preimages", /*optional=*/ true, "", + { + {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, + }}, + {RPCResult::Type::OBJ_DYN, "hash256_preimages", /*optional=*/ true, "", + { + {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, + }}, + {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/ true, "The unknown input fields", { {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, }}, + {RPCResult::Type::ARR, "proprietary", /*optional=*/true, "The input proprietary map", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"}, + {RPCResult::Type::NUM, "subtype", "The number for the subtype"}, + {RPCResult::Type::STR_HEX, "key", "The hex for the key"}, + {RPCResult::Type::STR_HEX, "value", "The hex for the value"}, + }}, + }}, }}, }}, {RPCResult::Type::ARR, "outputs", "", { {RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::OBJ, "redeem_script", /* optional */ true, "", + {RPCResult::Type::OBJ, "redeem_script", /*optional=*/true, "", { {RPCResult::Type::STR, "asm", "The asm"}, {RPCResult::Type::STR_HEX, "hex", "The hex"}, {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, }}, - {RPCResult::Type::OBJ, "witness_script", /* optional */ true, "", + {RPCResult::Type::OBJ, "witness_script", /*optional=*/true, "", { {RPCResult::Type::STR, "asm", "The asm"}, {RPCResult::Type::STR_HEX, "hex", "The hex"}, {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, }}, - {RPCResult::Type::ARR, "bip32_derivs", /* optional */ true, "", + {RPCResult::Type::ARR, "bip32_derivs", /*optional=*/true, "", { {RPCResult::Type::OBJ, "", "", { @@ -1126,13 +1219,23 @@ static RPCHelpMan decodepsbt() {RPCResult::Type::STR, "path", "The path"}, }}, }}, - {RPCResult::Type::OBJ_DYN, "unknown", "The unknown global fields", + {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown global fields", { {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, }}, + {RPCResult::Type::ARR, "proprietary", /*optional=*/true, "The output proprietary map", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"}, + {RPCResult::Type::NUM, "subtype", "The number for the subtype"}, + {RPCResult::Type::STR_HEX, "key", "The hex for the key"}, + {RPCResult::Type::STR_HEX, "value", "The hex for the value"}, + }}, + }}, }}, }}, - {RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, "The transaction fee paid if all UTXOs slots in the PSBT have been filled."}, + {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The transaction fee paid if all UTXOs slots in the PSBT have been filled."}, } }, RPCExamples{ @@ -1156,6 +1259,38 @@ static RPCHelpMan decodepsbt() TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false); result.pushKV("tx", tx_univ); + // Add the global xpubs + UniValue global_xpubs(UniValue::VARR); + for (std::pair<KeyOriginInfo, std::set<CExtPubKey>> xpub_pair : psbtx.m_xpubs) { + for (auto& xpub : xpub_pair.second) { + std::vector<unsigned char> ser_xpub; + ser_xpub.assign(BIP32_EXTKEY_WITH_VERSION_SIZE, 0); + xpub.EncodeWithVersion(ser_xpub.data()); + + UniValue keypath(UniValue::VOBJ); + keypath.pushKV("xpub", EncodeBase58Check(ser_xpub)); + keypath.pushKV("master_fingerprint", HexStr(Span<unsigned char>(xpub_pair.first.fingerprint, xpub_pair.first.fingerprint + 4))); + keypath.pushKV("path", WriteHDKeypath(xpub_pair.first.path)); + global_xpubs.push_back(keypath); + } + } + result.pushKV("global_xpubs", global_xpubs); + + // PSBT version + result.pushKV("psbt_version", static_cast<uint64_t>(psbtx.GetVersion())); + + // Proprietary + UniValue proprietary(UniValue::VARR); + for (const auto& entry : psbtx.m_proprietary) { + UniValue this_prop(UniValue::VOBJ); + this_prop.pushKV("identifier", HexStr(entry.identifier)); + this_prop.pushKV("subtype", entry.subtype); + this_prop.pushKV("key", HexStr(entry.key)); + this_prop.pushKV("value", HexStr(entry.value)); + proprietary.push_back(this_prop); + } + result.pushKV("proprietary", proprietary); + // Unknown data UniValue unknowns(UniValue::VOBJ); for (auto entry : psbtx.unknown) { @@ -1177,7 +1312,7 @@ static RPCHelpMan decodepsbt() txout = input.witness_utxo; UniValue o(UniValue::VOBJ); - ScriptToUniv(txout.scriptPubKey, o, true); + ScriptPubKeyToUniv(txout.scriptPubKey, o, /* include_hex */ true); UniValue out(UniValue::VOBJ); out.pushKV("amount", ValueFromAmount(txout.nValue)); @@ -1217,19 +1352,19 @@ static RPCHelpMan decodepsbt() } // Sighash - if (input.sighash_type > 0) { - in.pushKV("sighash", SighashToStr((unsigned char)input.sighash_type)); + if (input.sighash_type != std::nullopt) { + in.pushKV("sighash", SighashToStr((unsigned char)*input.sighash_type)); } // Redeem script and witness script if (!input.redeem_script.empty()) { UniValue r(UniValue::VOBJ); - ScriptToUniv(input.redeem_script, r, false); + ScriptToUniv(input.redeem_script, r); in.pushKV("redeem_script", r); } if (!input.witness_script.empty()) { UniValue r(UniValue::VOBJ); - ScriptToUniv(input.witness_script, r, false); + ScriptToUniv(input.witness_script, r); in.pushKV("witness_script", r); } @@ -1262,6 +1397,56 @@ static RPCHelpMan decodepsbt() in.pushKV("final_scriptwitness", txinwitness); } + // Ripemd160 hash preimages + if (!input.ripemd160_preimages.empty()) { + UniValue ripemd160_preimages(UniValue::VOBJ); + for (const auto& [hash, preimage] : input.ripemd160_preimages) { + ripemd160_preimages.pushKV(HexStr(hash), HexStr(preimage)); + } + in.pushKV("ripemd160_preimages", ripemd160_preimages); + } + + // Sha256 hash preimages + if (!input.sha256_preimages.empty()) { + UniValue sha256_preimages(UniValue::VOBJ); + for (const auto& [hash, preimage] : input.sha256_preimages) { + sha256_preimages.pushKV(HexStr(hash), HexStr(preimage)); + } + in.pushKV("sha256_preimages", sha256_preimages); + } + + // Hash160 hash preimages + if (!input.hash160_preimages.empty()) { + UniValue hash160_preimages(UniValue::VOBJ); + for (const auto& [hash, preimage] : input.hash160_preimages) { + hash160_preimages.pushKV(HexStr(hash), HexStr(preimage)); + } + in.pushKV("hash160_preimages", hash160_preimages); + } + + // Hash256 hash preimages + if (!input.hash256_preimages.empty()) { + UniValue hash256_preimages(UniValue::VOBJ); + for (const auto& [hash, preimage] : input.hash256_preimages) { + hash256_preimages.pushKV(HexStr(hash), HexStr(preimage)); + } + in.pushKV("hash256_preimages", hash256_preimages); + } + + // Proprietary + if (!input.m_proprietary.empty()) { + UniValue proprietary(UniValue::VARR); + for (const auto& entry : input.m_proprietary) { + UniValue this_prop(UniValue::VOBJ); + this_prop.pushKV("identifier", HexStr(entry.identifier)); + this_prop.pushKV("subtype", entry.subtype); + this_prop.pushKV("key", HexStr(entry.key)); + this_prop.pushKV("value", HexStr(entry.value)); + proprietary.push_back(this_prop); + } + in.pushKV("proprietary", proprietary); + } + // Unknown data if (input.unknown.size() > 0) { UniValue unknowns(UniValue::VOBJ); @@ -1284,12 +1469,12 @@ static RPCHelpMan decodepsbt() // Redeem script and witness script if (!output.redeem_script.empty()) { UniValue r(UniValue::VOBJ); - ScriptToUniv(output.redeem_script, r, false); + ScriptToUniv(output.redeem_script, r); out.pushKV("redeem_script", r); } if (!output.witness_script.empty()) { UniValue r(UniValue::VOBJ); - ScriptToUniv(output.witness_script, r, false); + ScriptToUniv(output.witness_script, r); out.pushKV("witness_script", r); } @@ -1306,6 +1491,20 @@ static RPCHelpMan decodepsbt() out.pushKV("bip32_derivs", keypaths); } + // Proprietary + if (!output.m_proprietary.empty()) { + UniValue proprietary(UniValue::VARR); + for (const auto& entry : output.m_proprietary) { + UniValue this_prop(UniValue::VOBJ); + this_prop.pushKV("identifier", HexStr(entry.identifier)); + this_prop.pushKV("subtype", entry.subtype); + this_prop.pushKV("key", HexStr(entry.key)); + this_prop.pushKV("value", HexStr(entry.value)); + proprietary.push_back(this_prop); + } + out.pushKV("proprietary", proprietary); + } + // Unknown data if (output.unknown.size() > 0) { UniValue unknowns(UniValue::VOBJ); @@ -1400,8 +1599,8 @@ static RPCHelpMan finalizepsbt() RPCResult{ RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::STR, "psbt", "The base64-encoded partially signed transaction if not extracted"}, - {RPCResult::Type::STR_HEX, "hex", "The hex-encoded network transaction if extracted"}, + {RPCResult::Type::STR, "psbt", /*optional=*/true, "The base64-encoded partially signed transaction if not extracted"}, + {RPCResult::Type::STR_HEX, "hex", /*optional=*/true, "The hex-encoded network transaction if extracted"}, {RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"}, } }, @@ -1449,39 +1648,7 @@ static RPCHelpMan createpsbt() return RPCHelpMan{"createpsbt", "\nCreates a transaction in the Partially Signed Transaction format.\n" "Implements the Creator role.\n", - { - {"inputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The json objects", - { - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", - { - {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, - {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, - {"sequence", RPCArg::Type::NUM, RPCArg::DefaultHint{"depends on the value of the 'replaceable' and 'locktime' arguments"}, "The sequence number"}, - }, - }, - }, - }, - {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The outputs (key-value pairs), where none of the keys are duplicated.\n" - "That is, each address can only appear once and there can only be one 'data' object.\n" - "For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" - " accepted as second parameter.", - { - {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "", - { - {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT}, - }, - }, - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", - { - {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, - }, - }, - }, - }, - {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"}, - {"replaceable", RPCArg::Type::BOOL, RPCArg::Default{false}, "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."}, - }, + CreateTxDoc(), RPCResult{ RPCResult::Type::STR, "", "The resulting raw transaction (base64-encoded string)" }, @@ -1633,7 +1800,7 @@ static RPCHelpMan utxoupdatepsbt() } } // We don't actually need private keys further on; hide them as a precaution. - HidingSigningProvider public_provider(&provider, /* nosign */ true, /* nobip32derivs */ false); + HidingSigningProvider public_provider(&provider, /*hide_secret=*/true, /*hide_origin=*/false); // Fetch previous transactions (inputs): CCoinsView viewDummy; @@ -1672,7 +1839,7 @@ static RPCHelpMan utxoupdatepsbt() // 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_type */ 1); + SignPSBTInput(public_provider, psbtx, i, &txdata, /*sighash=*/1); } // Update script/keypath information using descriptor data. @@ -1751,6 +1918,13 @@ static RPCHelpMan joinpsbts() for (unsigned int i = 0; i < psbt.tx->vout.size(); ++i) { merged_psbt.AddOutput(psbt.tx->vout[i], psbt.outputs[i]); } + for (auto& xpub_pair : psbt.m_xpubs) { + if (merged_psbt.m_xpubs.count(xpub_pair.first) == 0) { + merged_psbt.m_xpubs[xpub_pair.first] = xpub_pair.second; + } else { + merged_psbt.m_xpubs[xpub_pair.first].insert(xpub_pair.second.begin(), xpub_pair.second.end()); + } + } merged_psbt.unknown.insert(psbt.unknown.begin(), psbt.unknown.end()); } @@ -1793,33 +1967,33 @@ static RPCHelpMan analyzepsbt() RPCResult { RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::ARR, "inputs", "", + {RPCResult::Type::ARR, "inputs", /*optional=*/true, "", { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::BOOL, "has_utxo", "Whether a UTXO is provided"}, {RPCResult::Type::BOOL, "is_final", "Whether the input is finalized"}, - {RPCResult::Type::OBJ, "missing", /* optional */ true, "Things that are missing that are required to complete this input", + {RPCResult::Type::OBJ, "missing", /*optional=*/true, "Things that are missing that are required to complete this input", { - {RPCResult::Type::ARR, "pubkeys", /* optional */ true, "", + {RPCResult::Type::ARR, "pubkeys", /*optional=*/true, "", { {RPCResult::Type::STR_HEX, "keyid", "Public key ID, hash160 of the public key, of a public key whose BIP 32 derivation path is missing"}, }}, - {RPCResult::Type::ARR, "signatures", /* optional */ true, "", + {RPCResult::Type::ARR, "signatures", /*optional=*/true, "", { {RPCResult::Type::STR_HEX, "keyid", "Public key ID, hash160 of the public key, of a public key whose signature is missing"}, }}, - {RPCResult::Type::STR_HEX, "redeemscript", /* optional */ true, "Hash160 of the redeemScript that is missing"}, - {RPCResult::Type::STR_HEX, "witnessscript", /* optional */ true, "SHA256 of the witnessScript that is missing"}, + {RPCResult::Type::STR_HEX, "redeemscript", /*optional=*/true, "Hash160 of the redeemScript that is missing"}, + {RPCResult::Type::STR_HEX, "witnessscript", /*optional=*/true, "SHA256 of the witnessScript that is missing"}, }}, - {RPCResult::Type::STR, "next", /* optional */ true, "Role of the next person that this input needs to go to"}, + {RPCResult::Type::STR, "next", /*optional=*/true, "Role of the next person that this input needs to go to"}, }}, }}, - {RPCResult::Type::NUM, "estimated_vsize", /* optional */ true, "Estimated vsize of the final signed transaction"}, - {RPCResult::Type::STR_AMOUNT, "estimated_feerate", /* optional */ true, "Estimated feerate of the final signed transaction in " + CURRENCY_UNIT + "/kvB. Shown only if all UTXO slots in the PSBT have been filled"}, - {RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, "The transaction fee paid. Shown only if all UTXO slots in the PSBT have been filled"}, + {RPCResult::Type::NUM, "estimated_vsize", /*optional=*/true, "Estimated vsize of the final signed transaction"}, + {RPCResult::Type::STR_AMOUNT, "estimated_feerate", /*optional=*/true, "Estimated feerate of the final signed transaction in " + CURRENCY_UNIT + "/kvB. Shown only if all UTXO slots in the PSBT have been filled"}, + {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The transaction fee paid. Shown only if all UTXO slots in the PSBT have been filled"}, {RPCResult::Type::STR, "next", "Role of the next person that this psbt needs to go to"}, - {RPCResult::Type::STR, "error", /* optional */ true, "Error message (if there is one)"}, + {RPCResult::Type::STR, "error", /*optional=*/true, "Error message (if there is one)"}, } }, RPCExamples { diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 122a92f084..e23fe34480 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -1,11 +1,12 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2021 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/rawtransaction_util.h> #include <coins.h> +#include <consensus/amount.h> #include <core_io.h> #include <key_io.h> #include <policy/policy.h> @@ -18,6 +19,7 @@ #include <univalue.h> #include <util/rbf.h> #include <util/strencodings.h> +#include <util/translation.h> CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf) { @@ -61,7 +63,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal if (rbf) { nSequence = MAX_BIP125_RBF_SEQUENCE; /* CTxIn::SEQUENCE_FINAL - 2 */ } else if (rawTx.nLockTime) { - nSequence = CTxIn::SEQUENCE_FINAL - 1; + nSequence = CTxIn::MAX_SEQUENCE_NONFINAL; /* CTxIn::SEQUENCE_FINAL - 1 */ } else { nSequence = CTxIn::SEQUENCE_FINAL; } @@ -280,22 +282,22 @@ void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, int nHashType = ParseSighashString(hashType); // Script verification errors - std::map<int, std::string> input_errors; + std::map<int, bilingual_str> input_errors; bool complete = SignTransaction(mtx, keystore, coins, nHashType, input_errors); SignTransactionResultToJSON(mtx, complete, coins, input_errors, result); } -void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, std::string>& input_errors, UniValue& result) +void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, bilingual_str>& input_errors, UniValue& result) { // Make errors UniValue UniValue vErrors(UniValue::VARR); for (const auto& err_pair : input_errors) { - if (err_pair.second == "Missing amount") { + if (err_pair.second.original == "Missing amount") { // This particular error needs to be an exception for some reason throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing amount for %s", coins.at(mtx.vin.at(err_pair.first).prevout).out.ToString())); } - TxInErrorToJSON(mtx.vin.at(err_pair.first), vErrors, err_pair.second); + TxInErrorToJSON(mtx.vin.at(err_pair.first), vErrors, err_pair.second.original); } result.pushKV("hex", EncodeHexTx(CTransaction(mtx))); diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index ce7d5834fa..c3eb1417f8 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020 The Bitcoin Core developers +// Copyright (c) 2017-2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -8,6 +8,7 @@ #include <map> #include <string> +struct bilingual_str; class FillableSigningProvider; class UniValue; struct CMutableTransaction; @@ -25,7 +26,7 @@ class SigningProvider; * @param result JSON object where signed transaction results accumulate */ void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result); -void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, std::string>& input_errors, UniValue& result); +void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, bilingual_str>& input_errors, UniValue& result); /** * Parse a prevtxs UniValue array and get the map of coins from it diff --git a/src/rpc/register.h b/src/rpc/register.h index 6724203ffe..c5055cc9d7 100644 --- a/src/rpc/register.h +++ b/src/rpc/register.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2021 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 a7866474e1..95a7c25b93 100644 --- a/src/rpc/request.cpp +++ b/src/rpc/request.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2021 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,11 @@ #include <util/system.h> #include <util/strencodings.h> +#include <fstream> +#include <stdexcept> +#include <string> +#include <vector> + /** * JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility, * but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were @@ -70,7 +75,7 @@ static fs::path GetAuthCookieFile(bool temp=false) if (temp) { arg += ".tmp"; } - return AbsPathForConfigVal(fs::path(arg)); + return AbsPathForConfigVal(fs::PathFromString(arg)); } bool GenerateAuthCookie(std::string *cookie_out) @@ -83,11 +88,11 @@ bool GenerateAuthCookie(std::string *cookie_out) /** the umask determines what permissions are used to create this file - * these are set to 077 in init.cpp unless overridden with -sysperms. */ - fsbridge::ofstream file; + std::ofstream file; fs::path filepath_tmp = GetAuthCookieFile(true); file.open(filepath_tmp); if (!file.is_open()) { - LogPrintf("Unable to open cookie authentication file %s for writing\n", filepath_tmp.string()); + LogPrintf("Unable to open cookie authentication file %s for writing\n", fs::PathToString(filepath_tmp)); return false; } file << cookie; @@ -95,10 +100,10 @@ bool GenerateAuthCookie(std::string *cookie_out) fs::path filepath = GetAuthCookieFile(false); if (!RenameOver(filepath_tmp, filepath)) { - LogPrintf("Unable to rename cookie authentication file %s to %s\n", filepath_tmp.string(), filepath.string()); + LogPrintf("Unable to rename cookie authentication file %s to %s\n", fs::PathToString(filepath_tmp), fs::PathToString(filepath)); return false; } - LogPrintf("Generated RPC authentication cookie %s\n", filepath.string()); + LogPrintf("Generated RPC authentication cookie %s\n", fs::PathToString(filepath)); if (cookie_out) *cookie_out = cookie; @@ -107,7 +112,7 @@ bool GenerateAuthCookie(std::string *cookie_out) bool GetAuthCookie(std::string *cookie_out) { - fsbridge::ifstream file; + std::ifstream file; std::string cookie; fs::path filepath = GetAuthCookieFile(); file.open(filepath); diff --git a/src/rpc/request.h b/src/rpc/request.h index bb091efea8..a682c58d96 100644 --- a/src/rpc/request.h +++ b/src/rpc/request.h @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2021 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/server.cpp b/src/rpc/server.cpp index cf80b08b96..c70236cc1c 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -239,7 +239,7 @@ static RPCHelpMan getrpcinfo() UniValue result(UniValue::VOBJ); result.pushKV("active_commands", active_commands); - const std::string path = LogInstance().m_file_path.string(); + const std::string path = LogInstance().m_file_path.u8string(); UniValue log_path(UniValue::VSTR, path); result.pushKV("logpath", log_path); @@ -540,7 +540,7 @@ void RPCRunLater(const std::string& name, std::function<void()> func, int64_t nS int RPCSerializationFlags() { int flag = 0; - if (gArgs.GetArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) == 0) + if (gArgs.GetIntArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) == 0) flag |= SERIALIZE_TRANSACTION_NO_WITNESS; return flag; } diff --git a/src/rpc/server.h b/src/rpc/server.h index 03967020c2..01e8556050 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -1,12 +1,11 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_RPC_SERVER_H #define BITCOIN_RPC_SERVER_H -#include <amount.h> #include <rpc/request.h> #include <rpc/util.h> diff --git a/src/rpc/server_util.cpp b/src/rpc/server_util.cpp new file mode 100644 index 0000000000..6a1b41f066 --- /dev/null +++ b/src/rpc/server_util.cpp @@ -0,0 +1,95 @@ +// Copyright (c) 2021 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/server_util.h> + +#include <net_processing.h> +#include <node/context.h> +#include <policy/fees.h> +#include <rpc/protocol.h> +#include <rpc/request.h> +#include <txmempool.h> +#include <util/system.h> +#include <validation.h> + +#include <any> + +using node::NodeContext; + +NodeContext& EnsureAnyNodeContext(const std::any& context) +{ + auto node_context = util::AnyPtr<NodeContext>(context); + if (!node_context) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Node context not found"); + } + return *node_context; +} + +CTxMemPool& EnsureMemPool(const NodeContext& node) +{ + if (!node.mempool) { + throw JSONRPCError(RPC_CLIENT_MEMPOOL_DISABLED, "Mempool disabled or instance not found"); + } + return *node.mempool; +} + +CTxMemPool& EnsureAnyMemPool(const std::any& context) +{ + return EnsureMemPool(EnsureAnyNodeContext(context)); +} + +ArgsManager& EnsureArgsman(const NodeContext& node) +{ + if (!node.args) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Node args not found"); + } + return *node.args; +} + +ArgsManager& EnsureAnyArgsman(const std::any& context) +{ + return EnsureArgsman(EnsureAnyNodeContext(context)); +} + +ChainstateManager& EnsureChainman(const NodeContext& node) +{ + if (!node.chainman) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Node chainman not found"); + } + return *node.chainman; +} + +ChainstateManager& EnsureAnyChainman(const std::any& context) +{ + return EnsureChainman(EnsureAnyNodeContext(context)); +} + +CBlockPolicyEstimator& EnsureFeeEstimator(const NodeContext& node) +{ + if (!node.fee_estimator) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Fee estimation disabled"); + } + return *node.fee_estimator; +} + +CBlockPolicyEstimator& EnsureAnyFeeEstimator(const std::any& context) +{ + return EnsureFeeEstimator(EnsureAnyNodeContext(context)); +} + +CConnman& EnsureConnman(const NodeContext& node) +{ + if (!node.connman) { + throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + } + return *node.connman; +} + +PeerManager& EnsurePeerman(const NodeContext& node) +{ + if (!node.peerman) { + throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + } + return *node.peerman; +} diff --git a/src/rpc/server_util.h b/src/rpc/server_util.h new file mode 100644 index 0000000000..2cc710a803 --- /dev/null +++ b/src/rpc/server_util.h @@ -0,0 +1,32 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_RPC_SERVER_UTIL_H +#define BITCOIN_RPC_SERVER_UTIL_H + +#include <any> + +class ArgsManager; +class CBlockPolicyEstimator; +class CConnman; +class CTxMemPool; +class ChainstateManager; +class PeerManager; +namespace node { +struct NodeContext; +} // namespace node + +node::NodeContext& EnsureAnyNodeContext(const std::any& context); +CTxMemPool& EnsureMemPool(const node::NodeContext& node); +CTxMemPool& EnsureAnyMemPool(const std::any& context); +ArgsManager& EnsureArgsman(const node::NodeContext& node); +ArgsManager& EnsureAnyArgsman(const std::any& context); +ChainstateManager& EnsureChainman(const node::NodeContext& node); +ChainstateManager& EnsureAnyChainman(const std::any& context); +CBlockPolicyEstimator& EnsureFeeEstimator(const node::NodeContext& node); +CBlockPolicyEstimator& EnsureAnyFeeEstimator(const std::any& context); +CConnman& EnsureConnman(const node::NodeContext& node); +PeerManager& EnsurePeerman(const node::NodeContext& node); + +#endif // BITCOIN_RPC_SERVER_UTIL_H diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 76f63546da..1754418034 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <consensus/amount.h> #include <key_io.h> #include <outputtype.h> #include <rpc/util.h> @@ -20,6 +21,16 @@ const std::string UNIX_EPOCH_TIME = "UNIX epoch time"; const std::string EXAMPLE_ADDRESS[2] = {"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl", "bc1q02ad21edsxd23d32dfgqqsz4vv4nmtfzuklhy3"}; +std::string GetAllOutputTypes() +{ + std::vector<std::string> ret; + using U = std::underlying_type<TxoutType>::type; + for (U i = (U)TxoutType::NONSTANDARD; i <= (U)TxoutType::WITNESS_UNKNOWN; ++i) { + ret.emplace_back(GetTxnOutputType(static_cast<TxoutType>(i))); + } + return Join(ret, ", "); +} + void RPCTypeCheck(const UniValue& params, const std::list<UniValueType>& typesExpected, bool fAllowNull) @@ -209,7 +220,7 @@ CPubKey AddrToPubKey(const FillableSigningProvider& keystore, const std::string& } CKeyID key = GetKeyForDestination(keystore, dest); if (key.IsNull()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("%s does not refer to a key", addr_in)); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' does not refer to a key", addr_in)); } CPubKey vchPubKey; if (!keystore.GetPubKey(key, vchPubKey)) { @@ -316,7 +327,7 @@ public: UniValue obj(UniValue::VOBJ); obj.pushKV("iswitness", true); obj.pushKV("witness_version", (int)id.version); - obj.pushKV("witness_program", HexStr(Span<const unsigned char>(id.program, id.length))); + obj.pushKV("witness_program", HexStr({id.program, id.length})); return obj; } }; @@ -619,10 +630,9 @@ std::string RPCHelpMan::ToString() const ret += arg.ToString(/* oneline */ true); } if (was_optional) ret += " )"; - ret += "\n"; // Description - ret += m_description; + ret += "\n\n" + TrimString(m_description) + "\n"; // Arguments Sections sections; @@ -831,11 +841,14 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const } case Type::OBJ_DYN: case Type::OBJ: { + if (m_inner.empty()) { + sections.PushSection({indent + maybe_key + "{}", Description("empty JSON object")}); + return; + } sections.PushSection({indent + maybe_key + "{", Description("json object")}); for (const auto& i : m_inner) { i.ToSections(sections, OuterType::OBJ, current_indent + 2); } - CHECK_NONFATAL(!m_inner.empty()); if (m_type == Type::OBJ_DYN && m_inner.back().m_type != Type::ELISION) { // If the dictionary keys are dynamic, use three dots for continuation sections.PushSection({indent_next + "...", ""}); @@ -886,6 +899,17 @@ bool RPCResult::MatchesType(const UniValue& result) const CHECK_NONFATAL(false); } +void RPCResult::CheckInnerDoc() const +{ + if (m_type == Type::OBJ) { + // May or may not be empty + return; + } + // Everything else must either be empty or not + const bool inner_needed{m_type == Type::ARR || m_type == Type::ARR_FIXED || m_type == Type::OBJ_DYN}; + CHECK_NONFATAL(inner_needed != m_inner.empty()); +} + std::string RPCArg::ToStringObj(const bool oneline) const { std::string res; diff --git a/src/rpc/util.h b/src/rpc/util.h index d43ee33b0f..89d32d4193 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -39,6 +39,13 @@ class CPubKey; class CScript; struct Sections; +/** + * Gets all existing output types formatted for RPC help sections. + * + * @return Comma separated string representing output type names. + */ +std::string GetAllOutputTypes(); + /** Wrapper for UniValue::VType, which includes typeAny: * Used to denote don't care type. */ struct UniValueType { @@ -267,8 +274,7 @@ struct RPCResult { m_cond{std::move(cond)} { CHECK_NONFATAL(!m_cond.empty()); - const bool inner_needed{type == Type::ARR || type == Type::ARR_FIXED || type == Type::OBJ || type == Type::OBJ_DYN}; - CHECK_NONFATAL(inner_needed != inner.empty()); + CheckInnerDoc(); } RPCResult( @@ -292,8 +298,7 @@ struct RPCResult { m_description{std::move(description)}, m_cond{} { - const bool inner_needed{type == Type::ARR || type == Type::ARR_FIXED || type == Type::OBJ || type == Type::OBJ_DYN}; - CHECK_NONFATAL(inner_needed != inner.empty()); + CheckInnerDoc(); } RPCResult( @@ -311,6 +316,9 @@ struct RPCResult { std::string ToDescriptionString() const; /** Check whether the result JSON type matches. */ bool MatchesType(const UniValue& result) const; + +private: + void CheckInnerDoc() const; }; struct RPCResults { |