Skip to content

Commit f1a9fd6

Browse files
committed
Merge bitcoin/bitcoin#28251: validation: fix coins disappearing mid-package evaluation
32c1dd1 [test] mempool coins disappearing mid-package evaluation (glozow) a67f460 [refactor] split setup in mempool_limit test (glozow) d086961 [test framework] add ability to spend only confirmed utxos (glozow) 3ea71fe [validation] don't LimitMempoolSize in any subpackage submissions (glozow) d227b72 [validation] return correct result when already-in-mempool tx gets evicted (glozow) 9698b81 [refactor] back-fill results in AcceptPackage (glozow) 8ad7ad3 [validation] make PackageMempoolAcceptResult members mutable (glozow) 03b87c1 [validation] add AcceptSubPackage to delegate Accept* calls and clean up m_view (glozow) 3f01a3d [CCoinsViewMemPool] track non-base coins and allow Reset (glozow) 7d7f7a1 [policy] check for duplicate txids in package (glozow) Pull request description: While we are evaluating a package, we split it into "subpackages" for evaluation (currently subpackages all have size 1 except the last one). If a subpackage has size 1, we may add a tx to mempool and call `LimitMempoolSize()`, which evicts transactions if the mempool gets full. We handle the case where the just-submitted transaction is evicted immediately, but we don't handle the case in which a transaction from a previous subpackage (either just submitted or already in mempool) is evicted. Mainly, since the coins created by the evicted transaction are cached in `m_view`, we don't realize the UTXO has disappeared until `CheckInputsFromMempoolAndCache` asserts that they exist. Also, the returned `PackageMempoolAcceptResult` reports that the transaction is in mempool even though it isn't anymore. Fix this by not calling `LimitMempoolSize()` until the very end, and editing the results map with "mempool full" if things fall out. Pointed out by instagibbs in bitcoin/bitcoin@faeed68 on top of the v3 PR. ACKs for top commit: instagibbs: reACK bitcoin/bitcoin@32c1dd1 Tree-SHA512: 61e7f69db4712e5e5bfa27d037ab66bdd97f1bf60a8d9ffb96adb1f0609af012c810d681102ee5c7baec7b5fe8cb7c304a60c63ccc445d00d86a2b7f0e7ddb90
2 parents adc0921 + 32c1dd1 commit f1a9fd6

File tree

9 files changed

+392
-95
lines changed

9 files changed

+392
-95
lines changed

src/policy/packages.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ bool CheckPackage(const Package& txns, PackageValidationState& state)
3737
std::unordered_set<uint256, SaltedTxidHasher> later_txids;
3838
std::transform(txns.cbegin(), txns.cend(), std::inserter(later_txids, later_txids.end()),
3939
[](const auto& tx) { return tx->GetHash(); });
40+
41+
// Package must not contain any duplicate transactions, which is checked by txid. This also
42+
// includes transactions with duplicate wtxids and same-txid-different-witness transactions.
43+
if (later_txids.size() != txns.size()) {
44+
return state.Invalid(PackageValidationResult::PCKG_POLICY, "package-contains-duplicates");
45+
}
46+
4047
for (const auto& tx : txns) {
4148
for (const auto& input : tx->vin) {
4249
if (later_txids.find(input.prevout.hash) != later_txids.end()) {

src/test/txpackage_tests.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ BOOST_FIXTURE_TEST_CASE(package_sanitization_tests, TestChain100Setup)
6565
BOOST_CHECK(!CheckPackage(package_too_large, state_too_large));
6666
BOOST_CHECK_EQUAL(state_too_large.GetResult(), PackageValidationResult::PCKG_POLICY);
6767
BOOST_CHECK_EQUAL(state_too_large.GetRejectReason(), "package-too-large");
68+
69+
// Packages can't contain transactions with the same txid.
70+
Package package_duplicate_txids_empty;
71+
for (auto i{0}; i < 3; ++i) {
72+
CMutableTransaction empty_tx;
73+
package_duplicate_txids_empty.emplace_back(MakeTransactionRef(empty_tx));
74+
}
75+
PackageValidationState state_duplicates;
76+
BOOST_CHECK(!CheckPackage(package_duplicate_txids_empty, state_duplicates));
77+
BOOST_CHECK_EQUAL(state_duplicates.GetResult(), PackageValidationResult::PCKG_POLICY);
78+
BOOST_CHECK_EQUAL(state_duplicates.GetRejectReason(), "package-contains-duplicates");
6879
}
6980

7081
BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup)
@@ -809,18 +820,20 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
809820
expected_pool_size += 1;
810821
BOOST_CHECK_MESSAGE(submit_rich_parent.m_state.IsInvalid(), "Package validation unexpectedly succeeded");
811822

812-
// The child would have been validated on its own and failed, then submitted as a "package" of 1.
823+
// The child would have been validated on its own and failed.
813824
BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetResult(), PackageValidationResult::PCKG_TX);
814825
BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetRejectReason(), "transaction failed");
815826

816827
auto it_parent = submit_rich_parent.m_tx_results.find(tx_parent_rich->GetWitnessHash());
828+
auto it_child = submit_rich_parent.m_tx_results.find(tx_child_poor->GetWitnessHash());
817829
BOOST_CHECK(it_parent != submit_rich_parent.m_tx_results.end());
830+
BOOST_CHECK(it_child != submit_rich_parent.m_tx_results.end());
818831
BOOST_CHECK(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
832+
BOOST_CHECK(it_child->second.m_result_type == MempoolAcceptResult::ResultType::INVALID);
819833
BOOST_CHECK(it_parent->second.m_state.GetRejectReason() == "");
820834
BOOST_CHECK_MESSAGE(it_parent->second.m_base_fees.value() == high_parent_fee,
821835
strprintf("rich parent: expected fee %s, got %s", high_parent_fee, it_parent->second.m_base_fees.value()));
822836
BOOST_CHECK(it_parent->second.m_effective_feerate == CFeeRate(high_parent_fee, GetVirtualTransactionSize(*tx_parent_rich)));
823-
auto it_child = submit_rich_parent.m_tx_results.find(tx_child_poor->GetWitnessHash());
824837
BOOST_CHECK(it_child != submit_rich_parent.m_tx_results.end());
825838
BOOST_CHECK_EQUAL(it_child->second.m_result_type, MempoolAcceptResult::ResultType::INVALID);
826839
BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MEMPOOL_POLICY);

src/txmempool.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,7 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const {
993993
if (ptx) {
994994
if (outpoint.n < ptx->vout.size()) {
995995
coin = Coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false);
996+
m_non_base_coins.emplace(outpoint);
996997
return true;
997998
} else {
998999
return false;
@@ -1005,8 +1006,14 @@ void CCoinsViewMemPool::PackageAddTransaction(const CTransactionRef& tx)
10051006
{
10061007
for (unsigned int n = 0; n < tx->vout.size(); ++n) {
10071008
m_temp_added.emplace(COutPoint(tx->GetHash(), n), Coin(tx->vout[n], MEMPOOL_HEIGHT, false));
1009+
m_non_base_coins.emplace(COutPoint(tx->GetHash(), n));
10081010
}
10091011
}
1012+
void CCoinsViewMemPool::Reset()
1013+
{
1014+
m_temp_added.clear();
1015+
m_non_base_coins.clear();
1016+
}
10101017

10111018
size_t CTxMemPool::DynamicMemoryUsage() const {
10121019
LOCK(cs);

src/txmempool.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,15 +826,27 @@ class CCoinsViewMemPool : public CCoinsViewBacked
826826
* validation, since we can access transaction outputs without submitting them to mempool.
827827
*/
828828
std::unordered_map<COutPoint, Coin, SaltedOutpointHasher> m_temp_added;
829+
830+
/**
831+
* Set of all coins that have been fetched from mempool or created using PackageAddTransaction
832+
* (not base). Used to track the origin of a coin, see GetNonBaseCoins().
833+
*/
834+
mutable std::unordered_set<COutPoint, SaltedOutpointHasher> m_non_base_coins;
829835
protected:
830836
const CTxMemPool& mempool;
831837

832838
public:
833839
CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn);
840+
/** GetCoin, returning whether it exists and is not spent. Also updates m_non_base_coins if the
841+
* coin is not fetched from base. */
834842
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
835843
/** Add the coins created by this transaction. These coins are only temporarily stored in
836844
* m_temp_added and cannot be flushed to the back end. Only used for package validation. */
837845
void PackageAddTransaction(const CTransactionRef& tx);
846+
/** Get all coins in m_non_base_coins. */
847+
std::unordered_set<COutPoint, SaltedOutpointHasher> GetNonBaseCoins() const { return m_non_base_coins; }
848+
/** Clear m_temp_added and m_non_base_coins. */
849+
void Reset();
838850
};
839851

840852
/**

0 commit comments

Comments
 (0)