Skip to content

Commit 264f9ef

Browse files
committed
[validation] return MempoolAcceptResult for every tx on PCKG_TX failure
This makes the interface more predictable and useful. The caller understands one or more transactions failed, and can learn what happened with each transaction. We already have this information, so we might as well return it. It doesn't make sense to do this for other PackageValidationResult values because: - PCKG_RESULT_UNSET: this means everything succeeded, so the individual failures are no longer accurate. - PCKG_MEMPOOL_ERROR: something went wrong with the mempool logic; transaction failures might not be meaningful. - PCKG_POLICY: this means something was wrong with the package as a whole. The caller should use the PackageValidationState to find the error, rather than looking at individual MempoolAcceptResults.
1 parent dae81e0 commit 264f9ef

File tree

2 files changed

+43
-4
lines changed

2 files changed

+43
-4
lines changed

src/test/txpackage_tests.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup)
9090
const auto result_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, {tx_parent, tx_child}, /*test_accept=*/true);
9191
BOOST_CHECK_MESSAGE(result_parent_child.m_state.IsValid(),
9292
"Package validation unexpectedly failed: " << result_parent_child.m_state.GetRejectReason());
93+
BOOST_CHECK(result_parent_child.m_tx_results.size() == 2);
9394
auto it_parent = result_parent_child.m_tx_results.find(tx_parent->GetWitnessHash());
9495
auto it_child = result_parent_child.m_tx_results.find(tx_child->GetWitnessHash());
9596
BOOST_CHECK(it_parent != result_parent_child.m_tx_results.end());
@@ -112,6 +113,7 @@ BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup)
112113
BOOST_CHECK(result_single_large.m_state.IsInvalid());
113114
BOOST_CHECK_EQUAL(result_single_large.m_state.GetResult(), PackageValidationResult::PCKG_TX);
114115
BOOST_CHECK_EQUAL(result_single_large.m_state.GetRejectReason(), "transaction failed");
116+
BOOST_CHECK(result_single_large.m_tx_results.size() == 1);
115117
auto it_giant_tx = result_single_large.m_tx_results.find(giant_ptx->GetWitnessHash());
116118
BOOST_CHECK(it_giant_tx != result_single_large.m_tx_results.end());
117119
BOOST_CHECK_EQUAL(it_giant_tx->second.m_state.GetRejectReason(), "tx-size");
@@ -291,9 +293,15 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
291293
BOOST_CHECK(result_quit_early.m_state.IsInvalid());
292294
BOOST_CHECK_EQUAL(result_quit_early.m_state.GetResult(), PackageValidationResult::PCKG_TX);
293295
BOOST_CHECK(!result_quit_early.m_tx_results.empty());
296+
BOOST_CHECK_EQUAL(result_quit_early.m_tx_results.size(), 2);
294297
auto it_parent = result_quit_early.m_tx_results.find(tx_parent_invalid->GetWitnessHash());
298+
auto it_child = result_quit_early.m_tx_results.find(tx_child->GetWitnessHash());
295299
BOOST_CHECK(it_parent != result_quit_early.m_tx_results.end());
300+
BOOST_CHECK(it_child != result_quit_early.m_tx_results.end());
296301
BOOST_CHECK_EQUAL(it_parent->second.m_state.GetResult(), TxValidationResult::TX_WITNESS_MUTATED);
302+
BOOST_CHECK_EQUAL(it_parent->second.m_state.GetRejectReason(), "bad-witness-nonstandard");
303+
BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MISSING_INPUTS);
304+
BOOST_CHECK_EQUAL(it_child->second.m_state.GetRejectReason(), "bad-txns-inputs-missingorspent");
297305
}
298306

299307
// Child with missing parent.
@@ -317,6 +325,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
317325
expected_pool_size += 2;
318326
BOOST_CHECK_MESSAGE(submit_parent_child.m_state.IsValid(),
319327
"Package validation unexpectedly failed: " << submit_parent_child.m_state.GetRejectReason());
328+
BOOST_CHECK_EQUAL(submit_parent_child.m_tx_results.size(), package_parent_child.size());
320329
auto it_parent = submit_parent_child.m_tx_results.find(tx_parent->GetWitnessHash());
321330
auto it_child = submit_parent_child.m_tx_results.find(tx_child->GetWitnessHash());
322331
BOOST_CHECK(it_parent != submit_parent_child.m_tx_results.end());
@@ -341,6 +350,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
341350
package_parent_child, /*test_accept=*/false);
342351
BOOST_CHECK_MESSAGE(submit_deduped.m_state.IsValid(),
343352
"Package validation unexpectedly failed: " << submit_deduped.m_state.GetRejectReason());
353+
BOOST_CHECK_EQUAL(submit_deduped.m_tx_results.size(), package_parent_child.size());
344354
auto it_parent_deduped = submit_deduped.m_tx_results.find(tx_parent->GetWitnessHash());
345355
auto it_child_deduped = submit_deduped.m_tx_results.find(tx_child->GetWitnessHash());
346356
BOOST_CHECK(it_parent_deduped != submit_deduped.m_tx_results.end());
@@ -415,6 +425,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
415425
{ptx_parent, ptx_child1}, /*test_accept=*/false);
416426
BOOST_CHECK_MESSAGE(submit_witness1.m_state.IsValid(),
417427
"Package validation unexpectedly failed: " << submit_witness1.m_state.GetRejectReason());
428+
BOOST_CHECK_EQUAL(submit_witness1.m_tx_results.size(), 2);
418429
auto it_parent1 = submit_witness1.m_tx_results.find(ptx_parent->GetWitnessHash());
419430
auto it_child1 = submit_witness1.m_tx_results.find(ptx_child1->GetWitnessHash());
420431
BOOST_CHECK(it_parent1 != submit_witness1.m_tx_results.end());
@@ -432,6 +443,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
432443
{ptx_parent, ptx_child2}, /*test_accept=*/false);
433444
BOOST_CHECK_MESSAGE(submit_witness2.m_state.IsValid(),
434445
"Package validation unexpectedly failed: " << submit_witness2.m_state.GetRejectReason());
446+
BOOST_CHECK_EQUAL(submit_witness2.m_tx_results.size(), 2);
435447
auto it_parent2_deduped = submit_witness2.m_tx_results.find(ptx_parent->GetWitnessHash());
436448
auto it_child2 = submit_witness2.m_tx_results.find(ptx_child2->GetWitnessHash());
437449
BOOST_CHECK(it_parent2_deduped != submit_witness2.m_tx_results.end());
@@ -449,6 +461,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
449461
{ptx_parent, ptx_child1}, /*test_accept=*/false);
450462
BOOST_CHECK_MESSAGE(submit_segwit_dedup.m_state.IsValid(),
451463
"Package validation unexpectedly failed: " << submit_segwit_dedup.m_state.GetRejectReason());
464+
BOOST_CHECK_EQUAL(submit_segwit_dedup.m_tx_results.size(), 2);
452465
auto it_parent_dup = submit_segwit_dedup.m_tx_results.find(ptx_parent->GetWitnessHash());
453466
auto it_child_dup = submit_segwit_dedup.m_tx_results.find(ptx_child1->GetWitnessHash());
454467
BOOST_CHECK(it_parent_dup->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
@@ -477,6 +490,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
477490
{ptx_child2, ptx_grandchild}, /*test_accept=*/false);
478491
BOOST_CHECK_MESSAGE(submit_spend_ignored.m_state.IsValid(),
479492
"Package validation unexpectedly failed: " << submit_spend_ignored.m_state.GetRejectReason());
493+
BOOST_CHECK_EQUAL(submit_spend_ignored.m_tx_results.size(), 2);
480494
auto it_child2_ignored = submit_spend_ignored.m_tx_results.find(ptx_child2->GetWitnessHash());
481495
auto it_grandchild = submit_spend_ignored.m_tx_results.find(ptx_grandchild->GetWitnessHash());
482496
BOOST_CHECK(it_child2_ignored != submit_spend_ignored.m_tx_results.end());
@@ -577,6 +591,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
577591
{
578592
const auto mixed_result = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_mixed, false);
579593
BOOST_CHECK_MESSAGE(mixed_result.m_state.IsValid(), mixed_result.m_state.GetRejectReason());
594+
BOOST_CHECK_EQUAL(mixed_result.m_tx_results.size(), package_mixed.size());
580595
auto it_parent1 = mixed_result.m_tx_results.find(ptx_parent1->GetWitnessHash());
581596
auto it_parent2 = mixed_result.m_tx_results.find(ptx_parent2_v1->GetWitnessHash());
582597
auto it_parent3 = mixed_result.m_tx_results.find(ptx_parent3->GetWitnessHash());
@@ -648,6 +663,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
648663
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
649664
const auto submit_cpfp_deprio = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
650665
package_cpfp, /*test_accept=*/ false);
666+
BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
651667
BOOST_CHECK_MESSAGE(submit_cpfp_deprio.m_state.IsInvalid(),
652668
"Package validation unexpectedly succeeded: " << submit_cpfp_deprio.m_state.GetRejectReason());
653669
BOOST_CHECK(submit_cpfp_deprio.m_tx_results.empty());
@@ -667,6 +683,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
667683
expected_pool_size += 2;
668684
BOOST_CHECK_MESSAGE(submit_cpfp.m_state.IsValid(),
669685
"Package validation unexpectedly failed: " << submit_cpfp.m_state.GetRejectReason());
686+
BOOST_CHECK_EQUAL(submit_cpfp.m_tx_results.size(), package_cpfp.size());
670687
auto it_parent = submit_cpfp.m_tx_results.find(tx_parent->GetWitnessHash());
671688
auto it_child = submit_cpfp.m_tx_results.find(tx_child->GetWitnessHash());
672689
BOOST_CHECK(it_parent != submit_cpfp.m_tx_results.end());
@@ -736,6 +753,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
736753
"Package validation unexpectedly failed" << submit_prioritised_package.m_state.GetRejectReason());
737754
const CFeeRate expected_feerate(1 * COIN + 200,
738755
GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap));
756+
BOOST_CHECK_EQUAL(submit_prioritised_package.m_tx_results.size(), package_still_too_low.size());
739757
auto it_parent = submit_prioritised_package.m_tx_results.find(tx_parent_cheap->GetWitnessHash());
740758
auto it_child = submit_prioritised_package.m_tx_results.find(tx_child_cheap->GetWitnessHash());
741759
BOOST_CHECK(it_parent != submit_prioritised_package.m_tx_results.end());

src/validation.cpp

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,6 +1363,11 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
13631363
// the new transactions. This ensures we don't double-count transaction counts and sizes when
13641364
// checking ancestor/descendant limits, or double-count transaction fees for fee-related policy.
13651365
ATMPArgs single_args = ATMPArgs::SingleInPackageAccept(args);
1366+
// Results from individual validation. "Nonfinal" because if a transaction fails by itself but
1367+
// succeeds later (i.e. when evaluated with a fee-bumping child), the result changes (though not
1368+
// reflected in this map). If a transaction fails more than once, we want to return the first
1369+
// result, when it was considered on its own. So changes will only be from invalid -> valid.
1370+
std::map<uint256, MempoolAcceptResult> individual_results_nonfinal;
13661371
bool quit_early{false};
13671372
std::vector<CTransactionRef> txns_package_eval;
13681373
for (const auto& tx : package) {
@@ -1410,22 +1415,38 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
14101415
// some of them may still be valid.
14111416
quit_early = true;
14121417
package_state_quit_early.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
1413-
results_final.emplace(wtxid, single_res);
1418+
individual_results_nonfinal.emplace(wtxid, single_res);
14141419
} else {
1420+
individual_results_nonfinal.emplace(wtxid, single_res);
14151421
txns_package_eval.push_back(tx);
14161422
}
14171423
}
14181424
}
14191425

1420-
// Nothing to do if the entire package has already been submitted.
1426+
// Quit early because package validation won't change the result or the entire package has
1427+
// already been submitted.
14211428
if (quit_early || txns_package_eval.empty()) {
1429+
for (const auto& [wtxid, mempoolaccept_res] : individual_results_nonfinal) {
1430+
Assume(results_final.emplace(wtxid, mempoolaccept_res).second);
1431+
Assume(mempoolaccept_res.m_result_type == MempoolAcceptResult::ResultType::INVALID);
1432+
}
14221433
return PackageMempoolAcceptResult(package_state_quit_early, std::move(results_final));
14231434
}
1424-
// Validate the (deduplicated) transactions as a package.
1435+
// Validate the (deduplicated) transactions as a package. Note that submission_result has its
1436+
// own PackageValidationState; package_state_quit_early is unused past this point.
14251437
auto submission_result = AcceptMultipleTransactions(txns_package_eval, args);
14261438
// Include already-in-mempool transaction results in the final result.
14271439
for (const auto& [wtxid, mempoolaccept_res] : results_final) {
1428-
submission_result.m_tx_results.emplace(wtxid, mempoolaccept_res);
1440+
Assume(submission_result.m_tx_results.emplace(wtxid, mempoolaccept_res).second);
1441+
Assume(mempoolaccept_res.m_result_type != MempoolAcceptResult::ResultType::INVALID);
1442+
}
1443+
if (submission_result.m_state.GetResult() == PackageValidationResult::PCKG_TX) {
1444+
// Package validation failed because one or more transactions failed. Provide a result for
1445+
// each transaction; if AcceptMultipleTransactions() didn't return a result for a tx,
1446+
// include the previous individual failure reason.
1447+
submission_result.m_tx_results.insert(individual_results_nonfinal.cbegin(),
1448+
individual_results_nonfinal.cend());
1449+
Assume(submission_result.m_tx_results.size() == package.size());
14291450
}
14301451
return submission_result;
14311452
}

0 commit comments

Comments
 (0)