-
Notifications
You must be signed in to change notification settings - Fork 0
Merge bitcoin/bitcoin#28764: Fuzz: Check individual and package transaction invariants #986
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,116 @@ | ||||||||||||||||||
| // Copyright (c) 2022 The Bitcoin Core developers | ||||||||||||||||||
| // Distributed under the MIT software license, see the accompanying | ||||||||||||||||||
| // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||||||||||||||||
|
|
||||||||||||||||||
| #include <test/util/txmempool.h> | ||||||||||||||||||
|
|
||||||||||||||||||
| #include <chainparams.h> | ||||||||||||||||||
| #include <node/context.h> | ||||||||||||||||||
| #include <node/mempool_args.h> | ||||||||||||||||||
| #include <txmempool.h> | ||||||||||||||||||
| #include <util/check.h> | ||||||||||||||||||
| #include <util/time.h> | ||||||||||||||||||
| #include <util/translation.h> | ||||||||||||||||||
| #include <validation.h> | ||||||||||||||||||
|
|
||||||||||||||||||
| using node::NodeContext; | ||||||||||||||||||
|
|
||||||||||||||||||
| CTxMemPool::Options MemPoolOptionsForTest(const NodeContext& node) | ||||||||||||||||||
| { | ||||||||||||||||||
| CTxMemPool::Options mempool_opts{ | ||||||||||||||||||
| .estimator = node.fee_estimator.get(), | ||||||||||||||||||
| // Default to always checking mempool regardless of | ||||||||||||||||||
| // chainparams.DefaultConsistencyChecks for tests | ||||||||||||||||||
| .check_ratio = 1, | ||||||||||||||||||
| }; | ||||||||||||||||||
| const auto result{ApplyArgsManOptions(*node.args, ::Params(), mempool_opts)}; | ||||||||||||||||||
| Assert(result); | ||||||||||||||||||
| return mempool_opts; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction& tx) const | ||||||||||||||||||
| { | ||||||||||||||||||
| return FromTx(MakeTransactionRef(tx)); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransactionRef& tx) const | ||||||||||||||||||
| { | ||||||||||||||||||
| return CTxMemPoolEntry{tx, nFee, TicksSinceEpoch<std::chrono::seconds>(time), nHeight, m_sequence, spendsCoinbase, sigOpCost, lp}; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| std::optional<std::string> CheckPackageMempoolAcceptResult(const Package& txns, | ||||||||||||||||||
| const PackageMempoolAcceptResult& result, | ||||||||||||||||||
| bool expect_valid, | ||||||||||||||||||
| const CTxMemPool* mempool) | ||||||||||||||||||
| { | ||||||||||||||||||
| if (expect_valid) { | ||||||||||||||||||
| if (result.m_state.IsInvalid()) { | ||||||||||||||||||
| return strprintf("Package validation unexpectedly failed: %s", result.m_state.ToString()); | ||||||||||||||||||
| } | ||||||||||||||||||
| } else { | ||||||||||||||||||
| if (result.m_state.IsValid()) { | ||||||||||||||||||
| strprintf("Package validation unexpectedly succeeded. %s", result.m_state.ToString()); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| if (result.m_state.GetResult() != PackageValidationResult::PCKG_POLICY && txns.size() != result.m_tx_results.size()) { | ||||||||||||||||||
| strprintf("txns size %u does not match tx results size %u", txns.size(), result.m_tx_results.size()); | ||||||||||||||||||
| } | ||||||||||||||||||
| for (const auto& tx : txns) { | ||||||||||||||||||
| const auto& wtxid = tx->GetWitnessHash(); | ||||||||||||||||||
| if (result.m_tx_results.count(wtxid) == 0) { | ||||||||||||||||||
| return strprintf("result not found for tx %s", wtxid.ToString()); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| const auto& atmp_result = result.m_tx_results.at(wtxid); | ||||||||||||||||||
| const bool valid{atmp_result.m_result_type == MempoolAcceptResult::ResultType::VALID}; | ||||||||||||||||||
| if (expect_valid && atmp_result.m_state.IsInvalid()) { | ||||||||||||||||||
| return strprintf("tx %s unexpectedly failed: %s", wtxid.ToString(), atmp_result.m_state.ToString()); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| //m_replaced_transactions should exist iff the result was VALID | ||||||||||||||||||
| if (atmp_result.m_replaced_transactions.has_value() != valid) { | ||||||||||||||||||
| return strprintf("tx %s result should %shave m_replaced_transactions", | ||||||||||||||||||
| wtxid.ToString(), valid ? "" : "not "); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // m_vsize and m_base_fees should exist iff the result was VALID or MEMPOOL_ENTRY | ||||||||||||||||||
| const bool mempool_entry{atmp_result.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY}; | ||||||||||||||||||
| if (atmp_result.m_base_fees.has_value() != (valid || mempool_entry)) { | ||||||||||||||||||
| return strprintf("tx %s result should %shave m_base_fees", wtxid.ToString(), valid || mempool_entry ? "" : "not "); | ||||||||||||||||||
| } | ||||||||||||||||||
| if (atmp_result.m_vsize.has_value() != (valid || mempool_entry)) { | ||||||||||||||||||
| return strprintf("tx %s result should %shave m_vsize", wtxid.ToString(), valid || mempool_entry ? "" : "not "); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // m_other_wtxid should exist iff the result was DIFFERENT_WITNESS | ||||||||||||||||||
| const bool diff_witness{atmp_result.m_result_type == MempoolAcceptResult::ResultType::DIFFERENT_WITNESS}; | ||||||||||||||||||
| if (atmp_result.m_other_wtxid.has_value() != diff_witness) { | ||||||||||||||||||
| return strprintf("tx %s result should %shave m_other_wtxid", wtxid.ToString(), diff_witness ? "" : "not "); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // m_effective_feerate and m_wtxids_fee_calculations should exist iff the result was valid | ||||||||||||||||||
| if (atmp_result.m_effective_feerate.has_value() != valid) { | ||||||||||||||||||
| return strprintf("tx %s result should %shave m_effective_feerate", | ||||||||||||||||||
| wtxid.ToString(), valid ? "" : "not "); | ||||||||||||||||||
| } | ||||||||||||||||||
| if (atmp_result.m_wtxids_fee_calculations.has_value() != valid) { | ||||||||||||||||||
| return strprintf("tx %s result should %shave m_effective_feerate", | ||||||||||||||||||
| wtxid.ToString(), valid ? "" : "not "); | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+96
to
+99
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix copy-paste error in error message. The error message incorrectly references if (atmp_result.m_wtxids_fee_calculations.has_value() != valid) {
- return strprintf("tx %s result should %shave m_effective_feerate",
+ return strprintf("tx %s result should %shave m_wtxids_fee_calculations",
wtxid.ToString(), valid ? "" : "not ");
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
| if (mempool) { | ||||||||||||||||||
| // The tx by txid should be in the mempool iff the result was not INVALID. | ||||||||||||||||||
| const bool txid_in_mempool{atmp_result.m_result_type != MempoolAcceptResult::ResultType::INVALID}; | ||||||||||||||||||
| if (mempool->exists(GenTxid::Txid(tx->GetHash())) != txid_in_mempool) { | ||||||||||||||||||
| strprintf("tx %s should %sbe in mempool", wtxid.ToString(), txid_in_mempool ? "" : "not "); | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+104
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Missing return statement for mempool existence check. if (mempool->exists(GenTxid::Txid(tx->GetHash())) != txid_in_mempool) {
- strprintf("tx %s should %sbe in mempool", wtxid.ToString(), txid_in_mempool ? "" : "not ");
+ return strprintf("tx %s should %sbe in mempool", wtxid.ToString(), txid_in_mempool ? "" : "not ");
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| // Additionally, if the result was DIFFERENT_WITNESS, we shouldn't be able to find the tx in mempool by wtxid. | ||||||||||||||||||
| if (tx->HasWitness() && atmp_result.m_result_type == MempoolAcceptResult::ResultType::DIFFERENT_WITNESS) { | ||||||||||||||||||
| if (mempool->exists(GenTxid::Wtxid(wtxid))) { | ||||||||||||||||||
| strprintf("wtxid %s should not be in mempool", wtxid.ToString()); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| return std::nullopt; | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| // Copyright (c) 2022 The Bitcoin Core developers | ||
| // Distributed under the MIT software license, see the accompanying | ||
| // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
|
|
||
| #ifndef BITCOIN_TEST_UTIL_TXMEMPOOL_H | ||
| #define BITCOIN_TEST_UTIL_TXMEMPOOL_H | ||
|
|
||
| #include <policy/packages.h> | ||
| #include <txmempool.h> | ||
| #include <util/time.h> | ||
|
|
||
| namespace node { | ||
| struct NodeContext; | ||
| } | ||
| struct PackageMempoolAcceptResult; | ||
|
|
||
| CTxMemPool::Options MemPoolOptionsForTest(const node::NodeContext& node); | ||
|
|
||
| struct TestMemPoolEntryHelper { | ||
| // Default values | ||
| CAmount nFee{0}; | ||
| NodeSeconds time{}; | ||
| unsigned int nHeight{1}; | ||
| uint64_t m_sequence{0}; | ||
| bool spendsCoinbase{false}; | ||
| unsigned int sigOpCost{4}; | ||
| LockPoints lp; | ||
|
|
||
| CTxMemPoolEntry FromTx(const CMutableTransaction& tx) const; | ||
| CTxMemPoolEntry FromTx(const CTransactionRef& tx) const; | ||
|
|
||
| // Change the default value | ||
| TestMemPoolEntryHelper& Fee(CAmount _fee) { nFee = _fee; return *this; } | ||
| TestMemPoolEntryHelper& Time(NodeSeconds tp) { time = tp; return *this; } | ||
| TestMemPoolEntryHelper& Height(unsigned int _height) { nHeight = _height; return *this; } | ||
| TestMemPoolEntryHelper& Sequence(uint64_t _seq) { m_sequence = _seq; return *this; } | ||
| TestMemPoolEntryHelper& SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return *this; } | ||
| TestMemPoolEntryHelper& SigOpsCost(unsigned int _sigopsCost) { sigOpCost = _sigopsCost; return *this; } | ||
| }; | ||
|
|
||
| /** Check expected properties for every PackageMempoolAcceptResult, regardless of value. Returns | ||
| * a string if an error occurs with error populated, nullopt otherwise. If mempool is provided, | ||
| * checks that the expected transactions are in mempool (this should be set to nullptr for a test_accept). | ||
| */ | ||
| std::optional<std::string> CheckPackageMempoolAcceptResult(const Package& txns, | ||
| const PackageMempoolAcceptResult& result, | ||
| bool expect_valid, | ||
| const CTxMemPool* mempool); | ||
| #endif // BITCOIN_TEST_UTIL_TXMEMPOOL_H | ||
|
Comment on lines
+1
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) New test utility file looks good, but note the file operation consideration. This new test utility header is well-structured and provides appropriate testing helpers. However, according to the PR objectives, Bitcoin modified existing test utility files while this PR creates new ones. Per the PR comments, this adaptation was necessary because Dash lacks these test utility files. The implementation itself is correct and follows proper C++ patterns. 🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Missing return statements in error paths.
The error handling paths are missing return statements, causing the function to continue execution after detecting errors.
Apply this fix:
} else { if (result.m_state.IsValid()) { - strprintf("Package validation unexpectedly succeeded. %s", result.m_state.ToString()); + return strprintf("Package validation unexpectedly succeeded. %s", result.m_state.ToString()); } } if (result.m_state.GetResult() != PackageValidationResult::PCKG_POLICY && txns.size() != result.m_tx_results.size()) { - strprintf("txns size %u does not match tx results size %u", txns.size(), result.m_tx_results.size()); + return strprintf("txns size %u does not match tx results size %u", txns.size(), result.m_tx_results.size()); }🤖 Prompt for AI Agents