|
14 | 14 | #include <rpc/util.h>
|
15 | 15 | #include <txmempool.h>
|
16 | 16 | #include <univalue.h>
|
| 17 | +#include <util/moneystr.h> |
17 | 18 | #include <validation.h>
|
18 | 19 |
|
19 |
| -static std::vector<RPCResult> MempoolEntryDescription() { return { |
| 20 | +using node::DEFAULT_MAX_RAW_TX_FEE_RATE; |
| 21 | +using node::NodeContext; |
| 22 | + |
| 23 | +static RPCHelpMan sendrawtransaction() |
| 24 | +{ |
| 25 | + return RPCHelpMan{"sendrawtransaction", |
| 26 | + "\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n" |
| 27 | + "\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n" |
| 28 | + "for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n" |
| 29 | + "nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n" |
| 30 | + "\nA specific exception, RPC_TRANSACTION_ALREADY_IN_CHAIN, may throw if the transaction cannot be added to the mempool.\n" |
| 31 | + "\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n", |
| 32 | + { |
| 33 | + {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"}, |
| 34 | + {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())}, |
| 35 | + "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + |
| 36 | + "/kvB.\nSet to 0 to accept any fee rate.\n"}, |
| 37 | + }, |
| 38 | + RPCResult{ |
| 39 | + RPCResult::Type::STR_HEX, "", "The transaction hash in hex" |
| 40 | + }, |
| 41 | + RPCExamples{ |
| 42 | + "\nCreate a transaction\n" |
| 43 | + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") + |
| 44 | + "Sign the transaction, and get back the hex\n" |
| 45 | + + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + |
| 46 | + "\nSend the transaction (signed hex)\n" |
| 47 | + + HelpExampleCli("sendrawtransaction", "\"signedhex\"") + |
| 48 | + "\nAs a JSON-RPC call\n" |
| 49 | + + HelpExampleRpc("sendrawtransaction", "\"signedhex\"") |
| 50 | + }, |
| 51 | + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue |
| 52 | + { |
| 53 | + RPCTypeCheck(request.params, { |
| 54 | + UniValue::VSTR, |
| 55 | + UniValueType(), // VNUM or VSTR, checked inside AmountFromValue() |
| 56 | + }); |
| 57 | + |
| 58 | + CMutableTransaction mtx; |
| 59 | + if (!DecodeHexTx(mtx, request.params[0].get_str())) { |
| 60 | + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input."); |
| 61 | + } |
| 62 | + CTransactionRef tx(MakeTransactionRef(std::move(mtx))); |
| 63 | + |
| 64 | + const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ? |
| 65 | + DEFAULT_MAX_RAW_TX_FEE_RATE : |
| 66 | + CFeeRate(AmountFromValue(request.params[1])); |
| 67 | + |
| 68 | + int64_t virtual_size = GetVirtualTransactionSize(*tx); |
| 69 | + CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); |
| 70 | + |
| 71 | + std::string err_string; |
| 72 | + AssertLockNotHeld(cs_main); |
| 73 | + NodeContext& node = EnsureAnyNodeContext(request.context); |
| 74 | + const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true); |
| 75 | + if (TransactionError::OK != err) { |
| 76 | + throw JSONRPCTransactionError(err, err_string); |
| 77 | + } |
| 78 | + |
| 79 | + return tx->GetHash().GetHex(); |
| 80 | + }, |
| 81 | + }; |
| 82 | +} |
| 83 | + |
| 84 | +static RPCHelpMan testmempoolaccept() |
| 85 | +{ |
| 86 | + return RPCHelpMan{"testmempoolaccept", |
| 87 | + "\nReturns result of mempool acceptance tests indicating if raw transaction(s) (serialized, hex-encoded) would be accepted by mempool.\n" |
| 88 | + "\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" |
| 89 | + "\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n" |
| 90 | + "\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n" |
| 91 | + "\nThis checks if transactions violate the consensus or policy rules.\n" |
| 92 | + "\nSee sendrawtransaction call.\n", |
| 93 | + { |
| 94 | + {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.", |
| 95 | + { |
| 96 | + {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""}, |
| 97 | + }, |
| 98 | + }, |
| 99 | + {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())}, |
| 100 | + "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kvB\n"}, |
| 101 | + }, |
| 102 | + RPCResult{ |
| 103 | + RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n" |
| 104 | + "Returns results for each transaction in the same order they were passed in.\n" |
| 105 | + "Transactions that cannot be fully validated due to failures in other transactions will not contain an 'allowed' result.\n", |
| 106 | + { |
| 107 | + {RPCResult::Type::OBJ, "", "", |
| 108 | + { |
| 109 | + {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"}, |
| 110 | + {RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"}, |
| 111 | + {RPCResult::Type::STR, "package-error", /*optional=*/true, "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."}, |
| 112 | + {RPCResult::Type::BOOL, "allowed", /*optional=*/true, "Whether this tx would be accepted to the mempool and pass client-specified maxfeerate. " |
| 113 | + "If not present, the tx was not fully validated due to a failure in another tx in the list."}, |
| 114 | + {RPCResult::Type::NUM, "vsize", /*optional=*/true, "Virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted (only present when 'allowed' is true)"}, |
| 115 | + {RPCResult::Type::OBJ, "fees", /*optional=*/true, "Transaction fees (only present if 'allowed' is true)", |
| 116 | + { |
| 117 | + {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT}, |
| 118 | + }}, |
| 119 | + {RPCResult::Type::STR, "reject-reason", /*optional=*/true, "Rejection string (only present when 'allowed' is false)"}, |
| 120 | + }}, |
| 121 | + } |
| 122 | + }, |
| 123 | + RPCExamples{ |
| 124 | + "\nCreate a transaction\n" |
| 125 | + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") + |
| 126 | + "Sign the transaction, and get back the hex\n" |
| 127 | + + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + |
| 128 | + "\nTest acceptance of the transaction (signed hex)\n" |
| 129 | + + HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") + |
| 130 | + "\nAs a JSON-RPC call\n" |
| 131 | + + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]") |
| 132 | + }, |
| 133 | + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue |
| 134 | + { |
| 135 | + RPCTypeCheck(request.params, { |
| 136 | + UniValue::VARR, |
| 137 | + UniValueType(), // VNUM or VSTR, checked inside AmountFromValue() |
| 138 | + }); |
| 139 | + const UniValue raw_transactions = request.params[0].get_array(); |
| 140 | + if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) { |
| 141 | + throw JSONRPCError(RPC_INVALID_PARAMETER, |
| 142 | + "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions."); |
| 143 | + } |
| 144 | + |
| 145 | + const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ? |
| 146 | + DEFAULT_MAX_RAW_TX_FEE_RATE : |
| 147 | + CFeeRate(AmountFromValue(request.params[1])); |
| 148 | + |
| 149 | + std::vector<CTransactionRef> txns; |
| 150 | + txns.reserve(raw_transactions.size()); |
| 151 | + for (const auto& rawtx : raw_transactions.getValues()) { |
| 152 | + CMutableTransaction mtx; |
| 153 | + if (!DecodeHexTx(mtx, rawtx.get_str())) { |
| 154 | + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, |
| 155 | + "TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input."); |
| 156 | + } |
| 157 | + txns.emplace_back(MakeTransactionRef(std::move(mtx))); |
| 158 | + } |
| 159 | + |
| 160 | + NodeContext& node = EnsureAnyNodeContext(request.context); |
| 161 | + CTxMemPool& mempool = EnsureMemPool(node); |
| 162 | + ChainstateManager& chainman = EnsureChainman(node); |
| 163 | + CChainState& chainstate = chainman.ActiveChainstate(); |
| 164 | + const PackageMempoolAcceptResult package_result = [&] { |
| 165 | + LOCK(::cs_main); |
| 166 | + if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /* test_accept */ true); |
| 167 | + return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(), |
| 168 | + chainman.ProcessTransaction(txns[0], /*test_accept=*/true)); |
| 169 | + }(); |
| 170 | + |
| 171 | + UniValue rpc_result(UniValue::VARR); |
| 172 | + // We will check transaction fees while we iterate through txns in order. If any transaction fee |
| 173 | + // exceeds maxfeerate, we will leave the rest of the validation results blank, because it |
| 174 | + // doesn't make sense to return a validation result for a transaction if its ancestor(s) would |
| 175 | + // not be submitted. |
| 176 | + bool exit_early{false}; |
| 177 | + for (const auto& tx : txns) { |
| 178 | + UniValue result_inner(UniValue::VOBJ); |
| 179 | + result_inner.pushKV("txid", tx->GetHash().GetHex()); |
| 180 | + result_inner.pushKV("wtxid", tx->GetWitnessHash().GetHex()); |
| 181 | + if (package_result.m_state.GetResult() == PackageValidationResult::PCKG_POLICY) { |
| 182 | + result_inner.pushKV("package-error", package_result.m_state.GetRejectReason()); |
| 183 | + } |
| 184 | + auto it = package_result.m_tx_results.find(tx->GetWitnessHash()); |
| 185 | + if (exit_early || it == package_result.m_tx_results.end()) { |
| 186 | + // Validation unfinished. Just return the txid and wtxid. |
| 187 | + rpc_result.push_back(result_inner); |
| 188 | + continue; |
| 189 | + } |
| 190 | + const auto& tx_result = it->second; |
| 191 | + // Package testmempoolaccept doesn't allow transactions to already be in the mempool. |
| 192 | + CHECK_NONFATAL(tx_result.m_result_type != MempoolAcceptResult::ResultType::MEMPOOL_ENTRY); |
| 193 | + if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) { |
| 194 | + const CAmount fee = tx_result.m_base_fees.value(); |
| 195 | + // Check that fee does not exceed maximum fee |
| 196 | + const int64_t virtual_size = tx_result.m_vsize.value(); |
| 197 | + const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); |
| 198 | + if (max_raw_tx_fee && fee > max_raw_tx_fee) { |
| 199 | + result_inner.pushKV("allowed", false); |
| 200 | + result_inner.pushKV("reject-reason", "max-fee-exceeded"); |
| 201 | + exit_early = true; |
| 202 | + } else { |
| 203 | + // Only return the fee and vsize if the transaction would pass ATMP. |
| 204 | + // These can be used to calculate the feerate. |
| 205 | + result_inner.pushKV("allowed", true); |
| 206 | + result_inner.pushKV("vsize", virtual_size); |
| 207 | + UniValue fees(UniValue::VOBJ); |
| 208 | + fees.pushKV("base", ValueFromAmount(fee)); |
| 209 | + result_inner.pushKV("fees", fees); |
| 210 | + } |
| 211 | + } else { |
| 212 | + result_inner.pushKV("allowed", false); |
| 213 | + const TxValidationState state = tx_result.m_state; |
| 214 | + if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) { |
| 215 | + result_inner.pushKV("reject-reason", "missing-inputs"); |
| 216 | + } else { |
| 217 | + result_inner.pushKV("reject-reason", state.GetRejectReason()); |
| 218 | + } |
| 219 | + } |
| 220 | + rpc_result.push_back(result_inner); |
| 221 | + } |
| 222 | + return rpc_result; |
| 223 | + }, |
| 224 | + }; |
| 225 | +} |
| 226 | + |
| 227 | +static std::vector<RPCResult> MempoolEntryDescription() |
| 228 | +{ |
| 229 | + return { |
20 | 230 | 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."},
|
21 | 231 | RPCResult{RPCResult::Type::NUM, "weight", "transaction weight as defined in BIP 141."},
|
22 | 232 | RPCResult{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true,
|
@@ -50,7 +260,8 @@ static std::vector<RPCResult> MempoolEntryDescription() { return {
|
50 | 260 | {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}},
|
51 | 261 | RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction could be replaced due to BIP125 (replace-by-fee)"},
|
52 | 262 | RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"},
|
53 |
| -};} |
| 263 | + }; |
| 264 | +} |
54 | 265 |
|
55 | 266 | static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
|
56 | 267 | {
|
@@ -164,7 +375,7 @@ UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose, bool include_mempoo
|
164 | 375 | }
|
165 | 376 | }
|
166 | 377 |
|
167 |
| -RPCHelpMan getrawmempool() |
| 378 | +static RPCHelpMan getrawmempool() |
168 | 379 | {
|
169 | 380 | return RPCHelpMan{"getrawmempool",
|
170 | 381 | "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n"
|
@@ -214,7 +425,7 @@ RPCHelpMan getrawmempool()
|
214 | 425 | };
|
215 | 426 | }
|
216 | 427 |
|
217 |
| -RPCHelpMan getmempoolancestors() |
| 428 | +static RPCHelpMan getmempoolancestors() |
218 | 429 | {
|
219 | 430 | return RPCHelpMan{"getmempoolancestors",
|
220 | 431 | "\nIf txid is in the mempool, returns all in-mempool ancestors.\n",
|
@@ -278,7 +489,7 @@ RPCHelpMan getmempoolancestors()
|
278 | 489 | };
|
279 | 490 | }
|
280 | 491 |
|
281 |
| -RPCHelpMan getmempooldescendants() |
| 492 | +static RPCHelpMan getmempooldescendants() |
282 | 493 | {
|
283 | 494 | return RPCHelpMan{"getmempooldescendants",
|
284 | 495 | "\nIf txid is in the mempool, returns all in-mempool descendants.\n",
|
@@ -343,7 +554,7 @@ RPCHelpMan getmempooldescendants()
|
343 | 554 | };
|
344 | 555 | }
|
345 | 556 |
|
346 |
| -RPCHelpMan getmempoolentry() |
| 557 | +static RPCHelpMan getmempoolentry() |
347 | 558 | {
|
348 | 559 | return RPCHelpMan{"getmempoolentry",
|
349 | 560 | "\nReturns mempool data for given transaction\n",
|
@@ -394,7 +605,7 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool)
|
394 | 605 | return ret;
|
395 | 606 | }
|
396 | 607 |
|
397 |
| -RPCHelpMan getmempoolinfo() |
| 608 | +static RPCHelpMan getmempoolinfo() |
398 | 609 | {
|
399 | 610 | return RPCHelpMan{"getmempoolinfo",
|
400 | 611 | "\nReturns details on the active state of the TX memory pool.\n",
|
@@ -423,7 +634,7 @@ RPCHelpMan getmempoolinfo()
|
423 | 634 | };
|
424 | 635 | }
|
425 | 636 |
|
426 |
| -RPCHelpMan savemempool() |
| 637 | +static RPCHelpMan savemempool() |
427 | 638 | {
|
428 | 639 | return RPCHelpMan{"savemempool",
|
429 | 640 | "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n",
|
@@ -463,6 +674,8 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
|
463 | 674 | static const CRPCCommand commands[]{
|
464 | 675 | // category actor (function)
|
465 | 676 | // -------- ----------------
|
| 677 | + {"rawtransactions", &sendrawtransaction}, |
| 678 | + {"rawtransactions", &testmempoolaccept}, |
466 | 679 | {"blockchain", &getmempoolancestors},
|
467 | 680 | {"blockchain", &getmempooldescendants},
|
468 | 681 | {"blockchain", &getmempoolentry},
|
|
0 commit comments