aboutsummaryrefslogtreecommitdiff
path: root/src/rpc/mempool.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc/mempool.cpp')
-rw-r--r--src/rpc/mempool.cpp476
1 files changed, 476 insertions, 0 deletions
diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
new file mode 100644
index 0000000000..bc7ef0c08e
--- /dev/null
+++ b/src/rpc/mempool.cpp
@@ -0,0 +1,476 @@
+// 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 <rpc/blockchain.h>
+
+#include <core_io.h>
+#include <fs.h>
+#include <policy/rbf.h>
+#include <primitives/transaction.h>
+#include <rpc/server.h>
+#include <rpc/server_util.h>
+#include <rpc/util.h>
+#include <txmempool.h>
+#include <univalue.h>
+#include <validation.h>
+
+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", /*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<std::string> 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<uint256> 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<uint64_t>::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()));
+ int64_t maxmempool{gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000};
+ ret.pushKV("maxmempool", 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;
+},
+ };
+}
+
+void RegisterMempoolRPCCommands(CRPCTable& t)
+{
+ static const CRPCCommand commands[]{
+ // category actor (function)
+ // -------- ----------------
+ {"blockchain", &getmempoolancestors},
+ {"blockchain", &getmempooldescendants},
+ {"blockchain", &getmempoolentry},
+ {"blockchain", &getmempoolinfo},
+ {"blockchain", &getrawmempool},
+ {"blockchain", &savemempool},
+ };
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
+}