Merge bitcoin/bitcoin#33427: rpc: Always return per-wtxid entries in submitpackage tx-results

cad9a7fd73 rpc: Always return per-wtxid entries in submitpackage tx-results (John Moffett)

Pull request description:

  Follow-up to #28848

  When `submitpackage` produced no per-transaction result for a member, the RPC set `"error": "unevaluated"` but then continued without inserting the entry into `tx-results`, making it impossible for callers to know which `wtxids` were unevaluated.

  This inserts the error result before continuing, updates help text, and adjusts functional tests to expect entries for all submitted `wtxids`.

ACKs for top commit:
  instagibbs:
    ACK cad9a7fd73
  glozow:
    ACK cad9a7fd73

Tree-SHA512: 8df5c9b3d1f17aaf0311c38f028ae4b55d4c52a660f85171f105c4f65d130b14ab00698ac5d7c27403a0c37fff391c154c3ad44cc99ba4d549d9c30751b8360f
This commit is contained in:
merge-script
2025-09-23 09:36:18 -04:00
2 changed files with 22 additions and 7 deletions

View File

@@ -955,7 +955,7 @@ static RPCHelpMan submitpackage()
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_DYN, "tx-results", "The transaction results keyed by wtxid. An entry is returned for every submitted wtxid.",
{
{RPCResult::Type::OBJ, "wtxid", "transaction wtxid", {
{RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
@@ -968,7 +968,7 @@ static RPCHelpMan submitpackage()
{{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::STR, "error", /*optional=*/true, "Error string if rejected from mempool, or \"package-not-validated\" when the package aborts before any per-tx processing."},
}}
}},
{RPCResult::Type::ARR, "replaced-transactions", /*optional=*/true, "List of txids of replaced transactions",
@@ -1082,10 +1082,15 @@ static RPCHelpMan submitpackage()
for (const auto& tx : txns) {
UniValue result_inner{UniValue::VOBJ};
result_inner.pushKV("txid", tx->GetHash().GetHex());
const auto wtxid_hex = tx->GetWitnessHash().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");
// No per-tx result for this wtxid
// Current invariant: per-tx results are all-or-none (every member or empty on package abort).
// If any exist yet this one is missing, it's an unexpected partial map.
CHECK_NONFATAL(package_result.m_tx_results.empty());
result_inner.pushKV("error", "package-not-validated");
tx_result_map.pushKV(wtxid_hex, std::move(result_inner));
continue;
}
const auto& tx_result = it->second;
@@ -1118,7 +1123,7 @@ static RPCHelpMan submitpackage()
}
break;
}
tx_result_map.pushKV(tx->GetWitnessHash().GetHex(), std::move(result_inner));
tx_result_map.pushKV(wtxid_hex, std::move(result_inner));
}
rpc_result.pushKV("tx-results", std::move(tx_result_map));
UniValue replaced_list(UniValue::VARR);

View File

@@ -263,13 +263,23 @@ class RPCPackagesTest(BitcoinTestFramework):
])
submitres = node.submitpackage([tx1["hex"], tx2["hex"], tx_child["hex"]])
assert_equal(submitres, {'package_msg': 'conflict-in-package', 'tx-results': {}, 'replaced-transactions': []})
expected = {
tx1["wtxid"]: {"txid": tx1["txid"], "error": "package-not-validated"},
tx2["wtxid"]: {"txid": tx2["txid"], "error": "package-not-validated"},
tx_child["wtxid"]: {"txid": tx_child["txid"], "error": "package-not-validated"},
}
assert_equal(submitres, {"package_msg": "conflict-in-package", "tx-results": expected,"replaced-transactions": []})
# Submit tx1 to mempool, then try the same package again
node.sendrawtransaction(tx1["hex"])
submitres = node.submitpackage([tx1["hex"], tx2["hex"], tx_child["hex"]])
assert_equal(submitres, {'package_msg': 'conflict-in-package', 'tx-results': {}, 'replaced-transactions': []})
expected = {
tx1["wtxid"]: {"txid": tx1["txid"], "error": "package-not-validated"},
tx2["wtxid"]: {"txid": tx2["txid"], "error": "package-not-validated"},
tx_child["wtxid"]: {"txid": tx_child["txid"], "error": "package-not-validated"},
}
assert_equal(submitres, {"package_msg": "conflict-in-package", "tx-results": expected,"replaced-transactions": []})
assert tx_child["txid"] not in node.getrawmempool()
# without the in-mempool ancestor tx1 included in the call, tx2 can be submitted, but