aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrew Chow <github@achow101.com>2023-12-01 12:10:12 -0500
committerAndrew Chow <github@achow101.com>2023-12-01 12:17:15 -0500
commit6b3927f79a92a737f32ab32e96c681462da923ef (patch)
tree353acb668e5d2bd492f982e2514135e34939d77b /src
parent498994b6f55d04a7940f832e7fbd17e5acdaff15 (diff)
parentf23ba24aa079d68697d475789cd21bd7b5075550 (diff)
downloadbitcoin-6b3927f79a92a737f32ab32e96c681462da923ef.tar.xz
Merge bitcoin/bitcoin#28848: bugfix, Change up submitpackage results to return results for all transactions
f23ba24aa079d68697d475789cd21bd7b5075550 test_submitpackage: only make a chain of 3 txns (Greg Sanders) e67a345162912ef7c1bfa3c89c7e7c629505f0a3 doc: submitpackage vsize results are sigops-adjusted (Greg Sanders) b67db52c399089e5d4c4202ebb905794dfd050d0 RPC submitpackage: change return format to allow partial errors (Greg Sanders) Pull request description: This was prompted by errors being returned that didn't "make any sense" to me, because it would for example return a "fee too low" error, when the "real" error was the child had something invalid, which disallowed CPFP evaluation. Rather than make judgment calls on what error is important(which is currently just return the "first"!), we simply return all errors and let the callers determine what's best. Added a top level `package_msg` for quick eye-balling of general success of the package. This PR also fixes a couple bugs: 1) Currently we don't actually broadcast a transaction, even if it was entered into our mempool, if a subsequent transaction causes `PKG_TX` failure. 2) "other-wtxid" is uncovered by tests, but IIUC was previously required to return "fees" and "vsize" results, but did not. I just make those results optional. ACKs for top commit: Sjors: Light re-utACK f23ba24aa079d68697d475789cd21bd7b5075550 achow101: ACK f23ba24aa079d68697d475789cd21bd7b5075550 glozow: utACK f23ba24aa079d68697d475789cd21bd7b5075550, thanks for taking the suggestions Tree-SHA512: ebfd716a4fed9e8c2dea3d2181ba6a6171b06718d29ac2324c67b7a30b374d199f7e1739f91ab5d036be172d0479de9bc89c32263ee62143c0338b9b622d0cca
Diffstat (limited to 'src')
-rw-r--r--src/rpc/mempool.cpp71
1 files changed, 47 insertions, 24 deletions
diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
index 6cbc96c5ec..04bd7fa928 100644
--- a/src/rpc/mempool.cpp
+++ b/src/rpc/mempool.cpp
@@ -818,7 +818,7 @@ static RPCHelpMan submitpackage()
return RPCHelpMan{"submitpackage",
"Submit a package of raw transactions (serialized, hex-encoded) to local node.\n"
"The package must consist of a child with its parents, and none of the parents may depend on one another.\n"
- "The package will be validated according to consensus and mempool policy rules. If all transactions pass, they will be accepted to mempool.\n"
+ "The package will be validated according to consensus and mempool policy rules. If any transaction passes, it will be accepted to mempool.\n"
"This RPC is experimental and the interface may be unstable. Refer to doc/policy/packages.md for documentation on package policies.\n"
"Warning: successful submission does not mean the transactions will propagate throughout the network.\n"
,
@@ -832,19 +832,21 @@ static RPCHelpMan submitpackage()
RPCResult{
RPCResult::Type::OBJ, "", "",
{
+ {RPCResult::Type::STR, "package_msg", "The transaction package result message. \"success\" indicates all transactions were accepted into or are already in the mempool."},
{RPCResult::Type::OBJ_DYN, "tx-results", "transaction results keyed by wtxid",
{
{RPCResult::Type::OBJ, "wtxid", "transaction wtxid", {
{RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
{RPCResult::Type::STR_HEX, "other-wtxid", /*optional=*/true, "The wtxid of a different transaction with the same txid but different witness found in the mempool. This means the submitted transaction was ignored."},
- {RPCResult::Type::NUM, "vsize", "Virtual transaction size as defined in BIP 141."},
- {RPCResult::Type::OBJ, "fees", "Transaction fees", {
+ {RPCResult::Type::NUM, "vsize", /*optional=*/true, "Sigops-adjusted virtual transaction size."},
+ {RPCResult::Type::OBJ, "fees", /*optional=*/true, "Transaction fees", {
{RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT},
{RPCResult::Type::STR_AMOUNT, "effective-feerate", /*optional=*/true, "if the transaction was not already in the mempool, the effective feerate in " + CURRENCY_UNIT + " per KvB. For example, the package feerate and/or feerate with modified fees from prioritisetransaction."},
{RPCResult::Type::ARR, "effective-includes", /*optional=*/true, "if effective-feerate is provided, the wtxids of the transactions whose fees and vsizes are included in effective-feerate.",
{{RPCResult::Type::STR_HEX, "", "transaction wtxid in hex"},
}},
}},
+ {RPCResult::Type::STR, "error", /*optional=*/true, "The transaction error string, if it was rejected by the mempool"},
}}
}},
{RPCResult::Type::ARR, "replaced-transactions", /*optional=*/true, "List of txids of replaced transactions",
@@ -884,57 +886,77 @@ static RPCHelpMan submitpackage()
Chainstate& chainstate = EnsureChainman(node).ActiveChainstate();
const auto package_result = WITH_LOCK(::cs_main, return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/ false));
- // First catch any errors.
+ std::string package_msg = "success";
+
+ // First catch package-wide errors, continue if we can
switch(package_result.m_state.GetResult()) {
- case PackageValidationResult::PCKG_RESULT_UNSET: break;
- case PackageValidationResult::PCKG_POLICY:
+ case PackageValidationResult::PCKG_RESULT_UNSET:
{
- throw JSONRPCTransactionError(TransactionError::INVALID_PACKAGE,
- package_result.m_state.GetRejectReason());
+ // Belt-and-suspenders check; everything should be successful here
+ CHECK_NONFATAL(package_result.m_tx_results.size() == txns.size());
+ for (const auto& tx : txns) {
+ CHECK_NONFATAL(mempool.exists(GenTxid::Txid(tx->GetHash())));
+ }
+ break;
}
case PackageValidationResult::PCKG_MEMPOOL_ERROR:
{
+ // This only happens with internal bug; user should stop and report
throw JSONRPCTransactionError(TransactionError::MEMPOOL_ERROR,
package_result.m_state.GetRejectReason());
}
+ case PackageValidationResult::PCKG_POLICY:
case PackageValidationResult::PCKG_TX:
{
- for (const auto& tx : txns) {
- auto it = package_result.m_tx_results.find(tx->GetWitnessHash());
- if (it != package_result.m_tx_results.end() && it->second.m_state.IsInvalid()) {
- throw JSONRPCTransactionError(TransactionError::MEMPOOL_REJECTED,
- strprintf("%s failed: %s", tx->GetHash().ToString(), it->second.m_state.GetRejectReason()));
- }
- }
- // If a PCKG_TX error was returned, there must have been an invalid transaction.
- NONFATAL_UNREACHABLE();
+ // Package-wide error we want to return, but we also want to return individual responses
+ package_msg = package_result.m_state.GetRejectReason();
+ CHECK_NONFATAL(package_result.m_tx_results.size() == txns.size() ||
+ package_result.m_tx_results.empty());
+ break;
}
}
+
size_t num_broadcast{0};
for (const auto& tx : txns) {
+ // We don't want to re-submit the txn for validation in BroadcastTransaction
+ if (!mempool.exists(GenTxid::Txid(tx->GetHash()))) {
+ continue;
+ }
+
+ // We do not expect an error here; we are only broadcasting things already/still in mempool
std::string err_string;
const auto err = BroadcastTransaction(node, tx, err_string, /*max_tx_fee=*/0, /*relay=*/true, /*wait_callback=*/true);
if (err != TransactionError::OK) {
throw JSONRPCTransactionError(err,
- strprintf("transaction broadcast failed: %s (all transactions were submitted, %d transactions were broadcast successfully)",
+ strprintf("transaction broadcast failed: %s (%d transactions were broadcast successfully)",
err_string, num_broadcast));
}
num_broadcast++;
}
+
UniValue rpc_result{UniValue::VOBJ};
+ rpc_result.pushKV("package_msg", package_msg);
UniValue tx_result_map{UniValue::VOBJ};
std::set<uint256> replaced_txids;
for (const auto& tx : txns) {
- auto it = package_result.m_tx_results.find(tx->GetWitnessHash());
- CHECK_NONFATAL(it != package_result.m_tx_results.end());
UniValue result_inner{UniValue::VOBJ};
result_inner.pushKV("txid", tx->GetHash().GetHex());
+ auto it = package_result.m_tx_results.find(tx->GetWitnessHash());
+ if (it == package_result.m_tx_results.end()) {
+ // No results, report error and continue
+ result_inner.pushKV("error", "unevaluated");
+ continue;
+ }
const auto& tx_result = it->second;
- if (it->second.m_result_type == MempoolAcceptResult::ResultType::DIFFERENT_WITNESS) {
+ switch(it->second.m_result_type) {
+ case MempoolAcceptResult::ResultType::DIFFERENT_WITNESS:
result_inner.pushKV("other-wtxid", it->second.m_other_wtxid.value().GetHex());
- }
- if (it->second.m_result_type == MempoolAcceptResult::ResultType::VALID ||
- it->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY) {
+ break;
+ case MempoolAcceptResult::ResultType::INVALID:
+ result_inner.pushKV("error", it->second.m_state.ToString());
+ break;
+ case MempoolAcceptResult::ResultType::VALID:
+ case MempoolAcceptResult::ResultType::MEMPOOL_ENTRY:
result_inner.pushKV("vsize", int64_t{it->second.m_vsize.value()});
UniValue fees(UniValue::VOBJ);
fees.pushKV("base", ValueFromAmount(it->second.m_base_fees.value()));
@@ -955,6 +977,7 @@ static RPCHelpMan submitpackage()
replaced_txids.insert(ptx->GetHash());
}
}
+ break;
}
tx_result_map.pushKV(tx->GetWitnessHash().GetHex(), result_inner);
}