Line data Source code
1 : // Copyright (c) 2010 Satoshi Nakamoto
2 : // Copyright (c) 2009-2022 The Bitcoin Core developers
3 : // Distributed under the MIT software license, see the accompanying
4 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 :
6 : #include <rpc/blockchain.h>
7 :
8 : #include <instantsend/instantsend.h>
9 : #include <llmq/context.h>
10 : #include <util/helpers.h>
11 :
12 : #include <chainparams.h>
13 : #include <core_io.h>
14 : #include <fs.h>
15 : #include <policy/settings.h>
16 : #include <primitives/transaction.h>
17 : #include <rpc/server.h>
18 : #include <rpc/server_util.h>
19 : #include <rpc/util.h>
20 : #include <txmempool.h>
21 : #include <univalue.h>
22 : #include <util/moneystr.h>
23 : #include <validation.h>
24 : #include <util/system.h>
25 : #include <util/strencodings.h>
26 : #include <util/time.h>
27 :
28 : using node::DEFAULT_MAX_RAW_TX_FEE_RATE;
29 : using node::NodeContext;
30 :
31 95 : RPCHelpMan sendrawtransaction()
32 : {
33 190 : return RPCHelpMan{"sendrawtransaction",
34 95 : "\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n"
35 : "\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n"
36 : "for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n"
37 : "nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n"
38 : "\nA specific exception, RPC_TRANSACTION_ALREADY_IN_CHAIN, may throw if the transaction cannot be added to the mempool.\n"
39 : "\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n",
40 475 : {
41 95 : {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
42 190 : {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
43 95 : "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
44 : "/kB.\nSet to 0 to accept any fee rate.\n"},
45 95 : {"instantsend", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Deprecated and ignored"},
46 95 : {"bypasslimits", RPCArg::Type::BOOL, RPCArg::Default{false}, "Bypass transaction policy limits"},
47 : },
48 95 : RPCResult{
49 95 : RPCResult::Type::STR_HEX, "", "The transaction hash in hex"
50 : },
51 95 : RPCExamples{
52 : "\nCreate a transaction\n"
53 95 : + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
54 : "Sign the transaction, and get back the hex\n"
55 95 : + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
56 : "\nSend the transaction (signed hex)\n"
57 95 : + HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
58 : "\nAs a JSON-RPC call\n"
59 95 : + HelpExampleRpc("sendrawtransaction", "\"signedhex\"")
60 : },
61 97 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
62 : {
63 12 : RPCTypeCheck(request.params, {
64 2 : UniValue::VSTR,
65 2 : UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
66 2 : UniValue::VBOOL,
67 2 : UniValue::VBOOL,
68 : });
69 :
70 2 : CMutableTransaction mtx;
71 2 : if (!DecodeHexTx(mtx, request.params[0].get_str())) {
72 2 : throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
73 : }
74 0 : CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
75 :
76 0 : const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
77 0 : DEFAULT_MAX_RAW_TX_FEE_RATE :
78 0 : CFeeRate(AmountFromValue(request.params[1]));
79 :
80 0 : int64_t virtual_size = GetVirtualTransactionSize(*tx);
81 0 : CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
82 :
83 0 : bool bypass_limits = false;
84 0 : if (!request.params[3].isNull()) bypass_limits = request.params[3].get_bool();
85 0 : bilingual_str err_string;
86 0 : AssertLockNotHeld(cs_main);
87 0 : NodeContext& node = EnsureAnyNodeContext(request.context);
88 0 : const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee, /*relay=*/true, /*wait_callback=*/true, bypass_limits);
89 0 : if (TransactionError::OK != err) {
90 0 : throw JSONRPCTransactionError(err, err_string.original);
91 : }
92 :
93 0 : return tx->GetHash().GetHex();
94 4 : },
95 : };
96 0 : }
97 :
98 92 : static RPCHelpMan testmempoolaccept()
99 : {
100 184 : return RPCHelpMan{"testmempoolaccept",
101 : "\nReturns result of mempool acceptance tests indicating if raw transaction (serialized, hex-encoded) would be accepted by mempool.\n"
102 : "\nIf multiple transactions are passed in, parents must come before children and package policies apply: the transactions cannot conflict with any mempool transactions or each other.\n"
103 : "\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n"
104 92 : "\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n"
105 : "\nThis checks if transactions violate the consensus or policy rules.\n"
106 : "\nSee sendrawtransaction call.\n",
107 276 : {
108 184 : {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.",
109 184 : {
110 92 : {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
111 : },
112 : },
113 184 : {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
114 92 : "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"},
115 : },
116 92 : RPCResult{
117 92 : RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n"
118 : "Returns results for each transaction in the same order they were passed in.\n"
119 : "Transactions that cannot be fully validated due to failures in other transactions will not contain an 'allowed' result.\n",
120 184 : {
121 184 : {RPCResult::Type::OBJ, "", "",
122 644 : {
123 92 : {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
124 92 : {RPCResult::Type::STR, "package-error", /*optional=*/true, "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."},
125 92 : {RPCResult::Type::BOOL, "allowed", /*optional=*/true, "Whether this tx would be accepted to the mempool and pass client-specified maxfeerate. "
126 : "If not present, the tx was not fully validated due to a failure in another tx in the list."},
127 92 : {RPCResult::Type::NUM, "vsize", /*optional=*/true, "Transaction size."},
128 184 : {RPCResult::Type::OBJ, "fees", /*optional=*/true, "Transaction fees (only present if 'allowed' is true)",
129 184 : {
130 92 : {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT},
131 : }},
132 92 : {RPCResult::Type::STR, "reject-reason", /*optional=*/true, "Rejection string (only present when 'allowed' is false)"},
133 : }},
134 : }
135 : },
136 92 : RPCExamples{
137 : "\nCreate a transaction\n"
138 92 : + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
139 : "Sign the transaction, and get back the hex\n"
140 92 : + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
141 : "\nTest acceptance of the transaction (signed hex)\n"
142 92 : + HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") +
143 : "\nAs a JSON-RPC call\n"
144 92 : + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")
145 : },
146 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
147 : {
148 0 : RPCTypeCheck(request.params, {
149 0 : UniValue::VARR,
150 0 : UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
151 : });
152 0 : const UniValue raw_transactions = request.params[0].get_array();
153 0 : if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) {
154 0 : throw JSONRPCError(RPC_INVALID_PARAMETER,
155 0 : "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions.");
156 : }
157 :
158 0 : const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
159 0 : DEFAULT_MAX_RAW_TX_FEE_RATE :
160 0 : CFeeRate(AmountFromValue(request.params[1]));
161 :
162 0 : std::vector<CTransactionRef> txns;
163 0 : txns.reserve(raw_transactions.size());
164 0 : for (const auto& rawtx : raw_transactions.getValues()) {
165 0 : CMutableTransaction mtx;
166 0 : if (!DecodeHexTx(mtx, rawtx.get_str())) {
167 0 : throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
168 0 : "TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input.");
169 : }
170 0 : txns.emplace_back(MakeTransactionRef(std::move(mtx)));
171 0 : }
172 :
173 0 : NodeContext& node = EnsureAnyNodeContext(request.context);
174 0 : CTxMemPool& mempool = EnsureMemPool(node);
175 0 : ChainstateManager& chainman = EnsureChainman(node);
176 0 : CChainState& chainstate = chainman.ActiveChainstate();
177 0 : const PackageMempoolAcceptResult package_result = [&] {
178 0 : LOCK(::cs_main);
179 0 : if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true);
180 0 : return PackageMempoolAcceptResult(txns[0]->GetHash(),
181 0 : chainman.ProcessTransaction(txns[0], /*test_accept=*/true));
182 0 : }();
183 :
184 0 : UniValue rpc_result(UniValue::VARR);
185 : // We will check transaction fees while we iterate through txns in order. If any transaction fee
186 : // exceeds maxfeerate, we will leave the rest of the validation results blank, because it
187 : // doesn't make sense to return a validation result for a transaction if its ancestor(s) would
188 : // not be submitted.
189 0 : bool exit_early{false};
190 0 : for (const auto& tx : txns) {
191 0 : UniValue result_inner(UniValue::VOBJ);
192 0 : result_inner.pushKV("txid", tx->GetHash().GetHex());
193 0 : if (package_result.m_state.GetResult() == PackageValidationResult::PCKG_POLICY) {
194 0 : result_inner.pushKV("package-error", package_result.m_state.GetRejectReason());
195 0 : }
196 0 : auto it = package_result.m_tx_results.find(tx->GetHash());
197 0 : if (exit_early || it == package_result.m_tx_results.end()) {
198 : // Validation unfinished. Just return the txid.
199 0 : rpc_result.push_back(result_inner);
200 0 : continue;
201 : }
202 0 : const auto& tx_result = it->second;
203 : // Package testmempoolaccept doesn't allow transactions to already be in the mempool.
204 0 : CHECK_NONFATAL(tx_result.m_result_type != MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
205 0 : if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
206 0 : const CAmount fee = tx_result.m_base_fees.value();
207 : // Check that fee does not exceed maximum fee
208 0 : const int64_t virtual_size = tx_result.m_vsize.value();
209 0 : const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
210 0 : if (max_raw_tx_fee && fee > max_raw_tx_fee) {
211 0 : result_inner.pushKV("allowed", false);
212 0 : result_inner.pushKV("reject-reason", "max-fee-exceeded");
213 0 : exit_early = true;
214 0 : } else {
215 : // Only return the fee and vsize if the transaction would pass ATMP.
216 : // These can be used to calculate the feerate.
217 0 : result_inner.pushKV("allowed", true);
218 0 : result_inner.pushKV("vsize", virtual_size);
219 0 : UniValue fees(UniValue::VOBJ);
220 0 : fees.pushKV("base", ValueFromAmount(fee));
221 0 : result_inner.pushKV("fees", fees);
222 0 : }
223 0 : } else {
224 0 : result_inner.pushKV("allowed", false);
225 0 : const TxValidationState state = tx_result.m_state;
226 0 : if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
227 0 : result_inner.pushKV("reject-reason", "missing-inputs");
228 0 : } else {
229 0 : result_inner.pushKV("reject-reason", state.GetRejectReason());
230 : }
231 0 : }
232 0 : rpc_result.push_back(result_inner);
233 0 : }
234 0 : return rpc_result;
235 0 : },
236 : };
237 0 : }
238 :
239 368 : static std::vector<RPCResult> MempoolEntryDescription()
240 : {
241 6256 : return {
242 368 : RPCResult{RPCResult::Type::NUM, "vsize", "Transaction size."},
243 736 : RPCResult{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true,
244 368 : "transaction fee, denominated in " + CURRENCY_UNIT + " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
245 736 : RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee", /*optional=*/true,
246 368 : "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT +
247 : " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
248 368 : RPCResult{RPCResult::Type::NUM_TIME, "time", "local time transaction entered pool in " + UNIX_EPOCH_TIME},
249 368 : RPCResult{RPCResult::Type::NUM, "height", "block height when transaction entered pool"},
250 368 : RPCResult{RPCResult::Type::NUM, "descendantcount", "number of in-mempool descendant transactions (including this one)"},
251 368 : RPCResult{RPCResult::Type::NUM, "descendantsize", "size of in-mempool descendants (including this one)"},
252 736 : RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees", /*optional=*/true,
253 368 : "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " +
254 368 : CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
255 368 : RPCResult{RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"},
256 368 : RPCResult{RPCResult::Type::NUM, "ancestorsize", "size of in-mempool ancestors (including this one)"},
257 736 : RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees", /*optional=*/true,
258 368 : "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " +
259 368 : CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
260 736 : RPCResult{RPCResult::Type::OBJ, "fees", "",
261 1840 : {
262 368 : RPCResult{RPCResult::Type::STR_AMOUNT, "base", "transaction fee, denominated in " + CURRENCY_UNIT},
263 368 : RPCResult{RPCResult::Type::STR_AMOUNT, "modified", "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
264 368 : 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},
265 368 : 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},
266 : }},
267 736 : RPCResult{RPCResult::Type::ARR, "depends", "unconfirmed transactions used as inputs for this transaction",
268 368 : {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "parent transaction id"}}},
269 736 : RPCResult{RPCResult::Type::ARR, "spentby", "unconfirmed transactions spending outputs from this transaction",
270 368 : {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}},
271 368 : RPCResult{RPCResult::Type::BOOL, "instantsend", "True if this transaction was locked via InstantSend"},
272 368 : RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"}
273 : };
274 0 : }
275 :
276 0 : static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e, const llmq::CInstantSendManager* isman) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
277 : {
278 0 : AssertLockHeld(pool.cs);
279 :
280 0 : info.pushKV("vsize", (int)e.GetTxSize());
281 : // TODO: top-level fee fields are deprecated. deprecated_fee_fields_enabled blocks should be removed in v24
282 0 : const bool deprecated_fee_fields_enabled{IsDeprecatedRPCEnabled("fees")};
283 0 : if (deprecated_fee_fields_enabled) {
284 0 : info.pushKV("fee", ValueFromAmount(e.GetFee()));
285 0 : info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee()));
286 0 : }
287 0 : info.pushKV("time", count_seconds(e.GetTime()));
288 0 : info.pushKV("height", (int)e.GetHeight());
289 0 : info.pushKV("descendantcount", e.GetCountWithDescendants());
290 0 : info.pushKV("descendantsize", e.GetSizeWithDescendants());
291 0 : if (deprecated_fee_fields_enabled) {
292 0 : info.pushKV("descendantfees", e.GetModFeesWithDescendants());
293 0 : }
294 0 : info.pushKV("ancestorcount", e.GetCountWithAncestors());
295 0 : info.pushKV("ancestorsize", e.GetSizeWithAncestors());
296 0 : if (deprecated_fee_fields_enabled) {
297 0 : info.pushKV("ancestorfees", e.GetModFeesWithAncestors());
298 0 : }
299 :
300 0 : UniValue fees(UniValue::VOBJ);
301 0 : fees.pushKV("base", ValueFromAmount(e.GetFee()));
302 0 : fees.pushKV("modified", ValueFromAmount(e.GetModifiedFee()));
303 0 : fees.pushKV("ancestor", ValueFromAmount(e.GetModFeesWithAncestors()));
304 0 : fees.pushKV("descendant", ValueFromAmount(e.GetModFeesWithDescendants()));
305 0 : info.pushKV("fees", fees);
306 :
307 0 : const CTransaction& tx = e.GetTx();
308 0 : std::set<std::string> setDepends;
309 0 : for (const CTxIn& txin : tx.vin)
310 : {
311 0 : if (pool.exists(txin.prevout.hash))
312 0 : setDepends.insert(txin.prevout.hash.ToString());
313 : }
314 :
315 0 : UniValue depends(UniValue::VARR);
316 0 : for (const std::string& dep : setDepends)
317 : {
318 0 : depends.push_back(dep);
319 : }
320 :
321 0 : info.pushKV("depends", depends);
322 :
323 0 : UniValue spent(UniValue::VARR);
324 0 : const CTxMemPool::txiter& it = pool.mapTx.find(tx.GetHash());
325 0 : const CTxMemPoolEntry::Children& children = it->GetMemPoolChildrenConst();
326 0 : for (const CTxMemPoolEntry& child : children) {
327 0 : spent.push_back(child.GetTx().GetHash().ToString());
328 : }
329 :
330 0 : info.pushKV("spentby", spent);
331 0 : info.pushKV("instantlock", isman ? util::to_string(isman->IsLocked(tx.GetHash())) : "unknown");
332 0 : info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetHash()));
333 0 : }
334 :
335 0 : UniValue MempoolToJSON(const CTxMemPool& pool, const llmq::CInstantSendManager* isman, bool verbose, bool include_mempool_sequence)
336 : {
337 0 : if (verbose) {
338 0 : if (include_mempool_sequence) {
339 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Verbose results cannot contain mempool sequence values.");
340 : }
341 0 : LOCK(pool.cs);
342 0 : UniValue o(UniValue::VOBJ);
343 0 : for (const CTxMemPoolEntry& e : pool.mapTx) {
344 0 : const uint256& hash = e.GetTx().GetHash();
345 0 : UniValue info(UniValue::VOBJ);
346 0 : entryToJSON(pool, info, e, isman);
347 : // Mempool has unique entries so there is no advantage in using
348 : // UniValue::pushKV, which checks if the key already exists in O(N).
349 : // UniValue::__pushKV is used instead which currently is O(1).
350 0 : o.__pushKV(hash.ToString(), info);
351 0 : }
352 0 : return o;
353 0 : } else {
354 : uint64_t mempool_sequence;
355 0 : std::vector<uint256> vtxid;
356 : {
357 0 : LOCK(pool.cs);
358 0 : pool.queryHashes(vtxid);
359 0 : mempool_sequence = pool.GetSequence();
360 0 : }
361 0 : UniValue a(UniValue::VARR);
362 0 : for (const uint256& hash : vtxid)
363 0 : a.push_back(hash.ToString());
364 :
365 0 : if (!include_mempool_sequence) {
366 0 : return a;
367 : } else {
368 0 : UniValue o(UniValue::VOBJ);
369 0 : o.pushKV("txids", a);
370 0 : o.pushKV("mempool_sequence", mempool_sequence);
371 0 : return o;
372 0 : }
373 0 : }
374 0 : }
375 :
376 92 : static RPCHelpMan getrawmempool()
377 : {
378 184 : return RPCHelpMan{"getrawmempool",
379 92 : "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n"
380 : "\nHint: use getmempoolentry to fetch a specific transaction from the mempool.\n",
381 276 : {
382 92 : {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
383 92 : {"mempool_sequence", RPCArg::Type::BOOL, RPCArg::Default{false}, "If verbose=false, returns a json object with transaction list and mempool sequence number attached."},
384 : },
385 368 : {
386 184 : RPCResult{"for verbose = false",
387 92 : RPCResult::Type::ARR, "", "",
388 184 : {
389 92 : {RPCResult::Type::STR_HEX, "", "The transaction id"},
390 : }},
391 184 : RPCResult{"for verbose = true",
392 92 : RPCResult::Type::OBJ_DYN, "", "",
393 184 : {
394 92 : {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
395 : }},
396 184 : RPCResult{"for verbose = false and mempool_sequence = true",
397 92 : RPCResult::Type::OBJ, "", "",
398 276 : {
399 184 : {RPCResult::Type::ARR, "txids", "",
400 184 : {
401 92 : {RPCResult::Type::STR_HEX, "", "The transaction id"},
402 : }},
403 92 : {RPCResult::Type::NUM, "mempool_sequence", "The mempool sequence value."},
404 : }},
405 : },
406 92 : RPCExamples{
407 92 : HelpExampleCli("getrawmempool", "true")
408 92 : + HelpExampleRpc("getrawmempool", "true")
409 : },
410 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
411 : {
412 0 : bool fVerbose = false;
413 0 : if (!request.params[0].isNull())
414 0 : fVerbose = request.params[0].get_bool();
415 :
416 0 : bool include_mempool_sequence = false;
417 0 : if (!request.params[1].isNull()) {
418 0 : include_mempool_sequence = request.params[1].get_bool();
419 0 : }
420 :
421 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
422 0 : const CTxMemPool& mempool = EnsureMemPool(node);
423 0 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
424 :
425 0 : return MempoolToJSON(mempool, llmq_ctx.isman.get(), fVerbose, include_mempool_sequence);
426 : },
427 : };
428 0 : }
429 :
430 92 : static RPCHelpMan getmempoolancestors()
431 : {
432 184 : return RPCHelpMan{"getmempoolancestors",
433 92 : "\nIf txid is in the mempool, returns all in-mempool ancestors.\n",
434 276 : {
435 92 : {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
436 92 : {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
437 : },
438 276 : {
439 184 : RPCResult{"for verbose = false",
440 92 : RPCResult::Type::ARR, "", "",
441 92 : {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool ancestor transaction"}}},
442 184 : RPCResult{"for verbose = true",
443 92 : RPCResult::Type::OBJ_DYN, "", "",
444 184 : {
445 92 : {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
446 : }},
447 : },
448 92 : RPCExamples{
449 92 : HelpExampleCli("getmempoolancestors", "\"mytxid\"")
450 92 : + HelpExampleRpc("getmempoolancestors", "\"mytxid\"")
451 : },
452 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
453 : {
454 0 : bool fVerbose = false;
455 0 : if (!request.params[1].isNull())
456 0 : fVerbose = request.params[1].get_bool();
457 :
458 0 : uint256 hash(ParseHashV(request.params[0], "parameter 1"));
459 :
460 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
461 :
462 0 : const CTxMemPool& mempool = EnsureMemPool(node);
463 0 : LOCK(mempool.cs);
464 :
465 0 : CTxMemPool::txiter it = mempool.mapTx.find(hash);
466 0 : if (it == mempool.mapTx.end()) {
467 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
468 : }
469 :
470 0 : CTxMemPool::setEntries setAncestors;
471 0 : uint64_t noLimit = std::numeric_limits<uint64_t>::max();
472 0 : std::string dummy;
473 0 : mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false);
474 :
475 0 : if (!fVerbose) {
476 0 : UniValue o(UniValue::VARR);
477 0 : for (CTxMemPool::txiter ancestorIt : setAncestors) {
478 0 : o.push_back(ancestorIt->GetTx().GetHash().ToString());
479 : }
480 0 : return o;
481 0 : } else {
482 0 : UniValue o(UniValue::VOBJ);
483 0 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
484 0 : for (CTxMemPool::txiter ancestorIt : setAncestors) {
485 0 : const CTxMemPoolEntry &e = *ancestorIt;
486 0 : const uint256& _hash = e.GetTx().GetHash();
487 0 : UniValue info(UniValue::VOBJ);
488 0 : entryToJSON(mempool, info, e, llmq_ctx.isman.get());
489 0 : o.pushKV(_hash.ToString(), info);
490 0 : }
491 0 : return o;
492 0 : }
493 0 : },
494 : };
495 0 : }
496 :
497 92 : static RPCHelpMan getmempooldescendants()
498 : {
499 184 : return RPCHelpMan{"getmempooldescendants",
500 92 : "\nIf txid is in the mempool, returns all in-mempool descendants.\n",
501 276 : {
502 92 : {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
503 92 : {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
504 : },
505 276 : {
506 184 : RPCResult{"for verbose = false",
507 92 : RPCResult::Type::ARR, "", "",
508 92 : {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool descendant transaction"}}},
509 184 : RPCResult{"for verbose = true",
510 92 : RPCResult::Type::OBJ_DYN, "", "",
511 184 : {
512 92 : {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
513 : }},
514 : },
515 92 : RPCExamples{
516 92 : HelpExampleCli("getmempooldescendants", "\"mytxid\"")
517 92 : + HelpExampleRpc("getmempooldescendants", "\"mytxid\"")
518 : },
519 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
520 : {
521 0 : bool fVerbose = false;
522 0 : if (!request.params[1].isNull())
523 0 : fVerbose = request.params[1].get_bool();
524 :
525 0 : uint256 hash(ParseHashV(request.params[0], "parameter 1"));
526 :
527 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
528 :
529 0 : const CTxMemPool& mempool = EnsureMemPool(node);
530 0 : LOCK(mempool.cs);
531 :
532 0 : CTxMemPool::txiter it = mempool.mapTx.find(hash);
533 0 : if (it == mempool.mapTx.end()) {
534 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
535 : }
536 :
537 0 : CTxMemPool::setEntries setDescendants;
538 0 : mempool.CalculateDescendants(it, setDescendants);
539 : // CTxMemPool::CalculateDescendants will include the given tx
540 0 : setDescendants.erase(it);
541 :
542 0 : if (!fVerbose) {
543 0 : UniValue o(UniValue::VARR);
544 0 : for (CTxMemPool::txiter descendantIt : setDescendants) {
545 0 : o.push_back(descendantIt->GetTx().GetHash().ToString());
546 : }
547 :
548 0 : return o;
549 0 : } else {
550 0 : UniValue o(UniValue::VOBJ);
551 0 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
552 0 : for (CTxMemPool::txiter descendantIt : setDescendants) {
553 0 : const CTxMemPoolEntry &e = *descendantIt;
554 0 : const uint256& _hash = e.GetTx().GetHash();
555 0 : UniValue info(UniValue::VOBJ);
556 0 : entryToJSON(mempool, info, e, llmq_ctx.isman.get());
557 0 : o.pushKV(_hash.ToString(), info);
558 0 : }
559 0 : return o;
560 0 : }
561 0 : },
562 : };
563 0 : }
564 :
565 92 : static RPCHelpMan getmempoolentry()
566 : {
567 184 : return RPCHelpMan{"getmempoolentry",
568 92 : "\nReturns mempool data for given transaction\n",
569 184 : {
570 92 : {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
571 : },
572 92 : RPCResult{
573 92 : RPCResult::Type::OBJ, "", "", MempoolEntryDescription()},
574 92 : RPCExamples{
575 92 : HelpExampleCli("getmempoolentry", "\"mytxid\"")
576 92 : + HelpExampleRpc("getmempoolentry", "\"mytxid\"")
577 : },
578 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
579 : {
580 :
581 0 : uint256 hash(ParseHashV(request.params[0], "parameter 1"));
582 :
583 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
584 :
585 0 : const CTxMemPool& mempool = EnsureMemPool(node);
586 0 : LOCK(mempool.cs);
587 :
588 0 : CTxMemPool::txiter it = mempool.mapTx.find(hash);
589 0 : if (it == mempool.mapTx.end()) {
590 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
591 : }
592 :
593 0 : const CTxMemPoolEntry &e = *it;
594 0 : UniValue info(UniValue::VOBJ);
595 0 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
596 0 : entryToJSON(mempool, info, e, llmq_ctx.isman.get());
597 0 : return info;
598 0 : },
599 : };
600 0 : }
601 :
602 92 : static RPCHelpMan gettxspendingprevout()
603 : {
604 184 : return RPCHelpMan{"gettxspendingprevout",
605 92 : "Scans the mempool to find transactions spending any of the given outputs",
606 184 : {
607 184 : {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The transaction outputs that we want to check, and within each, the txid (string) vout (numeric).",
608 184 : {
609 184 : {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
610 276 : {
611 92 : {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
612 92 : {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
613 : },
614 : },
615 : },
616 : },
617 : },
618 92 : RPCResult{
619 92 : RPCResult::Type::ARR, "", "",
620 184 : {
621 184 : {RPCResult::Type::OBJ, "", "",
622 368 : {
623 92 : {RPCResult::Type::STR_HEX, "txid", "the transaction id of the checked output"},
624 92 : {RPCResult::Type::NUM, "vout", "the vout value of the checked output"},
625 92 : {RPCResult::Type::STR_HEX, "spendingtxid", /*optional=*/true, "the transaction id of the mempool transaction spending this output (omitted if unspent)"},
626 : }},
627 : }
628 : },
629 92 : RPCExamples{
630 92 : HelpExampleCli("gettxspendingprevout", "\"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":3}]\"")
631 92 : + HelpExampleRpc("gettxspendingprevout", "\"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":3}]\"")
632 : },
633 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
634 : {
635 0 : const UniValue& output_params = request.params[0].get_array();
636 0 : if (output_params.empty()) {
637 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, outputs are missing");
638 : }
639 :
640 0 : std::vector<COutPoint> prevouts;
641 0 : prevouts.reserve(output_params.size());
642 :
643 0 : for (unsigned int idx = 0; idx < output_params.size(); idx++) {
644 0 : const UniValue& o = output_params[idx].get_obj();
645 :
646 0 : RPCTypeCheckObj(o,
647 0 : {
648 0 : {"txid", UniValueType(UniValue::VSTR)},
649 0 : {"vout", UniValueType(UniValue::VNUM)},
650 : }, /*fAllowNull=*/false, /*fStrict=*/true);
651 :
652 0 : const uint256 txid(ParseHashO(o, "txid"));
653 0 : const int nOutput{o.find_value("vout").getInt<int>()};
654 0 : if (nOutput < 0) {
655 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative");
656 : }
657 :
658 0 : prevouts.emplace_back(txid, nOutput);
659 0 : }
660 :
661 0 : const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
662 0 : LOCK(mempool.cs);
663 :
664 0 : UniValue result{UniValue::VARR};
665 :
666 0 : for (const COutPoint& prevout : prevouts) {
667 0 : UniValue o(UniValue::VOBJ);
668 0 : o.pushKV("txid", prevout.hash.ToString());
669 0 : o.pushKV("vout", (uint64_t)prevout.n);
670 :
671 0 : const CTransaction* spendingTx = mempool.GetConflictTx(prevout);
672 0 : if (spendingTx != nullptr) {
673 0 : o.pushKV("spendingtxid", spendingTx->GetHash().ToString());
674 0 : }
675 :
676 0 : result.push_back(o);
677 0 : }
678 :
679 0 : return result;
680 0 : },
681 : };
682 0 : }
683 :
684 0 : UniValue MempoolInfoToJSON(const CTxMemPool& pool, const llmq::CInstantSendManager& isman)
685 : {
686 : // Make sure this call is atomic in the pool.
687 0 : LOCK(pool.cs);
688 0 : UniValue ret(UniValue::VOBJ);
689 0 : ret.pushKV("loaded", pool.IsLoaded());
690 0 : ret.pushKV("size", (int64_t)pool.size());
691 0 : ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize());
692 0 : ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage());
693 0 : ret.pushKV("total_fee", ValueFromAmount(pool.GetTotalFee()));
694 0 : int64_t maxmempool{gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000};
695 0 : ret.pushKV("maxmempool", maxmempool);
696 0 : ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK()));
697 0 : ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()));
698 0 : ret.pushKV("instantsendlocks", isman.GetInstantSendLockCount());
699 0 : ret.pushKV("unbroadcastcount", pool.GetUnbroadcastTxs().size());
700 0 : return ret;
701 0 : }
702 :
703 92 : static RPCHelpMan getmempoolinfo()
704 : {
705 184 : return RPCHelpMan{"getmempoolinfo",
706 92 : "\nReturns details on the active state of the TX memory pool.\n",
707 92 : {},
708 92 : RPCResult{
709 92 : RPCResult::Type::OBJ, "", "",
710 1012 : {
711 92 : {RPCResult::Type::BOOL, "loaded", "True if the mempool is fully loaded"},
712 92 : {RPCResult::Type::NUM, "size", "Current tx count"},
713 92 : {RPCResult::Type::NUM, "bytes", "Sum of all transaction sizes"},
714 92 : {RPCResult::Type::NUM, "usage", "Total memory usage for the mempool"},
715 92 : {RPCResult::Type::STR_AMOUNT, "total_fee", "Total fees for the mempool in " + CURRENCY_UNIT + ", ignoring modified fees through prioritisetransaction"},
716 92 : {RPCResult::Type::NUM, "maxmempool", "Maximum memory usage for the mempool"},
717 92 : {RPCResult::Type::STR_AMOUNT, "mempoolminfee", "Minimum fee rate in " + CURRENCY_UNIT + "/kB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee"},
718 92 : {RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"},
719 92 : {RPCResult::Type::NUM, "instantsendlocks", "Number of unconfirmed InstantSend locks"},
720 92 : {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"}
721 : }},
722 92 : RPCExamples{
723 92 : HelpExampleCli("getmempoolinfo", "")
724 92 : + HelpExampleRpc("getmempoolinfo", "")
725 : },
726 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
727 : {
728 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
729 0 : const CTxMemPool& mempool = EnsureMemPool(node);
730 0 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
731 0 : return MempoolInfoToJSON(mempool, *llmq_ctx.isman);
732 : },
733 : };
734 0 : }
735 :
736 92 : static RPCHelpMan savemempool()
737 : {
738 184 : return RPCHelpMan{"savemempool",
739 92 : "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n",
740 92 : {},
741 92 : RPCResult{
742 92 : RPCResult::Type::OBJ, "", "",
743 184 : {
744 92 : {RPCResult::Type::STR, "filename", "the directory and file where the mempool was saved"},
745 : }},
746 92 : RPCExamples{
747 92 : HelpExampleCli("savemempool", "")
748 92 : + HelpExampleRpc("savemempool", "")
749 : },
750 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
751 : {
752 0 : const ArgsManager& args{EnsureAnyArgsman(request.context)};
753 0 : const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
754 :
755 0 : if (!mempool.IsLoaded()) {
756 0 : throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet");
757 : }
758 :
759 0 : if (!DumpMempool(mempool)) {
760 0 : throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk");
761 : }
762 :
763 0 : UniValue ret(UniValue::VOBJ);
764 0 : ret.pushKV("filename", fs::path((args.GetDataDirNet() / "mempool.dat")).utf8string());
765 :
766 0 : return ret;
767 0 : },
768 : };
769 0 : }
770 :
771 92 : static RPCHelpMan submitpackage()
772 : {
773 184 : return RPCHelpMan{"submitpackage",
774 92 : "Submit a package of raw transactions (serialized, hex-encoded) to local node (-regtest only).\n"
775 : "The package will be validated according to consensus and mempool policy rules. If all transactions pass, they will be accepted to mempool.\n"
776 : "This RPC is experimental and the interface may be unstable. Refer to doc/policy/packages.md for documentation on package policies.\n"
777 : "Warning: until package relay is in use, successful submission does not mean the transaction will propagate to other nodes on the network.\n"
778 : "Currently, each transaction is broadcasted individually after submission, which means they must meet other nodes' feerate requirements alone.\n"
779 : ,
780 184 : {
781 184 : {"package", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of raw transactions.",
782 184 : {
783 92 : {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
784 : },
785 : },
786 : },
787 92 : RPCResult{
788 92 : RPCResult::Type::OBJ, "", "",
789 276 : {
790 184 : {RPCResult::Type::OBJ_DYN, "tx-results", "transaction results keyed by txid",
791 184 : {
792 368 : {RPCResult::Type::OBJ, "txid", "transaction txid", {
793 92 : {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
794 92 : {RPCResult::Type::NUM, "size", "Size of transaction in bytes"},
795 184 : {RPCResult::Type::OBJ, "fees", "Transaction fees", {
796 92 : {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT},
797 : }},
798 : }}
799 : }},
800 92 : {RPCResult::Type::STR_AMOUNT, "package-feerate", /*optional=*/true, "package feerate used for feerate checks in " + CURRENCY_UNIT + " per KvB. Excludes transactions which were deduplicated or accepted individually."},
801 : },
802 : },
803 92 : RPCExamples{
804 184 : HelpExampleCli("testmempoolaccept", "[rawtx1, rawtx2]") +
805 92 : HelpExampleCli("submitpackage", "[rawtx1, rawtx2]")
806 : },
807 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
808 : {
809 0 : if (!Params().IsMockableChain()) {
810 0 : throw std::runtime_error("submitpackage is for regression testing (-regtest mode) only");
811 : }
812 0 : RPCTypeCheck(request.params, {
813 0 : UniValue::VARR,
814 : });
815 0 : const UniValue raw_transactions = request.params[0].get_array();
816 0 : if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) {
817 0 : throw JSONRPCError(RPC_INVALID_PARAMETER,
818 0 : "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions.");
819 : }
820 :
821 0 : std::vector<CTransactionRef> txns;
822 0 : txns.reserve(raw_transactions.size());
823 0 : for (const auto& rawtx : raw_transactions.getValues()) {
824 0 : CMutableTransaction mtx;
825 0 : if (!DecodeHexTx(mtx, rawtx.get_str())) {
826 0 : throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
827 0 : "TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input.");
828 : }
829 0 : txns.emplace_back(MakeTransactionRef(std::move(mtx)));
830 0 : }
831 :
832 0 : NodeContext& node = EnsureAnyNodeContext(request.context);
833 0 : CTxMemPool& mempool = EnsureMemPool(node);
834 0 : CChainState& chainstate = EnsureChainman(node).ActiveChainstate();
835 0 : const auto package_result = WITH_LOCK(::cs_main, return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/ false));
836 :
837 : // First catch any errors.
838 0 : switch(package_result.m_state.GetResult()) {
839 0 : case PackageValidationResult::PCKG_RESULT_UNSET: break;
840 : case PackageValidationResult::PCKG_POLICY:
841 : {
842 0 : throw JSONRPCTransactionError(TransactionError::INVALID_PACKAGE,
843 0 : package_result.m_state.GetRejectReason());
844 : }
845 : case PackageValidationResult::PCKG_MEMPOOL_ERROR:
846 : {
847 0 : throw JSONRPCTransactionError(TransactionError::MEMPOOL_ERROR,
848 0 : package_result.m_state.GetRejectReason());
849 : }
850 : case PackageValidationResult::PCKG_TX:
851 : {
852 0 : for (const auto& tx : txns) {
853 0 : auto it = package_result.m_tx_results.find(tx->GetHash());
854 0 : if (it != package_result.m_tx_results.end() && it->second.m_state.IsInvalid()) {
855 0 : throw JSONRPCTransactionError(TransactionError::MEMPOOL_REJECTED,
856 0 : strprintf("%s failed: %s", tx->GetHash().ToString(), it->second.m_state.GetRejectReason()));
857 : }
858 : }
859 : // If a PCKG_TX error was returned, there must have been an invalid transaction.
860 0 : NONFATAL_UNREACHABLE();
861 : }
862 : }
863 0 : for (const auto& tx : txns) {
864 0 : size_t num_submitted{0};
865 0 : bilingual_str err_string;
866 0 : const auto err = BroadcastTransaction(node, tx, err_string, 0, true, true);
867 0 : if (err != TransactionError::OK) {
868 0 : throw JSONRPCTransactionError(err,
869 0 : strprintf("transaction broadcast failed: %s (all transactions were submitted, %d transactions were broadcast successfully)",
870 0 : err_string.original, num_submitted));
871 : }
872 0 : }
873 0 : UniValue rpc_result{UniValue::VOBJ};
874 0 : UniValue tx_result_map{UniValue::VOBJ};
875 0 : for (const auto& tx : txns) {
876 0 : auto it = package_result.m_tx_results.find(tx->GetHash());
877 0 : CHECK_NONFATAL(it != package_result.m_tx_results.end());
878 0 : UniValue result_inner{UniValue::VOBJ};
879 0 : result_inner.pushKV("txid", tx->GetHash().GetHex());
880 0 : if (it->second.m_result_type == MempoolAcceptResult::ResultType::VALID ||
881 0 : it->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY) {
882 0 : result_inner.pushKV("size", int64_t{it->second.m_vsize.value()});
883 0 : UniValue fees(UniValue::VOBJ);
884 0 : fees.pushKV("base", ValueFromAmount(it->second.m_base_fees.value()));
885 0 : result_inner.pushKV("fees", fees);
886 0 : }
887 0 : tx_result_map.pushKV(tx->GetHash().GetHex(), result_inner);
888 0 : }
889 0 : rpc_result.pushKV("tx-results", tx_result_map);
890 0 : if (package_result.m_package_feerate.has_value()) {
891 0 : rpc_result.pushKV("package-feerate", ValueFromAmount(package_result.m_package_feerate.value().GetFeePerK()));
892 0 : }
893 0 : return rpc_result;
894 0 : },
895 : };
896 0 : }
897 :
898 178 : void RegisterMempoolRPCCommands(CRPCTable& t)
899 : {
900 638 : static const CRPCCommand commands[]{
901 46 : {"rawtransactions", &sendrawtransaction},
902 46 : {"rawtransactions", &testmempoolaccept},
903 46 : {"blockchain", &getmempoolancestors},
904 46 : {"blockchain", &getmempooldescendants},
905 46 : {"blockchain", &getmempoolentry},
906 46 : {"blockchain", &gettxspendingprevout},
907 46 : {"blockchain", &getmempoolinfo},
908 46 : {"blockchain", &getrawmempool},
909 46 : {"blockchain", &savemempool},
910 46 : {"hidden", &submitpackage},
911 : };
912 1958 : for (const auto& c : commands) {
913 1780 : t.appendCommand(c.name, &c);
914 : }
915 178 : }
|