From fa2a5f301aa678aa7b6be4bb7987f2bcbdaf2be2 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Fri, 11 Mar 2022 17:51:36 +0100 Subject: rpc: Move mempool RPCs to new file Can be reviewed with: --color-moved=dimmed-zebra --color-moved-ws=ignore-all-space --- src/Makefile.am | 2 + src/bench/rpc_mempool.cpp | 2 +- src/rest.cpp | 1 + src/rpc/blockchain.cpp | 458 +-------------------------------------------- src/rpc/blockchain.h | 7 - src/rpc/mempool.cpp | 459 ++++++++++++++++++++++++++++++++++++++++++++++ src/rpc/mempool.h | 17 ++ 7 files changed, 489 insertions(+), 457 deletions(-) create mode 100644 src/rpc/mempool.cpp create mode 100644 src/rpc/mempool.h (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 8f4cbee62f..e940736b71 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -209,6 +209,7 @@ BITCOIN_CORE_H = \ reverse_iterator.h \ rpc/blockchain.h \ rpc/client.h \ + rpc/mempool.h \ rpc/mining.h \ rpc/protocol.h \ rpc/rawtransaction_util.h \ @@ -370,6 +371,7 @@ libbitcoin_node_a_SOURCES = \ pow.cpp \ rest.cpp \ rpc/blockchain.cpp \ + rpc/mempool.cpp \ rpc/mining.cpp \ rpc/misc.cpp \ rpc/net.cpp \ diff --git a/src/bench/rpc_mempool.cpp b/src/bench/rpc_mempool.cpp index 12dcff5844..64e4c46899 100644 --- a/src/bench/rpc_mempool.cpp +++ b/src/bench/rpc_mempool.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include -#include +#include #include #include diff --git a/src/rest.cpp b/src/rest.cpp index 063872b47a..d86a62030c 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 86dfbbae35..fec7c8f27f 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -26,10 +26,6 @@ #include #include #include -#include -#include -#include -#include #include #include #include @@ -40,8 +36,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -50,8 +46,6 @@ #include -#include - #include #include #include @@ -426,366 +420,6 @@ static RPCHelpMan getdifficulty() }; } -static std::vector 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", /*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", /*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", /*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, 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"}}}, - RPCResult{RPCResult::Type::ARR, "spentby", "unconfirmed transactions spending outputs from this transaction", - {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}}, - RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction could be replaced due to BIP125 (replace-by-fee)"}, - RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"}, -};} - -static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs) -{ - AssertLockHeld(pool.cs); - - info.pushKV("vsize", (int)e.GetTxSize()); - info.pushKV("weight", (int)e.GetTxWeight()); - // 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()); - if (deprecated_fee_fields_enabled) { - info.pushKV("descendantfees", e.GetModFeesWithDescendants()); - } - info.pushKV("ancestorcount", e.GetCountWithAncestors()); - info.pushKV("ancestorsize", e.GetSizeWithAncestors()); - 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 setDepends; - for (const CTxIn& txin : tx.vin) - { - if (pool.exists(GenTxid::Txid(txin.prevout.hash))) - setDepends.insert(txin.prevout.hash.ToString()); - } - - UniValue depends(UniValue::VARR); - for (const std::string& dep : setDepends) - { - depends.push_back(dep); - } - - info.pushKV("depends", depends); - - UniValue spent(UniValue::VARR); - const CTxMemPool::txiter& it = pool.mapTx.find(tx.GetHash()); - const CTxMemPoolEntry::Children& children = it->GetMemPoolChildrenConst(); - for (const CTxMemPoolEntry& child : children) { - spent.push_back(child.GetTx().GetHash().ToString()); - } - - info.pushKV("spentby", spent); - - // Add opt-in RBF status - bool rbfStatus = false; - RBFTransactionState rbfState = IsRBFOptIn(tx, pool); - if (rbfState == RBFTransactionState::UNKNOWN) { - throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not in mempool"); - } else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125) { - rbfStatus = true; - } - - info.pushKV("bip125-replaceable", rbfStatus); - info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetHash())); -} - -UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose, bool include_mempool_sequence) -{ - if (verbose) { - if (include_mempool_sequence) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Verbose results cannot contain mempool sequence values."); - } - LOCK(pool.cs); - UniValue o(UniValue::VOBJ); - for (const CTxMemPoolEntry& e : pool.mapTx) { - const uint256& hash = e.GetTx().GetHash(); - UniValue info(UniValue::VOBJ); - entryToJSON(pool, info, e); - // Mempool has unique entries so there is no advantage in using - // UniValue::pushKV, which checks if the key already exists in O(N). - // UniValue::__pushKV is used instead which currently is O(1). - o.__pushKV(hash.ToString(), info); - } - return o; - } else { - uint64_t mempool_sequence; - std::vector vtxid; - { - LOCK(pool.cs); - pool.queryHashes(vtxid); - mempool_sequence = pool.GetSequence(); - } - UniValue a(UniValue::VARR); - for (const uint256& hash : vtxid) - a.push_back(hash.ToString()); - - if (!include_mempool_sequence) { - return a; - } else { - UniValue o(UniValue::VOBJ); - o.pushKV("txids", a); - o.pushKV("mempool_sequence", mempool_sequence); - return o; - } - } -} - -static RPCHelpMan getrawmempool() -{ - return RPCHelpMan{"getrawmempool", - "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n" - "\nHint: use getmempoolentry to fetch a specific transaction from the mempool.\n", - { - {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, - {"mempool_sequence", RPCArg::Type::BOOL, RPCArg::Default{false}, "If verbose=false, returns a json object with transaction list and mempool sequence number attached."}, - }, - { - RPCResult{"for verbose = false", - RPCResult::Type::ARR, "", "", - { - {RPCResult::Type::STR_HEX, "", "The transaction id"}, - }}, - RPCResult{"for verbose = true", - RPCResult::Type::OBJ_DYN, "", "", - { - {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, - }}, - RPCResult{"for verbose = false and mempool_sequence = true", - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::ARR, "txids", "", - { - {RPCResult::Type::STR_HEX, "", "The transaction id"}, - }}, - {RPCResult::Type::NUM, "mempool_sequence", "The mempool sequence value."}, - }}, - }, - RPCExamples{ - HelpExampleCli("getrawmempool", "true") - + HelpExampleRpc("getrawmempool", "true") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - bool fVerbose = false; - if (!request.params[0].isNull()) - fVerbose = request.params[0].get_bool(); - - bool include_mempool_sequence = false; - if (!request.params[1].isNull()) { - include_mempool_sequence = request.params[1].get_bool(); - } - - return MempoolToJSON(EnsureAnyMemPool(request.context), fVerbose, include_mempool_sequence); -}, - }; -} - -static RPCHelpMan getmempoolancestors() -{ - return RPCHelpMan{"getmempoolancestors", - "\nIf txid is in the mempool, returns all in-mempool ancestors.\n", - { - {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, - {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, - }, - { - RPCResult{"for verbose = false", - RPCResult::Type::ARR, "", "", - {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool ancestor transaction"}}}, - RPCResult{"for verbose = true", - RPCResult::Type::OBJ_DYN, "", "", - { - {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, - }}, - }, - RPCExamples{ - HelpExampleCli("getmempoolancestors", "\"mytxid\"") - + HelpExampleRpc("getmempoolancestors", "\"mytxid\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - bool fVerbose = false; - if (!request.params[1].isNull()) - fVerbose = request.params[1].get_bool(); - - uint256 hash = ParseHashV(request.params[0], "parameter 1"); - - const CTxMemPool& mempool = EnsureAnyMemPool(request.context); - LOCK(mempool.cs); - - CTxMemPool::txiter it = mempool.mapTx.find(hash); - if (it == mempool.mapTx.end()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); - } - - CTxMemPool::setEntries setAncestors; - uint64_t noLimit = std::numeric_limits::max(); - std::string dummy; - mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false); - - if (!fVerbose) { - UniValue o(UniValue::VARR); - for (CTxMemPool::txiter ancestorIt : setAncestors) { - o.push_back(ancestorIt->GetTx().GetHash().ToString()); - } - return o; - } else { - UniValue o(UniValue::VOBJ); - for (CTxMemPool::txiter ancestorIt : setAncestors) { - const CTxMemPoolEntry &e = *ancestorIt; - const uint256& _hash = e.GetTx().GetHash(); - UniValue info(UniValue::VOBJ); - entryToJSON(mempool, info, e); - o.pushKV(_hash.ToString(), info); - } - return o; - } -}, - }; -} - -static RPCHelpMan getmempooldescendants() -{ - return RPCHelpMan{"getmempooldescendants", - "\nIf txid is in the mempool, returns all in-mempool descendants.\n", - { - {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, - {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, - }, - { - RPCResult{"for verbose = false", - RPCResult::Type::ARR, "", "", - {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool descendant transaction"}}}, - RPCResult{"for verbose = true", - RPCResult::Type::OBJ_DYN, "", "", - { - {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, - }}, - }, - RPCExamples{ - HelpExampleCli("getmempooldescendants", "\"mytxid\"") - + HelpExampleRpc("getmempooldescendants", "\"mytxid\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - bool fVerbose = false; - if (!request.params[1].isNull()) - fVerbose = request.params[1].get_bool(); - - uint256 hash = ParseHashV(request.params[0], "parameter 1"); - - const CTxMemPool& mempool = EnsureAnyMemPool(request.context); - LOCK(mempool.cs); - - CTxMemPool::txiter it = mempool.mapTx.find(hash); - if (it == mempool.mapTx.end()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); - } - - CTxMemPool::setEntries setDescendants; - mempool.CalculateDescendants(it, setDescendants); - // CTxMemPool::CalculateDescendants will include the given tx - setDescendants.erase(it); - - if (!fVerbose) { - UniValue o(UniValue::VARR); - for (CTxMemPool::txiter descendantIt : setDescendants) { - o.push_back(descendantIt->GetTx().GetHash().ToString()); - } - - return o; - } else { - UniValue o(UniValue::VOBJ); - for (CTxMemPool::txiter descendantIt : setDescendants) { - const CTxMemPoolEntry &e = *descendantIt; - const uint256& _hash = e.GetTx().GetHash(); - UniValue info(UniValue::VOBJ); - entryToJSON(mempool, info, e); - o.pushKV(_hash.ToString(), info); - } - return o; - } -}, - }; -} - -static RPCHelpMan getmempoolentry() -{ - return RPCHelpMan{"getmempoolentry", - "\nReturns mempool data for given transaction\n", - { - {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", MempoolEntryDescription()}, - RPCExamples{ - HelpExampleCli("getmempoolentry", "\"mytxid\"") - + HelpExampleRpc("getmempoolentry", "\"mytxid\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - uint256 hash = ParseHashV(request.params[0], "parameter 1"); - - const CTxMemPool& mempool = EnsureAnyMemPool(request.context); - LOCK(mempool.cs); - - CTxMemPool::txiter it = mempool.mapTx.find(hash); - if (it == mempool.mapTx.end()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); - } - - const CTxMemPoolEntry &e = *it; - UniValue info(UniValue::VOBJ); - entryToJSON(mempool, info, e); - return info; -}, - }; -} - static RPCHelpMan getblockfrompeer() { return RPCHelpMan{ @@ -1809,53 +1443,6 @@ static RPCHelpMan getchaintips() }; } -UniValue MempoolInfoToJSON(const CTxMemPool& pool) -{ - // Make sure this call is atomic in the pool. - LOCK(pool.cs); - UniValue ret(UniValue::VOBJ); - ret.pushKV("loaded", pool.IsLoaded()); - ret.pushKV("size", (int64_t)pool.size()); - 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.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())); - ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()}); - return ret; -} - -static RPCHelpMan getmempoolinfo() -{ - return RPCHelpMan{"getmempoolinfo", - "\nReturns details on the active state of the TX memory pool.\n", - {}, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::BOOL, "loaded", "True if the mempool is fully loaded"}, - {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 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"}, - {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"} - }}, - RPCExamples{ - HelpExampleCli("getmempoolinfo", "") - + HelpExampleRpc("getmempoolinfo", "") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - return MempoolInfoToJSON(EnsureAnyMemPool(request.context)); -}, - }; -} - static RPCHelpMan preciousblock() { return RPCHelpMan{"preciousblock", @@ -2352,41 +1939,6 @@ static RPCHelpMan getblockstats() }; } -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::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()) { - throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet"); - } - - if (!DumpMempool(mempool)) { - throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk"); - } - - UniValue ret(UniValue::VOBJ); - ret.pushKV("filename", fs::path((args.GetDataDirNet() / "mempool.dat")).u8string()); - - return ret; -}, - }; -} - namespace { //! Search for a given set of pubkey scripts bool FindScriptPubKey(std::atomic& scan_progress, const std::atomic& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set& needles, std::map& out_results, std::function& interruption_point) @@ -2825,6 +2377,14 @@ UniValue CreateUTXOSnapshot( return result; } + +RPCHelpMan getmempoolancestors(); +RPCHelpMan getmempooldescendants(); +RPCHelpMan getmempoolentry(); +RPCHelpMan getmempoolinfo(); +RPCHelpMan getrawmempool(); +RPCHelpMan savemempool(); + void RegisterBlockchainRPCCommands(CRPCTable &t) { // clang-format off diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index 1f51d7c1ad..a8c6d171cc 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -20,7 +20,6 @@ extern RecursiveMutex cs_main; class CBlock; class CBlockIndex; class CChainState; -class CTxMemPool; class UniValue; namespace node { struct NodeContext; @@ -42,12 +41,6 @@ void RPCNotifyBlockChange(const CBlockIndex*); /** Block description to JSON */ 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); - -/** Mempool to JSON */ -UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose = false, bool include_mempool_sequence = false); - /** Block header to JSON */ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex) LOCKS_EXCLUDED(cs_main); diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp new file mode 100644 index 0000000000..49d7914454 --- /dev/null +++ b/src/rpc/mempool.cpp @@ -0,0 +1,459 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static std::vector 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", /*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", /*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", /*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, 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"}}}, + RPCResult{RPCResult::Type::ARR, "spentby", "unconfirmed transactions spending outputs from this transaction", + {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}}, + RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction could be replaced due to BIP125 (replace-by-fee)"}, + RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"}, +};} + +static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs) +{ + AssertLockHeld(pool.cs); + + info.pushKV("vsize", (int)e.GetTxSize()); + info.pushKV("weight", (int)e.GetTxWeight()); + // 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()); + if (deprecated_fee_fields_enabled) { + info.pushKV("descendantfees", e.GetModFeesWithDescendants()); + } + info.pushKV("ancestorcount", e.GetCountWithAncestors()); + info.pushKV("ancestorsize", e.GetSizeWithAncestors()); + 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 setDepends; + for (const CTxIn& txin : tx.vin) + { + if (pool.exists(GenTxid::Txid(txin.prevout.hash))) + setDepends.insert(txin.prevout.hash.ToString()); + } + + UniValue depends(UniValue::VARR); + for (const std::string& dep : setDepends) + { + depends.push_back(dep); + } + + info.pushKV("depends", depends); + + UniValue spent(UniValue::VARR); + const CTxMemPool::txiter& it = pool.mapTx.find(tx.GetHash()); + const CTxMemPoolEntry::Children& children = it->GetMemPoolChildrenConst(); + for (const CTxMemPoolEntry& child : children) { + spent.push_back(child.GetTx().GetHash().ToString()); + } + + info.pushKV("spentby", spent); + + // Add opt-in RBF status + bool rbfStatus = false; + RBFTransactionState rbfState = IsRBFOptIn(tx, pool); + if (rbfState == RBFTransactionState::UNKNOWN) { + throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not in mempool"); + } else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125) { + rbfStatus = true; + } + + info.pushKV("bip125-replaceable", rbfStatus); + info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetHash())); +} + +UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose, bool include_mempool_sequence) +{ + if (verbose) { + if (include_mempool_sequence) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Verbose results cannot contain mempool sequence values."); + } + LOCK(pool.cs); + UniValue o(UniValue::VOBJ); + for (const CTxMemPoolEntry& e : pool.mapTx) { + const uint256& hash = e.GetTx().GetHash(); + UniValue info(UniValue::VOBJ); + entryToJSON(pool, info, e); + // Mempool has unique entries so there is no advantage in using + // UniValue::pushKV, which checks if the key already exists in O(N). + // UniValue::__pushKV is used instead which currently is O(1). + o.__pushKV(hash.ToString(), info); + } + return o; + } else { + uint64_t mempool_sequence; + std::vector vtxid; + { + LOCK(pool.cs); + pool.queryHashes(vtxid); + mempool_sequence = pool.GetSequence(); + } + UniValue a(UniValue::VARR); + for (const uint256& hash : vtxid) + a.push_back(hash.ToString()); + + if (!include_mempool_sequence) { + return a; + } else { + UniValue o(UniValue::VOBJ); + o.pushKV("txids", a); + o.pushKV("mempool_sequence", mempool_sequence); + return o; + } + } +} + +RPCHelpMan getrawmempool() +{ + return RPCHelpMan{"getrawmempool", + "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n" + "\nHint: use getmempoolentry to fetch a specific transaction from the mempool.\n", + { + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, + {"mempool_sequence", RPCArg::Type::BOOL, RPCArg::Default{false}, "If verbose=false, returns a json object with transaction list and mempool sequence number attached."}, + }, + { + RPCResult{"for verbose = false", + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::STR_HEX, "", "The transaction id"}, + }}, + RPCResult{"for verbose = true", + RPCResult::Type::OBJ_DYN, "", "", + { + {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, + }}, + RPCResult{"for verbose = false and mempool_sequence = true", + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::ARR, "txids", "", + { + {RPCResult::Type::STR_HEX, "", "The transaction id"}, + }}, + {RPCResult::Type::NUM, "mempool_sequence", "The mempool sequence value."}, + }}, + }, + RPCExamples{ + HelpExampleCli("getrawmempool", "true") + + HelpExampleRpc("getrawmempool", "true") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + bool fVerbose = false; + if (!request.params[0].isNull()) + fVerbose = request.params[0].get_bool(); + + bool include_mempool_sequence = false; + if (!request.params[1].isNull()) { + include_mempool_sequence = request.params[1].get_bool(); + } + + return MempoolToJSON(EnsureAnyMemPool(request.context), fVerbose, include_mempool_sequence); +}, + }; +} + +RPCHelpMan getmempoolancestors() +{ + return RPCHelpMan{"getmempoolancestors", + "\nIf txid is in the mempool, returns all in-mempool ancestors.\n", + { + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, + }, + { + RPCResult{"for verbose = false", + RPCResult::Type::ARR, "", "", + {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool ancestor transaction"}}}, + RPCResult{"for verbose = true", + RPCResult::Type::OBJ_DYN, "", "", + { + {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, + }}, + }, + RPCExamples{ + HelpExampleCli("getmempoolancestors", "\"mytxid\"") + + HelpExampleRpc("getmempoolancestors", "\"mytxid\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + bool fVerbose = false; + if (!request.params[1].isNull()) + fVerbose = request.params[1].get_bool(); + + uint256 hash = ParseHashV(request.params[0], "parameter 1"); + + const CTxMemPool& mempool = EnsureAnyMemPool(request.context); + LOCK(mempool.cs); + + CTxMemPool::txiter it = mempool.mapTx.find(hash); + if (it == mempool.mapTx.end()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); + } + + CTxMemPool::setEntries setAncestors; + uint64_t noLimit = std::numeric_limits::max(); + std::string dummy; + mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false); + + if (!fVerbose) { + UniValue o(UniValue::VARR); + for (CTxMemPool::txiter ancestorIt : setAncestors) { + o.push_back(ancestorIt->GetTx().GetHash().ToString()); + } + return o; + } else { + UniValue o(UniValue::VOBJ); + for (CTxMemPool::txiter ancestorIt : setAncestors) { + const CTxMemPoolEntry &e = *ancestorIt; + const uint256& _hash = e.GetTx().GetHash(); + UniValue info(UniValue::VOBJ); + entryToJSON(mempool, info, e); + o.pushKV(_hash.ToString(), info); + } + return o; + } +}, + }; +} + +RPCHelpMan getmempooldescendants() +{ + return RPCHelpMan{"getmempooldescendants", + "\nIf txid is in the mempool, returns all in-mempool descendants.\n", + { + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, + }, + { + RPCResult{"for verbose = false", + RPCResult::Type::ARR, "", "", + {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool descendant transaction"}}}, + RPCResult{"for verbose = true", + RPCResult::Type::OBJ_DYN, "", "", + { + {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, + }}, + }, + RPCExamples{ + HelpExampleCli("getmempooldescendants", "\"mytxid\"") + + HelpExampleRpc("getmempooldescendants", "\"mytxid\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + bool fVerbose = false; + if (!request.params[1].isNull()) + fVerbose = request.params[1].get_bool(); + + uint256 hash = ParseHashV(request.params[0], "parameter 1"); + + const CTxMemPool& mempool = EnsureAnyMemPool(request.context); + LOCK(mempool.cs); + + CTxMemPool::txiter it = mempool.mapTx.find(hash); + if (it == mempool.mapTx.end()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); + } + + CTxMemPool::setEntries setDescendants; + mempool.CalculateDescendants(it, setDescendants); + // CTxMemPool::CalculateDescendants will include the given tx + setDescendants.erase(it); + + if (!fVerbose) { + UniValue o(UniValue::VARR); + for (CTxMemPool::txiter descendantIt : setDescendants) { + o.push_back(descendantIt->GetTx().GetHash().ToString()); + } + + return o; + } else { + UniValue o(UniValue::VOBJ); + for (CTxMemPool::txiter descendantIt : setDescendants) { + const CTxMemPoolEntry &e = *descendantIt; + const uint256& _hash = e.GetTx().GetHash(); + UniValue info(UniValue::VOBJ); + entryToJSON(mempool, info, e); + o.pushKV(_hash.ToString(), info); + } + return o; + } +}, + }; +} + +RPCHelpMan getmempoolentry() +{ + return RPCHelpMan{"getmempoolentry", + "\nReturns mempool data for given transaction\n", + { + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", MempoolEntryDescription()}, + RPCExamples{ + HelpExampleCli("getmempoolentry", "\"mytxid\"") + + HelpExampleRpc("getmempoolentry", "\"mytxid\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + uint256 hash = ParseHashV(request.params[0], "parameter 1"); + + const CTxMemPool& mempool = EnsureAnyMemPool(request.context); + LOCK(mempool.cs); + + CTxMemPool::txiter it = mempool.mapTx.find(hash); + if (it == mempool.mapTx.end()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); + } + + const CTxMemPoolEntry &e = *it; + UniValue info(UniValue::VOBJ); + entryToJSON(mempool, info, e); + return info; +}, + }; +} + +UniValue MempoolInfoToJSON(const CTxMemPool& pool) +{ + // Make sure this call is atomic in the pool. + LOCK(pool.cs); + UniValue ret(UniValue::VOBJ); + ret.pushKV("loaded", pool.IsLoaded()); + ret.pushKV("size", (int64_t)pool.size()); + 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.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())); + ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()}); + return ret; +} + +RPCHelpMan getmempoolinfo() +{ + return RPCHelpMan{"getmempoolinfo", + "\nReturns details on the active state of the TX memory pool.\n", + {}, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::BOOL, "loaded", "True if the mempool is fully loaded"}, + {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 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"}, + {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"} + }}, + RPCExamples{ + HelpExampleCli("getmempoolinfo", "") + + HelpExampleRpc("getmempoolinfo", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + return MempoolInfoToJSON(EnsureAnyMemPool(request.context)); +}, + }; +} + +RPCHelpMan savemempool() +{ + return RPCHelpMan{"savemempool", + "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n", + {}, + 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()) { + throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet"); + } + + if (!DumpMempool(mempool)) { + throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk"); + } + + UniValue ret(UniValue::VOBJ); + ret.pushKV("filename", fs::path((args.GetDataDirNet() / "mempool.dat")).u8string()); + + return ret; +}, + }; +} diff --git a/src/rpc/mempool.h b/src/rpc/mempool.h new file mode 100644 index 0000000000..229d7d52dd --- /dev/null +++ b/src/rpc/mempool.h @@ -0,0 +1,17 @@ +// Copyright (c) 2017-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_RPC_MEMPOOL_H +#define BITCOIN_RPC_MEMPOOL_H + +class CTxMemPool; +class UniValue; + +/** Mempool information to JSON */ +UniValue MempoolInfoToJSON(const CTxMemPool& pool); + +/** Mempool to JSON */ +UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose = false, bool include_mempool_sequence = false); + +#endif // BITCOIN_RPC_MEMPOOL_H -- cgit v1.2.3