mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-09-06 05:01:07 +02:00
Merge bitcoin/bitcoin#32973: validation: docs and cleanups for MemPoolAccept coins views
b6d4688f77
[doc] reword comments in test_mid_package_replacement (glozow)f3a613aa5b
[cleanup] delete brittle test_mid_package_eviction (glozow)c3cd7fcb2c
[doc] remove references to now-nonexistent Finalize() function (glozow)d8140f5f05
don't make a copy of m_non_base_coins (glozow)98ba2b1db2
[doc] MemPoolAccept coins views (glozow)ba02c30b8a
[doc] always CleanupTemporaryCoins after a mempool trim (glozow) Pull request description: Deletes `test_mid_package_eviction` that is brittle and already covered in other places. It was introduced in #28251 addressing 2 issues: (1) calling `LimitMempoolSize()` in the middle of package validation and (2) not updating coins view cache when the mempool contents change, leading to "disappearing coins." (1) If you let `AcceptSingleTransaction` call `LimitMempoolSize` in the middle of package validation, you should get a failure in `test_mid_package_eviction_success` (the package is rejected): ``` diff --git a/src/validation.cpp b/src/validation.cpp index f2f6098e214..4bd6f059849 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1485,7 +1485,7 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef FinalizeSubpackage(args); // Limit the mempool, if appropriate. - if (!args.m_package_submission && !args.m_bypass_limits) { + if (!args.m_bypass_limits) { LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip()); // If mempool contents change, then the m_view cache is dirty. Given this isn't a package // submission, we won't be using the cache anymore, but clear it anyway for clarity. ``` Mempool modifications have a pretty narrow interface since #31122 and `TrimToSize()` cannot be called while there is an outstanding mempool changeset. So I think there is a low likelihood of accidentally reintroducing this problem and not immediately hitting e.g. a fuzzer crash on this lineb53fab1467/src/txmempool.cpp (L1143)
(2) If you remove the `CleanupTemporaryCoins()` call from `ClearSubPackageState()` you should get a failure from `test_mid_package_replacement`: ``` diff --git a/src/validation.cpp b/src/validation.cpp index f2f6098e214..01b904b69ef 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -779,7 +779,7 @@ private: m_subpackage = SubPackageState{}; // And clean coins while at it - CleanupTemporaryCoins(); + // CleanupTemporaryCoins(); } }; ``` I also added/cleaned up the documentation about coins views to hopefully make it extremely clear when people should `CleanupTemporaryCoins`. ACKs for top commit: instagibbs: reACKb6d4688f77
sdaftuar: utACKb6d4688f77
marcofleon: ACKb6d4688f77
Tree-SHA512: 79c68e263013b1153520f5453e6b579b8fe7e1d6a9952b1ac2c3c3c017034e6d21d7000a140bba4cc9d2ce50ea3a84cc6f91fd5febc52d7b3fa4f797955d987d
This commit is contained in:
@@ -177,98 +177,6 @@ class MempoolLimitTest(BitcoinTestFramework):
|
||||
evicted_feerate_btc_per_kvb = entry["fees"]["modified"] / (Decimal(entry["vsize"]) / 1000)
|
||||
assert_greater_than(evicted_feerate_btc_per_kvb, max_parent_feerate)
|
||||
|
||||
def test_mid_package_eviction(self):
|
||||
node = self.nodes[0]
|
||||
self.log.info("Check a package where each parent passes the current mempoolminfee but would cause eviction before package submission terminates")
|
||||
|
||||
self.restart_node(0, extra_args=self.extra_args[0])
|
||||
|
||||
# Restarting the node resets mempool minimum feerate
|
||||
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
|
||||
assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
|
||||
|
||||
fill_mempool(self, node)
|
||||
current_info = node.getmempoolinfo()
|
||||
mempoolmin_feerate = current_info["mempoolminfee"]
|
||||
|
||||
package_hex = []
|
||||
# UTXOs to be spent by the ultimate child transaction
|
||||
parent_utxos = []
|
||||
|
||||
evicted_vsize = 2000
|
||||
# Mempool transaction which is evicted due to being at the "bottom" of the mempool when the
|
||||
# mempool overflows and evicts by descendant score. It's important that the eviction doesn't
|
||||
# happen in the middle of package evaluation, as it can invalidate the coins cache.
|
||||
mempool_evicted_tx = self.wallet.send_self_transfer(
|
||||
from_node=node,
|
||||
fee_rate=mempoolmin_feerate,
|
||||
target_vsize=evicted_vsize,
|
||||
confirmed_only=True
|
||||
)
|
||||
# Already in mempool when package is submitted.
|
||||
assert mempool_evicted_tx["txid"] in node.getrawmempool()
|
||||
|
||||
# This parent spends the above mempool transaction that exists when its inputs are first
|
||||
# looked up, but disappears later. It is rejected for being too low fee (but eligible for
|
||||
# reconsideration), and its inputs are cached. When the mempool transaction is evicted, its
|
||||
# coin is no longer available, but the cache could still contains the tx.
|
||||
cpfp_parent = self.wallet.create_self_transfer(
|
||||
utxo_to_spend=mempool_evicted_tx["new_utxo"],
|
||||
fee_rate=mempoolmin_feerate - Decimal('0.00001'),
|
||||
confirmed_only=True)
|
||||
package_hex.append(cpfp_parent["hex"])
|
||||
parent_utxos.append(cpfp_parent["new_utxo"])
|
||||
assert_equal(node.testmempoolaccept([cpfp_parent["hex"]])[0]["reject-reason"], "mempool min fee not met")
|
||||
|
||||
self.wallet.rescan_utxos()
|
||||
|
||||
# Series of parents that don't need CPFP and are submitted individually. Each one is large and
|
||||
# high feerate, which means they should trigger eviction but not be evicted.
|
||||
parent_vsize = 25000
|
||||
num_big_parents = 3
|
||||
# Need to be large enough to trigger eviction
|
||||
# (note that the mempool usage of a tx is about three times its vsize)
|
||||
assert_greater_than(parent_vsize * num_big_parents * 3, current_info["maxmempool"] - current_info["bytes"])
|
||||
parent_feerate = 100 * mempoolmin_feerate
|
||||
|
||||
big_parent_txids = []
|
||||
for i in range(num_big_parents):
|
||||
parent = self.wallet.create_self_transfer(fee_rate=parent_feerate, target_vsize=parent_vsize, confirmed_only=True)
|
||||
parent_utxos.append(parent["new_utxo"])
|
||||
package_hex.append(parent["hex"])
|
||||
big_parent_txids.append(parent["txid"])
|
||||
# There is room for each of these transactions independently
|
||||
assert node.testmempoolaccept([parent["hex"]])[0]["allowed"]
|
||||
|
||||
# Create a child spending everything, bumping cpfp_parent just above mempool minimum
|
||||
# feerate. It's important not to bump too much as otherwise mempool_evicted_tx would not be
|
||||
# evicted, making this test much less meaningful.
|
||||
approx_child_vsize = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos)["tx"].get_vsize()
|
||||
cpfp_fee = (mempoolmin_feerate / 1000) * (cpfp_parent["tx"].get_vsize() + approx_child_vsize) - cpfp_parent["fee"]
|
||||
# Specific number of satoshis to fit within a small window. The parent_cpfp + child package needs to be
|
||||
# - When there is mid-package eviction, high enough feerate to meet the new mempoolminfee
|
||||
# - When there is no mid-package eviction, low enough feerate to be evicted immediately after submission.
|
||||
magic_satoshis = 1200
|
||||
cpfp_satoshis = int(cpfp_fee * COIN) + magic_satoshis
|
||||
|
||||
child = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=cpfp_satoshis)
|
||||
package_hex.append(child["hex"])
|
||||
|
||||
# Package should be submitted, temporarily exceeding maxmempool, and then evicted.
|
||||
with node.assert_debug_log(expected_msgs=["rolling minimum fee bumped"]):
|
||||
assert_equal(node.submitpackage(package_hex)["package_msg"], "transaction failed")
|
||||
|
||||
# Maximum size must never be exceeded.
|
||||
assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["bytes"])
|
||||
|
||||
# Evicted transaction and its descendants must not be in mempool.
|
||||
resulting_mempool_txids = node.getrawmempool()
|
||||
assert mempool_evicted_tx["txid"] not in resulting_mempool_txids
|
||||
assert cpfp_parent["txid"] not in resulting_mempool_txids
|
||||
assert child["txid"] not in resulting_mempool_txids
|
||||
for txid in big_parent_txids:
|
||||
assert txid in resulting_mempool_txids
|
||||
|
||||
def test_mid_package_replacement(self):
|
||||
node = self.nodes[0]
|
||||
self.log.info("Check a package where an early tx depends on a later-replaced mempool tx")
|
||||
@@ -283,9 +191,7 @@ class MempoolLimitTest(BitcoinTestFramework):
|
||||
current_info = node.getmempoolinfo()
|
||||
mempoolmin_feerate = current_info["mempoolminfee"]
|
||||
|
||||
# Mempool transaction which is evicted due to being at the "bottom" of the mempool when the
|
||||
# mempool overflows and evicts by descendant score. It's important that the eviction doesn't
|
||||
# happen in the middle of package evaluation, as it can invalidate the coins cache.
|
||||
# Mempool transaction is replaced by a package transaction.
|
||||
double_spent_utxo = self.wallet.get_utxo(confirmed_only=True)
|
||||
replaced_tx = self.wallet.send_self_transfer(
|
||||
from_node=node,
|
||||
@@ -297,8 +203,8 @@ class MempoolLimitTest(BitcoinTestFramework):
|
||||
assert replaced_tx["txid"] in node.getrawmempool()
|
||||
|
||||
# This parent spends the above mempool transaction that exists when its inputs are first
|
||||
# looked up, but disappears later. It is rejected for being too low fee (but eligible for
|
||||
# reconsideration), and its inputs are cached. When the mempool transaction is evicted, its
|
||||
# looked up, but will disappear if the replacement occurs. It is rejected for being too low fee (but eligible for
|
||||
# reconsideration), and its inputs are cached. When the mempool transaction is replaced, its
|
||||
# coin is no longer available, but the cache could still contain the tx.
|
||||
cpfp_parent = self.wallet.create_self_transfer(
|
||||
utxo_to_spend=replaced_tx["new_utxo"],
|
||||
@@ -433,7 +339,6 @@ class MempoolLimitTest(BitcoinTestFramework):
|
||||
|
||||
self.test_mid_package_eviction_success()
|
||||
self.test_mid_package_replacement()
|
||||
self.test_mid_package_eviction()
|
||||
self.test_rbf_carveout_disallowed()
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user