Skip to content

Commit cad9a7f

Browse files
committed
rpc: Always return per-wtxid entries in submitpackage tx-results
When submitpackage produced no per-transaction result for a member, the RPC previously set "error": "unevaluated" but then continued without inserting the entry into tx-results, making it impossible for callers to know which wtxids were unevaluated. Insert the placeholder result before continuing, update help text, and adjust functional tests to expect entries for all submitted wtxids.
1 parent 74fa028 commit cad9a7f

File tree

2 files changed

+22
-7
lines changed

2 files changed

+22
-7
lines changed

src/rpc/mempool.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,7 @@ static RPCHelpMan submitpackage()
955955
RPCResult::Type::OBJ, "", "",
956956
{
957957
{RPCResult::Type::STR, "package_msg", "The transaction package result message. \"success\" indicates all transactions were accepted into or are already in the mempool."},
958-
{RPCResult::Type::OBJ_DYN, "tx-results", "transaction results keyed by wtxid",
958+
{RPCResult::Type::OBJ_DYN, "tx-results", "The transaction results keyed by wtxid. An entry is returned for every submitted wtxid.",
959959
{
960960
{RPCResult::Type::OBJ, "wtxid", "transaction wtxid", {
961961
{RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
@@ -968,7 +968,7 @@ static RPCHelpMan submitpackage()
968968
{{RPCResult::Type::STR_HEX, "", "transaction wtxid in hex"},
969969
}},
970970
}},
971-
{RPCResult::Type::STR, "error", /*optional=*/true, "The transaction error string, if it was rejected by the mempool"},
971+
{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."},
972972
}}
973973
}},
974974
{RPCResult::Type::ARR, "replaced-transactions", /*optional=*/true, "List of txids of replaced transactions",
@@ -1082,10 +1082,15 @@ static RPCHelpMan submitpackage()
10821082
for (const auto& tx : txns) {
10831083
UniValue result_inner{UniValue::VOBJ};
10841084
result_inner.pushKV("txid", tx->GetHash().GetHex());
1085+
const auto wtxid_hex = tx->GetWitnessHash().GetHex();
10851086
auto it = package_result.m_tx_results.find(tx->GetWitnessHash());
10861087
if (it == package_result.m_tx_results.end()) {
1087-
// No results, report error and continue
1088-
result_inner.pushKV("error", "unevaluated");
1088+
// No per-tx result for this wtxid
1089+
// Current invariant: per-tx results are all-or-none (every member or empty on package abort).
1090+
// If any exist yet this one is missing, it's an unexpected partial map.
1091+
CHECK_NONFATAL(package_result.m_tx_results.empty());
1092+
result_inner.pushKV("error", "package-not-validated");
1093+
tx_result_map.pushKV(wtxid_hex, std::move(result_inner));
10891094
continue;
10901095
}
10911096
const auto& tx_result = it->second;
@@ -1118,7 +1123,7 @@ static RPCHelpMan submitpackage()
11181123
}
11191124
break;
11201125
}
1121-
tx_result_map.pushKV(tx->GetWitnessHash().GetHex(), std::move(result_inner));
1126+
tx_result_map.pushKV(wtxid_hex, std::move(result_inner));
11221127
}
11231128
rpc_result.pushKV("tx-results", std::move(tx_result_map));
11241129
UniValue replaced_list(UniValue::VARR);

test/functional/rpc_packages.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,23 @@ def test_conflicting(self):
263263
])
264264

265265
submitres = node.submitpackage([tx1["hex"], tx2["hex"], tx_child["hex"]])
266-
assert_equal(submitres, {'package_msg': 'conflict-in-package', 'tx-results': {}, 'replaced-transactions': []})
266+
expected = {
267+
tx1["wtxid"]: {"txid": tx1["txid"], "error": "package-not-validated"},
268+
tx2["wtxid"]: {"txid": tx2["txid"], "error": "package-not-validated"},
269+
tx_child["wtxid"]: {"txid": tx_child["txid"], "error": "package-not-validated"},
270+
}
271+
assert_equal(submitres, {"package_msg": "conflict-in-package", "tx-results": expected,"replaced-transactions": []})
267272

268273
# Submit tx1 to mempool, then try the same package again
269274
node.sendrawtransaction(tx1["hex"])
270275

271276
submitres = node.submitpackage([tx1["hex"], tx2["hex"], tx_child["hex"]])
272-
assert_equal(submitres, {'package_msg': 'conflict-in-package', 'tx-results': {}, 'replaced-transactions': []})
277+
expected = {
278+
tx1["wtxid"]: {"txid": tx1["txid"], "error": "package-not-validated"},
279+
tx2["wtxid"]: {"txid": tx2["txid"], "error": "package-not-validated"},
280+
tx_child["wtxid"]: {"txid": tx_child["txid"], "error": "package-not-validated"},
281+
}
282+
assert_equal(submitres, {"package_msg": "conflict-in-package", "tx-results": expected,"replaced-transactions": []})
273283
assert tx_child["txid"] not in node.getrawmempool()
274284

275285
# without the in-mempool ancestor tx1 included in the call, tx2 can be submitted, but

0 commit comments

Comments
 (0)