Skip to content

Commit fac5a51

Browse files
author
MarcoFalke
committed
Move mempool RPCs to rpc/mempool
Can be reviewed with --color-moved=dimmed-zebra --color-moved-ws=ignore-all-space
1 parent fa0f666 commit fac5a51

File tree

2 files changed

+210
-208
lines changed

2 files changed

+210
-208
lines changed

src/rpc/mempool.cpp

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,216 @@
1414
#include <rpc/util.h>
1515
#include <txmempool.h>
1616
#include <univalue.h>
17+
#include <util/moneystr.h>
1718
#include <validation.h>
1819

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+
19227
static std::vector<RPCResult> MempoolEntryDescription()
20228
{
21229
return {
@@ -466,6 +674,8 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
466674
static const CRPCCommand commands[]{
467675
// category actor (function)
468676
// -------- ----------------
677+
{"rawtransactions", &sendrawtransaction},
678+
{"rawtransactions", &testmempoolaccept},
469679
{"blockchain", &getmempoolancestors},
470680
{"blockchain", &getmempooldescendants},
471681
{"blockchain", &getmempoolentry},

0 commit comments

Comments
 (0)