mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-05-02 16:01:58 +02:00
Merge bitcoin/bitcoin#24656: refactor: Move mempool RPCs to rpc/mempool
fac5a51c47fba21678fb35805e40d00fe7c891a0 Move mempool RPCs to rpc/mempool (MarcoFalke) fa0f666dd7b55a5a0250c5e35d2c3dfd565463b7 style: Add static keyword where possible in rpc/mempool (MarcoFalke) Pull request description: This moves the remaining mempool RPCs to `rpc/mempool`. Previously all mempool RPCs from the `blockchain` category have been moved. This patch moves the ones from the `rawtransactions` category. In the future, as a follow-up to this refactoring patch, it could be considered whether a new `mempool` category should be introduced. Beside a clearer code organization, this pull request should also reduce the compile time and space of the `rawtransactions.cpp` file. ACKs for top commit: promag: Code review ACK fac5a51c47fba21678fb35805e40d00fe7c891a0. Tree-SHA512: 5578b894b68d0595869a9b03ed8dceebe3366f73dec5f090ccc36ff4002b1bc4d58af77546c2d71537c1be03694d9a28c4b1bfbb3569560997879293c5c0301e
This commit is contained in:
commit
3d2f24bb38
@ -14,9 +14,219 @@
|
|||||||
#include <rpc/util.h>
|
#include <rpc/util.h>
|
||||||
#include <txmempool.h>
|
#include <txmempool.h>
|
||||||
#include <univalue.h>
|
#include <univalue.h>
|
||||||
|
#include <util/moneystr.h>
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
|
|
||||||
static std::vector<RPCResult> MempoolEntryDescription() { return {
|
using node::DEFAULT_MAX_RAW_TX_FEE_RATE;
|
||||||
|
using node::NodeContext;
|
||||||
|
|
||||||
|
static RPCHelpMan sendrawtransaction()
|
||||||
|
{
|
||||||
|
return RPCHelpMan{"sendrawtransaction",
|
||||||
|
"\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n"
|
||||||
|
"\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n"
|
||||||
|
"for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n"
|
||||||
|
"nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n"
|
||||||
|
"\nA specific exception, RPC_TRANSACTION_ALREADY_IN_CHAIN, may throw if the transaction cannot be added to the mempool.\n"
|
||||||
|
"\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n",
|
||||||
|
{
|
||||||
|
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
|
||||||
|
{"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
|
||||||
|
"Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
|
||||||
|
"/kvB.\nSet to 0 to accept any fee rate.\n"},
|
||||||
|
},
|
||||||
|
RPCResult{
|
||||||
|
RPCResult::Type::STR_HEX, "", "The transaction hash in hex"
|
||||||
|
},
|
||||||
|
RPCExamples{
|
||||||
|
"\nCreate a transaction\n"
|
||||||
|
+ HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
|
||||||
|
"Sign the transaction, and get back the hex\n"
|
||||||
|
+ HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
|
||||||
|
"\nSend the transaction (signed hex)\n"
|
||||||
|
+ HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
|
||||||
|
"\nAs a JSON-RPC call\n"
|
||||||
|
+ HelpExampleRpc("sendrawtransaction", "\"signedhex\"")
|
||||||
|
},
|
||||||
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
|
{
|
||||||
|
RPCTypeCheck(request.params, {
|
||||||
|
UniValue::VSTR,
|
||||||
|
UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
|
||||||
|
});
|
||||||
|
|
||||||
|
CMutableTransaction mtx;
|
||||||
|
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
|
||||||
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
|
||||||
|
}
|
||||||
|
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
|
||||||
|
|
||||||
|
const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
|
||||||
|
DEFAULT_MAX_RAW_TX_FEE_RATE :
|
||||||
|
CFeeRate(AmountFromValue(request.params[1]));
|
||||||
|
|
||||||
|
int64_t virtual_size = GetVirtualTransactionSize(*tx);
|
||||||
|
CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
|
||||||
|
|
||||||
|
std::string err_string;
|
||||||
|
AssertLockNotHeld(cs_main);
|
||||||
|
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||||
|
const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true);
|
||||||
|
if (TransactionError::OK != err) {
|
||||||
|
throw JSONRPCTransactionError(err, err_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx->GetHash().GetHex();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static RPCHelpMan testmempoolaccept()
|
||||||
|
{
|
||||||
|
return RPCHelpMan{"testmempoolaccept",
|
||||||
|
"\nReturns result of mempool acceptance tests indicating if raw transaction(s) (serialized, hex-encoded) would be accepted by mempool.\n"
|
||||||
|
"\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"
|
||||||
|
"\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n"
|
||||||
|
"\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n"
|
||||||
|
"\nThis checks if transactions violate the consensus or policy rules.\n"
|
||||||
|
"\nSee sendrawtransaction call.\n",
|
||||||
|
{
|
||||||
|
{"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.",
|
||||||
|
{
|
||||||
|
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
|
||||||
|
"Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kvB\n"},
|
||||||
|
},
|
||||||
|
RPCResult{
|
||||||
|
RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n"
|
||||||
|
"Returns results for each transaction in the same order they were passed in.\n"
|
||||||
|
"Transactions that cannot be fully validated due to failures in other transactions will not contain an 'allowed' result.\n",
|
||||||
|
{
|
||||||
|
{RPCResult::Type::OBJ, "", "",
|
||||||
|
{
|
||||||
|
{RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
|
||||||
|
{RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"},
|
||||||
|
{RPCResult::Type::STR, "package-error", /*optional=*/true, "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."},
|
||||||
|
{RPCResult::Type::BOOL, "allowed", /*optional=*/true, "Whether this tx would be accepted to the mempool and pass client-specified maxfeerate. "
|
||||||
|
"If not present, the tx was not fully validated due to a failure in another tx in the list."},
|
||||||
|
{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)"},
|
||||||
|
{RPCResult::Type::OBJ, "fees", /*optional=*/true, "Transaction fees (only present if 'allowed' is true)",
|
||||||
|
{
|
||||||
|
{RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT},
|
||||||
|
}},
|
||||||
|
{RPCResult::Type::STR, "reject-reason", /*optional=*/true, "Rejection string (only present when 'allowed' is false)"},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RPCExamples{
|
||||||
|
"\nCreate a transaction\n"
|
||||||
|
+ HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
|
||||||
|
"Sign the transaction, and get back the hex\n"
|
||||||
|
+ HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
|
||||||
|
"\nTest acceptance of the transaction (signed hex)\n"
|
||||||
|
+ HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") +
|
||||||
|
"\nAs a JSON-RPC call\n"
|
||||||
|
+ HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")
|
||||||
|
},
|
||||||
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
|
{
|
||||||
|
RPCTypeCheck(request.params, {
|
||||||
|
UniValue::VARR,
|
||||||
|
UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
|
||||||
|
});
|
||||||
|
const UniValue raw_transactions = request.params[0].get_array();
|
||||||
|
if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER,
|
||||||
|
"Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
|
||||||
|
DEFAULT_MAX_RAW_TX_FEE_RATE :
|
||||||
|
CFeeRate(AmountFromValue(request.params[1]));
|
||||||
|
|
||||||
|
std::vector<CTransactionRef> txns;
|
||||||
|
txns.reserve(raw_transactions.size());
|
||||||
|
for (const auto& rawtx : raw_transactions.getValues()) {
|
||||||
|
CMutableTransaction mtx;
|
||||||
|
if (!DecodeHexTx(mtx, rawtx.get_str())) {
|
||||||
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
|
||||||
|
"TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input.");
|
||||||
|
}
|
||||||
|
txns.emplace_back(MakeTransactionRef(std::move(mtx)));
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||||
|
CTxMemPool& mempool = EnsureMemPool(node);
|
||||||
|
ChainstateManager& chainman = EnsureChainman(node);
|
||||||
|
CChainState& chainstate = chainman.ActiveChainstate();
|
||||||
|
const PackageMempoolAcceptResult package_result = [&] {
|
||||||
|
LOCK(::cs_main);
|
||||||
|
if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /* test_accept */ true);
|
||||||
|
return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(),
|
||||||
|
chainman.ProcessTransaction(txns[0], /*test_accept=*/true));
|
||||||
|
}();
|
||||||
|
|
||||||
|
UniValue rpc_result(UniValue::VARR);
|
||||||
|
// We will check transaction fees while we iterate through txns in order. If any transaction fee
|
||||||
|
// exceeds maxfeerate, we will leave the rest of the validation results blank, because it
|
||||||
|
// doesn't make sense to return a validation result for a transaction if its ancestor(s) would
|
||||||
|
// not be submitted.
|
||||||
|
bool exit_early{false};
|
||||||
|
for (const auto& tx : txns) {
|
||||||
|
UniValue result_inner(UniValue::VOBJ);
|
||||||
|
result_inner.pushKV("txid", tx->GetHash().GetHex());
|
||||||
|
result_inner.pushKV("wtxid", tx->GetWitnessHash().GetHex());
|
||||||
|
if (package_result.m_state.GetResult() == PackageValidationResult::PCKG_POLICY) {
|
||||||
|
result_inner.pushKV("package-error", package_result.m_state.GetRejectReason());
|
||||||
|
}
|
||||||
|
auto it = package_result.m_tx_results.find(tx->GetWitnessHash());
|
||||||
|
if (exit_early || it == package_result.m_tx_results.end()) {
|
||||||
|
// Validation unfinished. Just return the txid and wtxid.
|
||||||
|
rpc_result.push_back(result_inner);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto& tx_result = it->second;
|
||||||
|
// Package testmempoolaccept doesn't allow transactions to already be in the mempool.
|
||||||
|
CHECK_NONFATAL(tx_result.m_result_type != MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
|
||||||
|
if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
|
||||||
|
const CAmount fee = tx_result.m_base_fees.value();
|
||||||
|
// Check that fee does not exceed maximum fee
|
||||||
|
const int64_t virtual_size = tx_result.m_vsize.value();
|
||||||
|
const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
|
||||||
|
if (max_raw_tx_fee && fee > max_raw_tx_fee) {
|
||||||
|
result_inner.pushKV("allowed", false);
|
||||||
|
result_inner.pushKV("reject-reason", "max-fee-exceeded");
|
||||||
|
exit_early = true;
|
||||||
|
} else {
|
||||||
|
// Only return the fee and vsize if the transaction would pass ATMP.
|
||||||
|
// These can be used to calculate the feerate.
|
||||||
|
result_inner.pushKV("allowed", true);
|
||||||
|
result_inner.pushKV("vsize", virtual_size);
|
||||||
|
UniValue fees(UniValue::VOBJ);
|
||||||
|
fees.pushKV("base", ValueFromAmount(fee));
|
||||||
|
result_inner.pushKV("fees", fees);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result_inner.pushKV("allowed", false);
|
||||||
|
const TxValidationState state = tx_result.m_state;
|
||||||
|
if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
|
||||||
|
result_inner.pushKV("reject-reason", "missing-inputs");
|
||||||
|
} else {
|
||||||
|
result_inner.pushKV("reject-reason", state.GetRejectReason());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rpc_result.push_back(result_inner);
|
||||||
|
}
|
||||||
|
return rpc_result;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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, "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::NUM, "weight", "transaction weight as defined in BIP 141."},
|
||||||
RPCResult{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true,
|
RPCResult{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true,
|
||||||
@ -50,7 +260,8 @@ static std::vector<RPCResult> MempoolEntryDescription() { return {
|
|||||||
{RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}},
|
{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, "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)"},
|
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)
|
static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
|
||||||
{
|
{
|
||||||
@ -164,7 +375,7 @@ UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose, bool include_mempoo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RPCHelpMan getrawmempool()
|
static RPCHelpMan getrawmempool()
|
||||||
{
|
{
|
||||||
return RPCHelpMan{"getrawmempool",
|
return RPCHelpMan{"getrawmempool",
|
||||||
"\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n"
|
"\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n"
|
||||||
@ -214,7 +425,7 @@ RPCHelpMan getrawmempool()
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
RPCHelpMan getmempoolancestors()
|
static RPCHelpMan getmempoolancestors()
|
||||||
{
|
{
|
||||||
return RPCHelpMan{"getmempoolancestors",
|
return RPCHelpMan{"getmempoolancestors",
|
||||||
"\nIf txid is in the mempool, returns all in-mempool ancestors.\n",
|
"\nIf txid is in the mempool, returns all in-mempool ancestors.\n",
|
||||||
@ -278,7 +489,7 @@ RPCHelpMan getmempoolancestors()
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
RPCHelpMan getmempooldescendants()
|
static RPCHelpMan getmempooldescendants()
|
||||||
{
|
{
|
||||||
return RPCHelpMan{"getmempooldescendants",
|
return RPCHelpMan{"getmempooldescendants",
|
||||||
"\nIf txid is in the mempool, returns all in-mempool descendants.\n",
|
"\nIf txid is in the mempool, returns all in-mempool descendants.\n",
|
||||||
@ -343,7 +554,7 @@ RPCHelpMan getmempooldescendants()
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
RPCHelpMan getmempoolentry()
|
static RPCHelpMan getmempoolentry()
|
||||||
{
|
{
|
||||||
return RPCHelpMan{"getmempoolentry",
|
return RPCHelpMan{"getmempoolentry",
|
||||||
"\nReturns mempool data for given transaction\n",
|
"\nReturns mempool data for given transaction\n",
|
||||||
@ -394,7 +605,7 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
RPCHelpMan getmempoolinfo()
|
static RPCHelpMan getmempoolinfo()
|
||||||
{
|
{
|
||||||
return RPCHelpMan{"getmempoolinfo",
|
return RPCHelpMan{"getmempoolinfo",
|
||||||
"\nReturns details on the active state of the TX memory pool.\n",
|
"\nReturns details on the active state of the TX memory pool.\n",
|
||||||
@ -423,7 +634,7 @@ RPCHelpMan getmempoolinfo()
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
RPCHelpMan savemempool()
|
static RPCHelpMan savemempool()
|
||||||
{
|
{
|
||||||
return RPCHelpMan{"savemempool",
|
return RPCHelpMan{"savemempool",
|
||||||
"\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n",
|
"\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n",
|
||||||
@ -463,6 +674,8 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
|
|||||||
static const CRPCCommand commands[]{
|
static const CRPCCommand commands[]{
|
||||||
// category actor (function)
|
// category actor (function)
|
||||||
// -------- ----------------
|
// -------- ----------------
|
||||||
|
{"rawtransactions", &sendrawtransaction},
|
||||||
|
{"rawtransactions", &testmempoolaccept},
|
||||||
{"blockchain", &getmempoolancestors},
|
{"blockchain", &getmempoolancestors},
|
||||||
{"blockchain", &getmempooldescendants},
|
{"blockchain", &getmempooldescendants},
|
||||||
{"blockchain", &getmempoolentry},
|
{"blockchain", &getmempoolentry},
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
#include <script/standard.h>
|
#include <script/standard.h>
|
||||||
#include <uint256.h>
|
#include <uint256.h>
|
||||||
#include <util/bip32.h>
|
#include <util/bip32.h>
|
||||||
#include <util/moneystr.h>
|
|
||||||
#include <util/strencodings.h>
|
#include <util/strencodings.h>
|
||||||
#include <util/string.h>
|
#include <util/string.h>
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
@ -46,7 +45,6 @@
|
|||||||
|
|
||||||
using node::AnalyzePSBT;
|
using node::AnalyzePSBT;
|
||||||
using node::BroadcastTransaction;
|
using node::BroadcastTransaction;
|
||||||
using node::DEFAULT_MAX_RAW_TX_FEE_RATE;
|
|
||||||
using node::FindCoins;
|
using node::FindCoins;
|
||||||
using node::GetTransaction;
|
using node::GetTransaction;
|
||||||
using node::NodeContext;
|
using node::NodeContext;
|
||||||
@ -714,210 +712,6 @@ static RPCHelpMan signrawtransactionwithkey()
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static RPCHelpMan sendrawtransaction()
|
|
||||||
{
|
|
||||||
return RPCHelpMan{"sendrawtransaction",
|
|
||||||
"\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n"
|
|
||||||
"\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n"
|
|
||||||
"for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n"
|
|
||||||
"nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n"
|
|
||||||
"\nA specific exception, RPC_TRANSACTION_ALREADY_IN_CHAIN, may throw if the transaction cannot be added to the mempool.\n"
|
|
||||||
"\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n",
|
|
||||||
{
|
|
||||||
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
|
|
||||||
{"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
|
|
||||||
"Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
|
|
||||||
"/kvB.\nSet to 0 to accept any fee rate.\n"},
|
|
||||||
},
|
|
||||||
RPCResult{
|
|
||||||
RPCResult::Type::STR_HEX, "", "The transaction hash in hex"
|
|
||||||
},
|
|
||||||
RPCExamples{
|
|
||||||
"\nCreate a transaction\n"
|
|
||||||
+ HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
|
|
||||||
"Sign the transaction, and get back the hex\n"
|
|
||||||
+ HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
|
|
||||||
"\nSend the transaction (signed hex)\n"
|
|
||||||
+ HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
|
|
||||||
"\nAs a JSON-RPC call\n"
|
|
||||||
+ HelpExampleRpc("sendrawtransaction", "\"signedhex\"")
|
|
||||||
},
|
|
||||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
||||||
{
|
|
||||||
RPCTypeCheck(request.params, {
|
|
||||||
UniValue::VSTR,
|
|
||||||
UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
|
|
||||||
});
|
|
||||||
|
|
||||||
CMutableTransaction mtx;
|
|
||||||
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
|
|
||||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
|
|
||||||
}
|
|
||||||
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
|
|
||||||
|
|
||||||
const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
|
|
||||||
DEFAULT_MAX_RAW_TX_FEE_RATE :
|
|
||||||
CFeeRate(AmountFromValue(request.params[1]));
|
|
||||||
|
|
||||||
int64_t virtual_size = GetVirtualTransactionSize(*tx);
|
|
||||||
CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
|
|
||||||
|
|
||||||
std::string err_string;
|
|
||||||
AssertLockNotHeld(cs_main);
|
|
||||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
|
||||||
const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true);
|
|
||||||
if (TransactionError::OK != err) {
|
|
||||||
throw JSONRPCTransactionError(err, err_string);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx->GetHash().GetHex();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static RPCHelpMan testmempoolaccept()
|
|
||||||
{
|
|
||||||
return RPCHelpMan{"testmempoolaccept",
|
|
||||||
"\nReturns result of mempool acceptance tests indicating if raw transaction(s) (serialized, hex-encoded) would be accepted by mempool.\n"
|
|
||||||
"\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"
|
|
||||||
"\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n"
|
|
||||||
"\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n"
|
|
||||||
"\nThis checks if transactions violate the consensus or policy rules.\n"
|
|
||||||
"\nSee sendrawtransaction call.\n",
|
|
||||||
{
|
|
||||||
{"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.",
|
|
||||||
{
|
|
||||||
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
|
|
||||||
"Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kvB\n"},
|
|
||||||
},
|
|
||||||
RPCResult{
|
|
||||||
RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n"
|
|
||||||
"Returns results for each transaction in the same order they were passed in.\n"
|
|
||||||
"Transactions that cannot be fully validated due to failures in other transactions will not contain an 'allowed' result.\n",
|
|
||||||
{
|
|
||||||
{RPCResult::Type::OBJ, "", "",
|
|
||||||
{
|
|
||||||
{RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
|
|
||||||
{RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"},
|
|
||||||
{RPCResult::Type::STR, "package-error", /*optional=*/true, "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."},
|
|
||||||
{RPCResult::Type::BOOL, "allowed", /*optional=*/true, "Whether this tx would be accepted to the mempool and pass client-specified maxfeerate. "
|
|
||||||
"If not present, the tx was not fully validated due to a failure in another tx in the list."},
|
|
||||||
{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)"},
|
|
||||||
{RPCResult::Type::OBJ, "fees", /*optional=*/true, "Transaction fees (only present if 'allowed' is true)",
|
|
||||||
{
|
|
||||||
{RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT},
|
|
||||||
}},
|
|
||||||
{RPCResult::Type::STR, "reject-reason", /*optional=*/true, "Rejection string (only present when 'allowed' is false)"},
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RPCExamples{
|
|
||||||
"\nCreate a transaction\n"
|
|
||||||
+ HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
|
|
||||||
"Sign the transaction, and get back the hex\n"
|
|
||||||
+ HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
|
|
||||||
"\nTest acceptance of the transaction (signed hex)\n"
|
|
||||||
+ HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") +
|
|
||||||
"\nAs a JSON-RPC call\n"
|
|
||||||
+ HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")
|
|
||||||
},
|
|
||||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
||||||
{
|
|
||||||
RPCTypeCheck(request.params, {
|
|
||||||
UniValue::VARR,
|
|
||||||
UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
|
|
||||||
});
|
|
||||||
const UniValue raw_transactions = request.params[0].get_array();
|
|
||||||
if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER,
|
|
||||||
"Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
|
|
||||||
DEFAULT_MAX_RAW_TX_FEE_RATE :
|
|
||||||
CFeeRate(AmountFromValue(request.params[1]));
|
|
||||||
|
|
||||||
std::vector<CTransactionRef> txns;
|
|
||||||
txns.reserve(raw_transactions.size());
|
|
||||||
for (const auto& rawtx : raw_transactions.getValues()) {
|
|
||||||
CMutableTransaction mtx;
|
|
||||||
if (!DecodeHexTx(mtx, rawtx.get_str())) {
|
|
||||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
|
|
||||||
"TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input.");
|
|
||||||
}
|
|
||||||
txns.emplace_back(MakeTransactionRef(std::move(mtx)));
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
|
||||||
CTxMemPool& mempool = EnsureMemPool(node);
|
|
||||||
ChainstateManager& chainman = EnsureChainman(node);
|
|
||||||
CChainState& chainstate = chainman.ActiveChainstate();
|
|
||||||
const PackageMempoolAcceptResult package_result = [&] {
|
|
||||||
LOCK(::cs_main);
|
|
||||||
if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /* test_accept */ true);
|
|
||||||
return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(),
|
|
||||||
chainman.ProcessTransaction(txns[0], /*test_accept=*/ true));
|
|
||||||
}();
|
|
||||||
|
|
||||||
UniValue rpc_result(UniValue::VARR);
|
|
||||||
// We will check transaction fees while we iterate through txns in order. If any transaction fee
|
|
||||||
// exceeds maxfeerate, we will leave the rest of the validation results blank, because it
|
|
||||||
// doesn't make sense to return a validation result for a transaction if its ancestor(s) would
|
|
||||||
// not be submitted.
|
|
||||||
bool exit_early{false};
|
|
||||||
for (const auto& tx : txns) {
|
|
||||||
UniValue result_inner(UniValue::VOBJ);
|
|
||||||
result_inner.pushKV("txid", tx->GetHash().GetHex());
|
|
||||||
result_inner.pushKV("wtxid", tx->GetWitnessHash().GetHex());
|
|
||||||
if (package_result.m_state.GetResult() == PackageValidationResult::PCKG_POLICY) {
|
|
||||||
result_inner.pushKV("package-error", package_result.m_state.GetRejectReason());
|
|
||||||
}
|
|
||||||
auto it = package_result.m_tx_results.find(tx->GetWitnessHash());
|
|
||||||
if (exit_early || it == package_result.m_tx_results.end()) {
|
|
||||||
// Validation unfinished. Just return the txid and wtxid.
|
|
||||||
rpc_result.push_back(result_inner);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto& tx_result = it->second;
|
|
||||||
// Package testmempoolaccept doesn't allow transactions to already be in the mempool.
|
|
||||||
CHECK_NONFATAL(tx_result.m_result_type != MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
|
|
||||||
if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
|
|
||||||
const CAmount fee = tx_result.m_base_fees.value();
|
|
||||||
// Check that fee does not exceed maximum fee
|
|
||||||
const int64_t virtual_size = tx_result.m_vsize.value();
|
|
||||||
const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
|
|
||||||
if (max_raw_tx_fee && fee > max_raw_tx_fee) {
|
|
||||||
result_inner.pushKV("allowed", false);
|
|
||||||
result_inner.pushKV("reject-reason", "max-fee-exceeded");
|
|
||||||
exit_early = true;
|
|
||||||
} else {
|
|
||||||
// Only return the fee and vsize if the transaction would pass ATMP.
|
|
||||||
// These can be used to calculate the feerate.
|
|
||||||
result_inner.pushKV("allowed", true);
|
|
||||||
result_inner.pushKV("vsize", virtual_size);
|
|
||||||
UniValue fees(UniValue::VOBJ);
|
|
||||||
fees.pushKV("base", ValueFromAmount(fee));
|
|
||||||
result_inner.pushKV("fees", fees);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result_inner.pushKV("allowed", false);
|
|
||||||
const TxValidationState state = tx_result.m_state;
|
|
||||||
if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
|
|
||||||
result_inner.pushKV("reject-reason", "missing-inputs");
|
|
||||||
} else {
|
|
||||||
result_inner.pushKV("reject-reason", state.GetRejectReason());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rpc_result.push_back(result_inner);
|
|
||||||
}
|
|
||||||
return rpc_result;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static RPCHelpMan decodepsbt()
|
static RPCHelpMan decodepsbt()
|
||||||
{
|
{
|
||||||
return RPCHelpMan{
|
return RPCHelpMan{
|
||||||
@ -1928,10 +1722,8 @@ static const CRPCCommand commands[] =
|
|||||||
{ "rawtransactions", &createrawtransaction, },
|
{ "rawtransactions", &createrawtransaction, },
|
||||||
{ "rawtransactions", &decoderawtransaction, },
|
{ "rawtransactions", &decoderawtransaction, },
|
||||||
{ "rawtransactions", &decodescript, },
|
{ "rawtransactions", &decodescript, },
|
||||||
{ "rawtransactions", &sendrawtransaction, },
|
|
||||||
{ "rawtransactions", &combinerawtransaction, },
|
{ "rawtransactions", &combinerawtransaction, },
|
||||||
{ "rawtransactions", &signrawtransactionwithkey, },
|
{ "rawtransactions", &signrawtransactionwithkey, },
|
||||||
{ "rawtransactions", &testmempoolaccept, },
|
|
||||||
{ "rawtransactions", &decodepsbt, },
|
{ "rawtransactions", &decodepsbt, },
|
||||||
{ "rawtransactions", &combinepsbt, },
|
{ "rawtransactions", &combinepsbt, },
|
||||||
{ "rawtransactions", &finalizepsbt, },
|
{ "rawtransactions", &finalizepsbt, },
|
||||||
|
Loading…
x
Reference in New Issue
Block a user