aboutsummaryrefslogtreecommitdiff
path: root/src/rpc/rawtransaction.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc/rawtransaction.cpp')
-rw-r--r--src/rpc/rawtransaction.cpp246
1 files changed, 146 insertions, 100 deletions
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 47c776bbd1..339d711ac9 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -10,10 +10,12 @@
#include <index/txindex.h>
#include <key_io.h>
#include <merkleblock.h>
+#include <node/blockstorage.h>
#include <node/coin.h>
#include <node/context.h>
#include <node/psbt.h>
#include <node/transaction.h>
+#include <policy/packages.h>
#include <policy/policy.h>
#include <policy/rbf.h>
#include <primitives/transaction.h>
@@ -35,13 +37,12 @@
#include <validation.h>
#include <validationinterface.h>
-
#include <numeric>
#include <stdint.h>
#include <univalue.h>
-static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
+static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, CChainState& active_chainstate)
{
// Call into TxToUniv() in bitcoin-common to decode the transaction hex.
//
@@ -54,10 +55,10 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue&
LOCK(cs_main);
entry.pushKV("blockhash", hashBlock.GetHex());
- CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(hashBlock);
+ CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(hashBlock);
if (pindex) {
- if (::ChainActive().Contains(pindex)) {
- entry.pushKV("confirmations", 1 + ::ChainActive().Height() - pindex->nHeight);
+ if (active_chainstate.m_chain.Contains(pindex)) {
+ entry.pushKV("confirmations", 1 + active_chainstate.m_chain.Height() - pindex->nHeight);
entry.pushKV("time", pindex->GetBlockTime());
entry.pushKV("blocktime", pindex->GetBlockTime());
}
@@ -85,7 +86,7 @@ static RPCHelpMan getrawtransaction()
"If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
- {"verbose", RPCArg::Type::BOOL, /* default */ "false", "If false, return a string, otherwise return a json object"},
+ {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If false, return a string, otherwise return a json object"},
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "The block in which to look for the transaction"},
},
{
@@ -132,9 +133,10 @@ static RPCHelpMan getrawtransaction()
{
{RPCResult::Type::STR, "asm", "the asm"},
{RPCResult::Type::STR, "hex", "the hex"},
- {RPCResult::Type::NUM, "reqSigs", "The required sigs"},
+ {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
- {RPCResult::Type::ARR, "addresses", "",
+ {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"},
+ {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses",
{
{RPCResult::Type::STR, "address", "bitcoin address"},
}},
@@ -157,7 +159,8 @@ static RPCHelpMan getrawtransaction()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- const NodeContext& node = EnsureNodeContext(request.context);
+ const NodeContext& node = EnsureAnyNodeContext(request.context);
+ ChainstateManager& chainman = EnsureChainman(node);
bool in_active_chain = true;
uint256 hash = ParseHashV(request.params[0], "parameter 1");
@@ -178,11 +181,11 @@ static RPCHelpMan getrawtransaction()
LOCK(cs_main);
uint256 blockhash = ParseHashV(request.params[2], "parameter 3");
- blockindex = g_chainman.m_blockman.LookupBlockIndex(blockhash);
+ blockindex = chainman.m_blockman.LookupBlockIndex(blockhash);
if (!blockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block hash not found");
}
- in_active_chain = ::ChainActive().Contains(blockindex);
+ in_active_chain = chainman.ActiveChain().Contains(blockindex);
}
bool f_txindex_ready = false;
@@ -215,7 +218,7 @@ static RPCHelpMan getrawtransaction()
UniValue result(UniValue::VOBJ);
if (blockindex) result.pushKV("in_active_chain", in_active_chain);
- TxToJSON(*tx, hash_block, result);
+ TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate());
return result;
},
};
@@ -257,21 +260,23 @@ static RPCHelpMan gettxoutproof()
CBlockIndex* pblockindex = nullptr;
uint256 hashBlock;
+ ChainstateManager& chainman = EnsureAnyChainman(request.context);
if (!request.params[1].isNull()) {
LOCK(cs_main);
hashBlock = ParseHashV(request.params[1], "blockhash");
- pblockindex = g_chainman.m_blockman.LookupBlockIndex(hashBlock);
+ pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
} else {
LOCK(cs_main);
+ CChainState& active_chainstate = chainman.ActiveChainstate();
// Loop through txids and try to find which block they're in. Exit loop once a block is found.
for (const auto& tx : setTxids) {
- const Coin& coin = AccessByTxid(::ChainstateActive().CoinsTip(), tx);
+ const Coin& coin = AccessByTxid(active_chainstate.CoinsTip(), tx);
if (!coin.IsSpent()) {
- pblockindex = ::ChainActive()[coin.nHeight];
+ pblockindex = active_chainstate.m_chain[coin.nHeight];
break;
}
}
@@ -290,7 +295,7 @@ static RPCHelpMan gettxoutproof()
if (!tx || hashBlock.IsNull()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
}
- pblockindex = g_chainman.m_blockman.LookupBlockIndex(hashBlock);
+ pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
if (!pblockindex) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
}
@@ -348,10 +353,11 @@ static RPCHelpMan verifytxoutproof()
if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)
return res;
+ ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
- const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());
- if (!pindex || !::ChainActive().Contains(pindex) || pindex->nTx == 0) {
+ const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());
+ if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
}
@@ -382,7 +388,7 @@ static RPCHelpMan createrawtransaction()
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
- {"sequence", RPCArg::Type::NUM, /* default */ "depends on the value of the 'replaceable' and 'locktime' arguments", "The sequence number"},
+ {"sequence", RPCArg::Type::NUM, RPCArg::DefaultHint{"depends on the value of the 'replaceable' and 'locktime' arguments"}, "The sequence number"},
},
},
},
@@ -392,7 +398,7 @@ static RPCHelpMan createrawtransaction()
"For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
" accepted as second parameter.",
{
- {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
+ {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "",
{
{"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT},
},
@@ -404,8 +410,8 @@ static RPCHelpMan createrawtransaction()
},
},
},
- {"locktime", RPCArg::Type::NUM, /* default */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"},
- {"replaceable", RPCArg::Type::BOOL, /* default */ "false", "Marks this transaction as BIP125-replaceable.\n"
+ {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
+ {"replaceable", RPCArg::Type::BOOL, RPCArg::Default{false}, "Marks this transaction as BIP125-replaceable.\n"
" Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."},
},
RPCResult{
@@ -444,7 +450,7 @@ static RPCHelpMan decoderawtransaction()
"\nReturn a JSON object representing the serialized, hex-encoded transaction.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction hex string"},
- {"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction.\n"
+ {"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n"
"If iswitness is not present, heuristic tests will be used in decoding.\n"
"If true, only witness deserialization will be tried.\n"
"If false, only non-witness deserialization will be tried.\n"
@@ -490,9 +496,10 @@ static RPCHelpMan decoderawtransaction()
{
{RPCResult::Type::STR, "asm", "the asm"},
{RPCResult::Type::STR_HEX, "hex", "the hex"},
- {RPCResult::Type::NUM, "reqSigs", "The required sigs"},
+ {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
- {RPCResult::Type::ARR, "addresses", "",
+ {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"},
+ {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses",
{
{RPCResult::Type::STR, "address", "bitcoin address"},
}},
@@ -548,8 +555,9 @@ static RPCHelpMan decodescript()
{
{RPCResult::Type::STR, "asm", "Script public key"},
{RPCResult::Type::STR, "type", "The output type (e.g. "+GetAllOutputTypes()+")"},
- {RPCResult::Type::NUM, "reqSigs", "The required signatures"},
- {RPCResult::Type::ARR, "addresses", "",
+ {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"},
+ {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"},
+ {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses",
{
{RPCResult::Type::STR, "address", "bitcoin address"},
}},
@@ -559,8 +567,9 @@ static RPCHelpMan decodescript()
{RPCResult::Type::STR, "asm", "String representation of the script public key"},
{RPCResult::Type::STR_HEX, "hex", "Hex string of the script public key"},
{RPCResult::Type::STR, "type", "The type of the script public key (e.g. witness_v0_keyhash or witness_v0_scripthash)"},
- {RPCResult::Type::NUM, "reqSigs", "The required signatures (always 1)"},
- {RPCResult::Type::ARR, "addresses", "(always length 1)",
+ {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"},
+ {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"},
+ {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses",
{
{RPCResult::Type::STR, "address", "segwit address"},
}},
@@ -672,10 +681,11 @@ static RPCHelpMan combinerawtransaction()
CCoinsView viewDummy;
CCoinsViewCache view(&viewDummy);
{
- const CTxMemPool& mempool = EnsureMemPool(request.context);
- LOCK(cs_main);
- LOCK(mempool.cs);
- CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip();
+ NodeContext& node = EnsureAnyNodeContext(request.context);
+ const CTxMemPool& mempool = EnsureMemPool(node);
+ ChainstateManager& chainman = EnsureChainman(node);
+ LOCK2(cs_main, mempool.cs);
+ CCoinsViewCache &viewChain = chainman.ActiveChainstate().CoinsTip();
CCoinsViewMemPool viewMempool(&viewChain, mempool);
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
@@ -743,7 +753,7 @@ static RPCHelpMan signrawtransactionwithkey()
},
},
},
- {"sighashtype", RPCArg::Type::STR, /* default */ "ALL", "The signature hash type. Must be one of:\n"
+ {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type. Must be one of:\n"
" \"ALL\"\n"
" \"NONE\"\n"
" \"SINGLE\"\n"
@@ -799,7 +809,7 @@ static RPCHelpMan signrawtransactionwithkey()
for (const CTxIn& txin : mtx.vin) {
coins[txin.prevout]; // Create empty map entry keyed by prevout.
}
- NodeContext& node = EnsureNodeContext(request.context);
+ NodeContext& node = EnsureAnyNodeContext(request.context);
FindCoins(node, coins);
// Parse the prevtxs array
@@ -823,9 +833,9 @@ static RPCHelpMan sendrawtransaction()
"\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
- {"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()),
+ {"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 +
- "/kB.\nSet to 0 to accept any fee rate.\n"},
+ "/kvB.\nSet to 0 to accept any fee rate.\n"},
},
RPCResult{
RPCResult::Type::STR_HEX, "", "The transaction hash in hex"
@@ -862,7 +872,7 @@ static RPCHelpMan sendrawtransaction()
std::string err_string;
AssertLockNotHeld(cs_main);
- NodeContext& node = EnsureNodeContext(request.context);
+ 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);
@@ -876,8 +886,11 @@ static RPCHelpMan sendrawtransaction()
static RPCHelpMan testmempoolaccept()
{
return RPCHelpMan{"testmempoolaccept",
- "\nReturns result of mempool acceptance tests indicating if raw transaction (serialized, hex-encoded) would be accepted by mempool.\n"
- "\nThis checks if the transaction violates the consensus or policy rules.\n"
+ "\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 25 (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.\n"
@@ -886,17 +899,21 @@ static RPCHelpMan testmempoolaccept()
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
},
},
- {"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()), "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"},
+ {"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"
- "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 an earlier transaction failed.\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::BOOL, "allowed", "If the mempool allows this tx to be inserted"},
+ {RPCResult::Type::STR, "package-error", "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."},
+ {RPCResult::Type::BOOL, "allowed", "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", "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", "Transaction fees (only present if 'allowed' is true)",
{
@@ -923,59 +940,86 @@ static RPCHelpMan testmempoolaccept()
UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
});
- if (request.params[0].get_array().size() != 1) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Array must contain exactly one raw transaction for now");
- }
-
- CMutableTransaction mtx;
- if (!DecodeHexTx(mtx, request.params[0].get_array()[0].get_str())) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
+ 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.");
}
- 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]));
- CTxMemPool& mempool = EnsureMemPool(request.context);
- int64_t virtual_size = GetVirtualTransactionSize(*tx);
- CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
-
- UniValue result(UniValue::VARR);
- UniValue result_0(UniValue::VOBJ);
- result_0.pushKV("txid", tx->GetHash().GetHex());
- result_0.pushKV("wtxid", tx->GetWitnessHash().GetHex());
-
- const MempoolAcceptResult accept_result = WITH_LOCK(cs_main, return AcceptToMemoryPool(::ChainstateActive(), mempool, std::move(tx),
- false /* bypass_limits */, /* test_accept */ true));
-
- // Only return the fee and vsize if the transaction would pass ATMP.
- // These can be used to calculate the feerate.
- if (accept_result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
- const CAmount fee = accept_result.m_base_fees.value();
- // Check that fee does not exceed maximum fee
- if (max_raw_tx_fee && fee > max_raw_tx_fee) {
- result_0.pushKV("allowed", false);
- result_0.pushKV("reject-reason", "max-fee-exceeded");
- } else {
- result_0.pushKV("allowed", true);
- result_0.pushKV("vsize", virtual_size);
- UniValue fees(UniValue::VOBJ);
- fees.pushKV("base", ValueFromAmount(fee));
- result_0.pushKV("fees", fees);
+ std::vector<CTransactionRef> txns;
+ 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.");
}
- result.push_back(std::move(result_0));
- } else {
- result_0.pushKV("allowed", false);
- const TxValidationState state = accept_result.m_state;
- if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
- result_0.pushKV("reject-reason", "missing-inputs");
+ txns.emplace_back(MakeTransactionRef(std::move(mtx)));
+ }
+
+ NodeContext& node = EnsureAnyNodeContext(request.context);
+ CTxMemPool& mempool = EnsureMemPool(node);
+ CChainState& chainstate = EnsureChainman(node).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(),
+ AcceptToMemoryPool(chainstate, mempool, txns[0], /* bypass_limits */ false, /* test_accept*/ true));
+ }();
+
+ UniValue rpc_result(UniValue::VARR);
+ // We will check transaction fees we iterate through txns in order. If any transaction fee
+ // exceeds maxfeerate, we will keave 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;
+ 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 = GetVirtualTransactionSize(*tx);
+ 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_0.pushKV("reject-reason", state.GetRejectReason());
+ 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());
+ }
}
- result.push_back(std::move(result_0));
+ rpc_result.push_back(result_inner);
}
- return result;
+ return rpc_result;
},
};
}
@@ -1350,7 +1394,7 @@ static RPCHelpMan finalizepsbt()
"Implements the Finalizer and Extractor roles.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"},
- {"extract", RPCArg::Type::BOOL, /* default */ "true", "If true and the transaction is complete,\n"
+ {"extract", RPCArg::Type::BOOL, RPCArg::Default{true}, "If true and the transaction is complete,\n"
" extract and return the complete transaction in normal network serialization instead of the PSBT."},
},
RPCResult{
@@ -1412,7 +1456,7 @@ static RPCHelpMan createpsbt()
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
- {"sequence", RPCArg::Type::NUM, /* default */ "depends on the value of the 'replaceable' and 'locktime' arguments", "The sequence number"},
+ {"sequence", RPCArg::Type::NUM, RPCArg::DefaultHint{"depends on the value of the 'replaceable' and 'locktime' arguments"}, "The sequence number"},
},
},
},
@@ -1422,7 +1466,7 @@ static RPCHelpMan createpsbt()
"For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
" accepted as second parameter.",
{
- {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
+ {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "",
{
{"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT},
},
@@ -1434,8 +1478,8 @@ static RPCHelpMan createpsbt()
},
},
},
- {"locktime", RPCArg::Type::NUM, /* default */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"},
- {"replaceable", RPCArg::Type::BOOL, /* default */ "false", "Marks this transaction as BIP125 replaceable.\n"
+ {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
+ {"replaceable", RPCArg::Type::BOOL, RPCArg::Default{false}, "Marks this transaction as BIP125 replaceable.\n"
" Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."},
},
RPCResult{
@@ -1487,9 +1531,9 @@ static RPCHelpMan converttopsbt()
"createpsbt and walletcreatefundedpsbt should be used for new applications.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of a raw transaction"},
- {"permitsigdata", RPCArg::Type::BOOL, /* default */ "false", "If true, any signatures in the input will be discarded and conversion\n"
+ {"permitsigdata", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, any signatures in the input will be discarded and conversion\n"
" will continue. If false, RPC will fail if any signatures are present."},
- {"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction.\n"
+ {"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n"
"If iswitness is not present, heuristic tests will be used in decoding.\n"
"If true, only witness deserialization will be tried.\n"
"If false, only non-witness deserialization will be tried.\n"
@@ -1559,7 +1603,7 @@ static RPCHelpMan utxoupdatepsbt()
{"", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with an output descriptor and extra information", {
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
- {"range", RPCArg::Type::RANGE, "1000", "Up to what index HD chains should be explored (either end or [begin,end])"},
+ {"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "Up to what index HD chains should be explored (either end or [begin,end])"},
}},
}},
},
@@ -1595,9 +1639,11 @@ static RPCHelpMan utxoupdatepsbt()
CCoinsView viewDummy;
CCoinsViewCache view(&viewDummy);
{
- const CTxMemPool& mempool = EnsureMemPool(request.context);
+ NodeContext& node = EnsureAnyNodeContext(request.context);
+ const CTxMemPool& mempool = EnsureMemPool(node);
+ ChainstateManager& chainman = EnsureChainman(node);
LOCK2(cs_main, mempool.cs);
- CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip();
+ CCoinsViewCache &viewChain = chainman.ActiveChainstate().CoinsTip();
CCoinsViewMemPool viewMempool(&viewChain, mempool);
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
@@ -1769,7 +1815,7 @@ static RPCHelpMan analyzepsbt()
}},
}},
{RPCResult::Type::NUM, "estimated_vsize", /* optional */ true, "Estimated vsize of the final signed transaction"},
- {RPCResult::Type::STR_AMOUNT, "estimated_feerate", /* optional */ true, "Estimated feerate of the final signed transaction in " + CURRENCY_UNIT + "/kB. Shown only if all UTXO slots in the PSBT have been filled"},
+ {RPCResult::Type::STR_AMOUNT, "estimated_feerate", /* optional */ true, "Estimated feerate of the final signed transaction in " + CURRENCY_UNIT + "/kvB. Shown only if all UTXO slots in the PSBT have been filled"},
{RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, "The transaction fee paid. Shown only if all UTXO slots in the PSBT have been filled"},
{RPCResult::Type::STR, "next", "Role of the next person that this psbt needs to go to"},
{RPCResult::Type::STR, "error", /* optional */ true, "Error message (if there is one)"},
@@ -1828,13 +1874,13 @@ static RPCHelpMan analyzepsbt()
}
if (!inputs_result.empty()) result.pushKV("inputs", inputs_result);
- if (psbta.estimated_vsize != nullopt) {
+ if (psbta.estimated_vsize != std::nullopt) {
result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize);
}
- if (psbta.estimated_feerate != nullopt) {
+ if (psbta.estimated_feerate != std::nullopt) {
result.pushKV("estimated_feerate", ValueFromAmount(psbta.estimated_feerate->GetFeePerK()));
}
- if (psbta.fee != nullopt) {
+ if (psbta.fee != std::nullopt) {
result.pushKV("fee", ValueFromAmount(*psbta.fee));
}
result.pushKV("next", PSBTRoleName(psbta.next));