Changeset View
Changeset View
Standalone View
Standalone View
src/rpc/rawtransaction.cpp
Show All 11 Lines | |||||
#include <index/txindex.h> | #include <index/txindex.h> | ||||
#include <key_io.h> | #include <key_io.h> | ||||
#include <merkleblock.h> | #include <merkleblock.h> | ||||
#include <node/blockstorage.h> | #include <node/blockstorage.h> | ||||
#include <node/coin.h> | #include <node/coin.h> | ||||
#include <node/context.h> | #include <node/context.h> | ||||
#include <node/psbt.h> | #include <node/psbt.h> | ||||
#include <node/transaction.h> | #include <node/transaction.h> | ||||
#include <policy/packages.h> | |||||
#include <policy/policy.h> | #include <policy/policy.h> | ||||
#include <primitives/transaction.h> | #include <primitives/transaction.h> | ||||
#include <psbt.h> | #include <psbt.h> | ||||
#include <random.h> | #include <random.h> | ||||
#include <rpc/blockchain.h> | #include <rpc/blockchain.h> | ||||
#include <rpc/rawtransaction_util.h> | #include <rpc/rawtransaction_util.h> | ||||
#include <rpc/server.h> | #include <rpc/server.h> | ||||
#include <rpc/util.h> | #include <rpc/util.h> | ||||
▲ Show 20 Lines • Show All 1,019 Lines • ▼ Show 20 Lines | return RPCHelpMan{ | ||||
return tx->GetHash().GetHex(); | return tx->GetHash().GetHex(); | ||||
}, | }, | ||||
}; | }; | ||||
} | } | ||||
static RPCHelpMan testmempoolaccept() { | static RPCHelpMan testmempoolaccept() { | ||||
return RPCHelpMan{ | return RPCHelpMan{ | ||||
"testmempoolaccept", | "testmempoolaccept", | ||||
"Returns result of mempool acceptance tests indicating if raw" | "\nReturns result of mempool acceptance tests indicating if raw " | ||||
" transaction (serialized, hex-encoded) would be accepted" | "transaction(s) (serialized, hex-encoded) would be accepted by " | ||||
" by mempool.\n" | "mempool.\n" | ||||
"\nThis checks if the transaction violates the consensus or policy " | "\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" | "rules.\n" | ||||
"\nSee sendrawtransaction call.\n", | "\nSee sendrawtransaction call.\n", | ||||
{ | { | ||||
{ | { | ||||
"rawtxs", | "rawtxs", | ||||
RPCArg::Type::ARR, | RPCArg::Type::ARR, | ||||
RPCArg::Optional::NO, | RPCArg::Optional::NO, | ||||
"An array of hex strings of raw transactions.\n" | "An array of hex strings of raw transactions.", | ||||
" Length must be one for now.", | |||||
{ | { | ||||
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, | {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, | ||||
""}, | ""}, | ||||
}, | }, | ||||
}, | }, | ||||
{"maxfeerate", RPCArg::Type::AMOUNT, | {"maxfeerate", RPCArg::Type::AMOUNT, | ||||
/* default */ | /* default */ | ||||
FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()), | FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()), | ||||
"Reject transactions whose fee rate is higher than the specified " | "Reject transactions whose fee rate is higher than the specified " | ||||
"value, expressed in " + | "value, expressed in " + | ||||
Currency::get().ticker + "/kB\n"}, | Currency::get().ticker + "/kB\n"}, | ||||
}, | }, | ||||
RPCResult{ | RPCResult{ | ||||
RPCResult::Type::ARR, | RPCResult::Type::ARR, | ||||
"", | "", | ||||
"The result of the mempool acceptance test for each raw " | "The result of the mempool acceptance test for each raw " | ||||
"transaction in the input array.\n" | "transaction in the input array.\n" | ||||
"Length is exactly one for now.", | "Returns results for each transaction in the same order they were " | ||||
"passed in.\n" | |||||
"It is possible for transactions to not be fully validated " | |||||
"('allowed' unset) if another transaction failed.\n", | |||||
{ | { | ||||
{RPCResult::Type::OBJ, | {RPCResult::Type::OBJ, | ||||
"", | "", | ||||
"", | "", | ||||
{ | { | ||||
{RPCResult::Type::STR_HEX, "txid", | {RPCResult::Type::STR_HEX, "txid", | ||||
"The transaction hash in hex"}, | "The transaction hash in hex"}, | ||||
{RPCResult::Type::STR, "package-error", | |||||
"Package validation error, if any (only possible if " | |||||
"rawtxs had more than 1 transaction)."}, | |||||
{RPCResult::Type::BOOL, "allowed", | {RPCResult::Type::BOOL, "allowed", | ||||
"If the mempool allows this tx to be inserted"}, | "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, "size", "The transaction size"}, | {RPCResult::Type::NUM, "size", "The transaction size"}, | ||||
{RPCResult::Type::OBJ, | {RPCResult::Type::OBJ, | ||||
"fees", | "fees", | ||||
"Transaction fees (only present if 'allowed' is true)", | "Transaction fees (only present if 'allowed' is true)", | ||||
{ | { | ||||
{RPCResult::Type::STR_AMOUNT, "base", | {RPCResult::Type::STR_AMOUNT, "base", | ||||
"transaction fee in " + Currency::get().ticker}, | "transaction fee in " + Currency::get().ticker}, | ||||
}}, | }}, | ||||
Show All 17 Lines | return RPCHelpMan{ | ||||
[&](const RPCHelpMan &self, const Config &config, | [&](const RPCHelpMan &self, const Config &config, | ||||
const JSONRPCRequest &request) -> UniValue { | const JSONRPCRequest &request) -> UniValue { | ||||
RPCTypeCheck(request.params, | RPCTypeCheck(request.params, | ||||
{ | { | ||||
UniValue::VARR, | UniValue::VARR, | ||||
// VNUM or VSTR, checked inside AmountFromValue() | // VNUM or VSTR, checked inside AmountFromValue() | ||||
UniValueType(), | UniValueType(), | ||||
}); | }); | ||||
const UniValue raw_transactions = request.params[0].get_array(); | |||||
if (request.params[0].get_array().size() != 1) { | if (raw_transactions.size() < 1 || | ||||
throw JSONRPCError( | raw_transactions.size() > MAX_PACKAGE_COUNT) { | ||||
RPC_INVALID_PARAMETER, | throw JSONRPCError(RPC_INVALID_PARAMETER, | ||||
"Array must contain exactly one raw transaction for now"); | "Array must contain between 1 and " + | ||||
} | ToString(MAX_PACKAGE_COUNT) + | ||||
" transactions."); | |||||
CMutableTransaction mtx; | |||||
if (!DecodeHexTx(mtx, request.params[0].get_array()[0].get_str())) { | |||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | |||||
"TX decode failed"); | |||||
} | } | ||||
CTransactionRef tx(MakeTransactionRef(std::move(mtx))); | |||||
const TxId &txid = tx->GetId(); | |||||
const CFeeRate max_raw_tx_fee_rate = | const CFeeRate max_raw_tx_fee_rate = | ||||
request.params[1].isNull() | request.params[1].isNull() | ||||
? DEFAULT_MAX_RAW_TX_FEE_RATE | ? DEFAULT_MAX_RAW_TX_FEE_RATE | ||||
: CFeeRate(AmountFromValue(request.params[1])); | : CFeeRate(AmountFromValue(request.params[1])); | ||||
NodeContext &node = EnsureAnyNodeContext(request.context); | 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()); | |||||
} | |||||
txns.emplace_back(MakeTransactionRef(std::move(mtx))); | |||||
} | |||||
NodeContext &node = EnsureAnyNodeContext(request.context); | |||||
CTxMemPool &mempool = EnsureMemPool(node); | CTxMemPool &mempool = EnsureMemPool(node); | ||||
int64_t virtual_size = GetVirtualTransactionSize(*tx); | CChainState &chainstate = EnsureChainman(node).ActiveChainstate(); | ||||
Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); | const PackageMempoolAcceptResult package_result = [&] { | ||||
LOCK(::cs_main); | |||||
UniValue result(UniValue::VARR); | if (txns.size() > 1) { | ||||
UniValue result_0(UniValue::VOBJ); | return ProcessNewPackage(config, chainstate, mempool, txns, | ||||
result_0.pushKV("txid", txid.GetHex()); | /* test_accept */ true); | ||||
} | |||||
ChainstateManager &chainman = EnsureChainman(node); | return PackageMempoolAcceptResult( | ||||
txns[0]->GetId(), | |||||
const MempoolAcceptResult accept_result = WITH_LOCK( | AcceptToMemoryPool(chainstate, config, mempool, txns[0], | ||||
cs_main, | /* bypass_limits */ false, | ||||
return AcceptToMemoryPool( | /* test_accept*/ true)); | ||||
chainman.ActiveChainstate(), config, mempool, std::move(tx), | }(); | ||||
false /* bypass_limits */, true /* test_accept */)); | |||||
UniValue rpc_result(UniValue::VARR); | |||||
// Only return the fee and size if the transaction would pass ATMP. | // We will check transaction fees while we iterate through txns in | ||||
// These can be used to calculate the feerate. | // order. If any transaction fee exceeds maxfeerate, we will leave | ||||
if (accept_result.m_result_type == | // 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->GetId().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->GetId()); | |||||
if (exit_early || it == package_result.m_tx_results.end()) { | |||||
// Validation unfinished. Just return the txid. | |||||
rpc_result.push_back(result_inner); | |||||
continue; | |||||
} | |||||
const auto &tx_result = it->second; | |||||
if (tx_result.m_result_type == | |||||
MempoolAcceptResult::ResultType::VALID) { | MempoolAcceptResult::ResultType::VALID) { | ||||
const Amount fee = accept_result.m_base_fees.value(); | const Amount fee = tx_result.m_base_fees.value(); | ||||
// Check that fee does not exceed maximum fee | // Check that fee does not exceed maximum fee | ||||
if (max_raw_tx_fee != Amount::zero() && fee > max_raw_tx_fee) { | const int64_t virtual_size = GetVirtualTransactionSize(*tx); | ||||
result_0.pushKV("allowed", false); | const Amount max_raw_tx_fee = | ||||
result_0.pushKV("reject-reason", "max-fee-exceeded"); | max_raw_tx_fee_rate.GetFee(virtual_size); | ||||
if (max_raw_tx_fee != Amount::zero() && | |||||
fee > max_raw_tx_fee) { | |||||
result_inner.pushKV("allowed", false); | |||||
result_inner.pushKV("reject-reason", | |||||
"max-fee-exceeded"); | |||||
exit_early = true; | |||||
} else { | } else { | ||||
result_0.pushKV("allowed", true); | // Only return the fee and size if the transaction | ||||
result_0.pushKV("size", virtual_size); | // would pass ATMP. | ||||
// These can be used to calculate the feerate. | |||||
result_inner.pushKV("allowed", true); | |||||
result_inner.pushKV("size", virtual_size); | |||||
UniValue fees(UniValue::VOBJ); | UniValue fees(UniValue::VOBJ); | ||||
fees.pushKV("base", fee); | fees.pushKV("base", fee); | ||||
result_0.pushKV("fees", fees); | result_inner.pushKV("fees", fees); | ||||
} | } | ||||
result.push_back(std::move(result_0)); | |||||
} else { | } else { | ||||
result_0.pushKV("allowed", false); | result_inner.pushKV("allowed", false); | ||||
const TxValidationState state = accept_result.m_state; | const TxValidationState state = tx_result.m_state; | ||||
if (state.GetResult() == | if (state.GetResult() == | ||||
TxValidationResult::TX_MISSING_INPUTS) { | TxValidationResult::TX_MISSING_INPUTS) { | ||||
result_0.pushKV("reject-reason", "missing-inputs"); | result_inner.pushKV("reject-reason", "missing-inputs"); | ||||
} else { | } else { | ||||
result_0.pushKV("reject-reason", state.GetRejectReason()); | result_inner.pushKV("reject-reason", | ||||
state.GetRejectReason()); | |||||
} | } | ||||
result.push_back(std::move(result_0)); | |||||
} | } | ||||
return result; | rpc_result.push_back(result_inner); | ||||
} | |||||
return rpc_result; | |||||
}, | }, | ||||
}; | }; | ||||
} | } | ||||
static RPCHelpMan decodepsbt() { | static RPCHelpMan decodepsbt() { | ||||
return RPCHelpMan{ | return RPCHelpMan{ | ||||
"decodepsbt", | "decodepsbt", | ||||
"Return a JSON object representing the serialized, base64-encoded " | "Return a JSON object representing the serialized, base64-encoded " | ||||
▲ Show 20 Lines • Show All 1,042 Lines • Show Last 20 Lines |