Skip to content

Conversation

glozow
Copy link
Member

@glozow glozow commented Aug 10, 2023

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 faeed68 on top of the v3 PR.

@DrahtBot
Copy link
Contributor

DrahtBot commented Aug 10, 2023

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK instagibbs
Concept ACK fjahr

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #28453 (wallet: Receive silent payment transactions by achow101)
  • #28450 (WIP: Add package evaluation fuzzer by instagibbs)
  • #28368 (Fee Estimator updates from Validation Interface/CScheduler thread by ismaelsadeeq)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@glozow
Copy link
Member Author

glozow commented Aug 10, 2023

cc @instagibbs

@instagibbs
Copy link
Member

@sipa you might want to weigh in as well

Copy link
Member

@instagibbs instagibbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

conditional ACK c2c9dfe

on having a test case catching the crash included. Tested with my own test case in e3622f7

Copy link

@ariard ariard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note the code path affected by the bug is under the submitpackage RPC, which is clearly indicated as an experimental RPC with an unstable interface.

More test coverage sounds good.

Copy link
Contributor

@fjahr fjahr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK, will do a full review once the test has been added.

@glozow
Copy link
Member Author

glozow commented Aug 22, 2023

Pushed a test - it should fail on master with the assertion.

Generally, the conditions to hit the case are:

  • An almost-full mempool.
  • mempool_evicted_tx (parent) A to-be-evicted tx that is submitted ahead of time. Has a very low feerate and is evicted immediately when mempool overflows. It has an output coin_to_disappear
  • A package consisting of:
    • cpfp_parent (child_a) parent that spends coin_to_disappear and fails a check that requires its input to be loaded and makes it eligible for reconsideration (e.g. below mempool minimum feerate).
    • high-feerate parent (child_b), high feerate so that they are submitted individually and trigger eviction.
      • I've used 6 high-feerate parents in this test to make it easy to trigger overflow.
      • In the EA test, child_b triggers eviction due to proactive trimming of the sponsorless parent instead of mempool overflow.
    • package_child (child_c) spending the above parents.
      • The child must pay a fee high enough to CPFP the low-feerate parent.
    • Additional requirements:
      • The low-feerate parent needs to be checked before the higher-feerate parents so that its inputs are loaded before the eviction happens. Otherwise it will just hit missing-inputs. This also means that as a side effect of the fee-based linearization in validate package transactions with their in-package ancestor sets #26711 (which would put the low-feerate parent at the end), this test won't trigger. But I don't think the linearization is an adequate solution to this problem.
      • To make the test interesting, it should be low enough that the to-be-evicted tx is evicted regardless of when the eviction happens.

Timeline of what happens:

  1. mempool is almost full
  2. mempool_evicted_tx is submitted to mempool
  3. AcceptPackage(package):
    1. AcceptSingleTransaction(cpfp_parent). This loads coin_to_disappear into m_view . After the fee is checked, the tx fails and we continue onto the next transactions.
    2. for each of the n high-feerate parents, AcceptSingleTransaction(tx). These succeed and, in Finalize(), the transactions are submitted.
      1. (Without PR's changes) this also calls LimitMempool() which evicts by descendant score until the mempool is within size limits again.
      2. This kicks out mempool_evicted_tx, which means the m_viewmempool will no longer have a coin for coin_to_disappear. However, the coin has already been cached in m_view and it doesn't know this.
    3. AcceptSingleTransaction(package_child) fails due to missing inputs.
    4. AcceptMultipleTransactions([cpfp_parent, package_child])
      1. PreChecks looks for the inputs of each transaction, including the now-nonexistent coin_to_disappear. This succeeds because m_view has a coin for it.
      2. The CPFP works, so both transactions are submitted in SubmitPackage() , which calls ConsensusScriptChecks() which calls CheckInputsFromMempoolAndCache() which asserts that the coins all exist.
        1. We can't find the coin in the mempool (which is where it used to be).
        2. So we assume we will find it in the UTXO set. We call AccessCoin() and assert !coinFromUTXOSet.IsSpent(). This fails because the coin does not exist (which is the same as isspent).

@glozow glozow force-pushed the 2023-08-mid-package-trim branch from c4ac016 to 3729ee4 Compare August 22, 2023 14:01
@instagibbs
Copy link
Member

the above test description sounds right. The eldest ancestor gets trimmed due to size limitations, then things go wonky without this change

Copy link
Member

@instagibbs instagibbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 3729ee4

The newly added test, while slightly obscure to trigger it, makes sense and triggers the crash bug when the fix is not applied.

image

@glozow glozow force-pushed the 2023-08-mid-package-trim branch from 3729ee4 to a5a029c Compare August 23, 2023 15:19
@glozow
Copy link
Member Author

glozow commented Aug 23, 2023

Updated the approach + added a test for replacement case.

There are now 2 fixes:
(1) Clear any non-chainstate coins at the end of AcceptSubPackage so they can't be used in a different AcceptSubPackage.
(2) Don't trim in the middle of package evaluation.

Technically (1) is sufficient, but I think (2) is also beneficial as it avoids changing the mempool minimum feerate (which affects how we group transactions) mid-package evaluation. It would be sad if a CPFP package contains a parent that's just barely above mempool minimum feerate, so we submit it, then evict it (due to later parent), then can't submit the child... when we could have just submitted all of them and evicted something else.

@glozow glozow changed the title validation: avoid mempool eviction mid-package evaluation validation: fix coins disappearing mid-package evaluation Aug 23, 2023
Copy link
Member

@instagibbs instagibbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ifirst pass on most recent fix to a5a029c

approach ack on fixes, would be flakey to trim things within packages prematurely.

Copy link
Member

@instagibbs instagibbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 22fe13e

Copy link

@ariard ariard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the in-depth explanation of the conditions to hit the case. Will review more the code and test coverage as high-prio.

assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))

self.fill_mempool()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand one of the condition to hit the case is a full mempool and when we call LimitMempool(), one interesting variation of the test case could be to limit the mempool size to the subpackage only. It might be a tricky one, though boundary conditions are always nice to exercise.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, full mempool is one of the conditions (i.e. the one tested in test_mid_package_eviction). It's not strictly necessary though, the coin can also disappear due to replacement.

one interesting variation of the test case could be to limit the mempool size to the subpackage only

Do you mean setting the mempool's maximum memory to something very tiny?

…icted

Bug fix: a transaction may be in the mempool when package evaluation
begins (so it is added to results_final with MEMPOOL_ENTRY or
DIFFERENT_WITNESS), but get evicted due to another transaction
submission.
Don't do any mempool evictions until package validation is done,
preventing the mempool minimum feerate from changing. Whether we submit
transactions separately or as a package depends on whether they meet the
mempool minimum feerate threshold, so it's best that the value not
change while we are evaluating a package.
This avoids a situation where we have a CPFP package in which
the parents meet the mempool minimum feerate and are submitted by
themselves, but they are evicted before we have submitted the child.
Useful to ensure that the topologies of packages/transactions are as
expected, preventing bugs caused by having unexpected mempool ancestors.
We want to be able to re-use fill_mempool so that none of the tests
affect each other.

Change the logs from info to debug because they are otherwise repeated
many times in the test output.
Test for scenario(s) outlined in PR 28251.
Test what happens when a package transaction spends a mempool coin which
is fetched and then disappears mid-package evaluation due to eviction or
replacement.
@glozow glozow force-pushed the 2023-08-mid-package-trim branch from 40d0db8 to 32c1dd1 Compare September 13, 2023 15:16
@instagibbs
Copy link
Member

instagibbs commented Sep 13, 2023

reACK 32c1dd1

inclusion of 7d7f7a1 duplicate-txid check in CheckPackage which prevents the assertion being hit for the 2nd of the duplicate txids

@fanquake fanquake merged commit f1a9fd6 into bitcoin:master Sep 13, 2023
Frank-GER pushed a commit to syscoin/syscoin that referenced this pull request Sep 19, 2023
…valuation

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@faeed68 on top of the v3 PR.

ACKs for top commit:
  instagibbs:
    reACK bitcoin@32c1dd1

Tree-SHA512: 61e7f69db4712e5e5bfa27d037ab66bdd97f1bf60a8d9ffb96adb1f0609af012c810d681102ee5c7baec7b5fe8cb7c304a60c63ccc445d00d86a2b7f0e7ddb90
glozow added a commit to bitcoin-core/gui that referenced this pull request Sep 28, 2023
262ab8e Add package evaluation fuzzer (Greg Sanders)

Pull request description:

  This fuzzer target caught the issue in bitcoin/bitcoin#28251 within 5 minutes on master branch, and an additional issue which I've applied a preliminary patch to cover.

  Fuzzer target does the following:

  1) Picks mempool confgs, including max package size, count, mempool size, etc
  2) Generates 1 to 26 transactions with arbitrary coins/fees, the first N-1 spending only confirmed outpoints
  3) Nth transaction, if >1, sweeps all unconfirmed outpoints in mempool
  4) If N==1, it may submit it through single-tx submission path, to allow for more interesting topologies
  5) Otherwise submits through package submission interface
  6) Repeat 1-5  a few hundred times per mempool instance

  In other words, it ends up building chains of txns in the mempool using parents-and-children packages, which is currently the topology supported on master.

  The test itself is a direct rip of tx_pool.cpp, with a number of assertions removed because they were failing for unknown reasons, likely due to the notification changes of single tx submission to package, which is used to track addition/removal of transactions in the test. I'll continue working on re-adding these assertions for further invariant testing.

ACKs for top commit:
  murchandamus:
    ACK 262ab8e
  glozow:
    reACK 262ab8e
  dergoegge:
    tACK 262ab8e

Tree-SHA512: 190784777d0f2361b051b3271db8f79b7927e3cab88596d2c30e556da721510bd17f6cc96f6bb03403bbf0589ad3f799fa54e63c1b2bd92a2084485b5e3e96a5
Frank-GER pushed a commit to syscoin/syscoin that referenced this pull request Oct 5, 2023
262ab8e Add package evaluation fuzzer (Greg Sanders)

Pull request description:

  This fuzzer target caught the issue in bitcoin#28251 within 5 minutes on master branch, and an additional issue which I've applied a preliminary patch to cover.

  Fuzzer target does the following:

  1) Picks mempool confgs, including max package size, count, mempool size, etc
  2) Generates 1 to 26 transactions with arbitrary coins/fees, the first N-1 spending only confirmed outpoints
  3) Nth transaction, if >1, sweeps all unconfirmed outpoints in mempool
  4) If N==1, it may submit it through single-tx submission path, to allow for more interesting topologies
  5) Otherwise submits through package submission interface
  6) Repeat 1-5  a few hundred times per mempool instance

  In other words, it ends up building chains of txns in the mempool using parents-and-children packages, which is currently the topology supported on master.

  The test itself is a direct rip of tx_pool.cpp, with a number of assertions removed because they were failing for unknown reasons, likely due to the notification changes of single tx submission to package, which is used to track addition/removal of transactions in the test. I'll continue working on re-adding these assertions for further invariant testing.

ACKs for top commit:
  murchandamus:
    ACK 262ab8e
  glozow:
    reACK 262ab8e
  dergoegge:
    tACK 262ab8e

Tree-SHA512: 190784777d0f2361b051b3271db8f79b7927e3cab88596d2c30e556da721510bd17f6cc96f6bb03403bbf0589ad3f799fa54e63c1b2bd92a2084485b5e3e96a5
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Jul 12, 2024
Summary:
```
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.
```

Of course the replacement case doesn't apply to our codebase, but the mempool trimming does.

Backport of [[bitcoin/bitcoin#28251 | core#28251]] and [[bitcoin/bitcoin#27127 | core#27127]].
Includes a commit from [[bitcoin/bitcoin#26657 | core#26657]]:
bitcoin/bitcoin@d0a909a

Depends on D16443 and D16444.

Test Plan:
  ninja all check-all

Reviewers: #bitcoin_abc, PiRK

Reviewed By: #bitcoin_abc, PiRK

Subscribers: PiRK

Differential Revision: https://reviews.bitcoinabc.org/D16447
roqqit pushed a commit to doged-io/doged that referenced this pull request Aug 1, 2024
Summary:
```
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.
```

Of course the replacement case doesn't apply to our codebase, but the mempool trimming does.

Backport of [[bitcoin/bitcoin#28251 | core#28251]] and [[bitcoin/bitcoin#27127 | core#27127]].
Includes a commit from [[bitcoin/bitcoin#26657 | core#26657]]:
bitcoin/bitcoin@d0a909a

Depends on D16443 and D16444.

Test Plan:
  ninja all check-all

Reviewers: #bitcoin_abc, PiRK

Reviewed By: #bitcoin_abc, PiRK

Subscribers: PiRK

Differential Revision: https://reviews.bitcoinabc.org/D16447
@bitcoin bitcoin locked and limited conversation to collaborators Sep 12, 2024
@bitcoin bitcoin unlocked this conversation Oct 23, 2024
@sdaftuar
Copy link
Member

I'm reviewing this PR more carefully, after seeing @instagibbs' comment here: #31122 (comment)

As I understand it, I think there are two key behaviors introduced in this PR:

  1. Clearing state between subpackage evaluations (specifically, clearing the coins in m_viewmempool)
  2. Deferring mempool limiting until the end of package validation

However, the specific issue that @glozow described above (#28251 (comment)), where we need to ensure that package validation fails if an input is evicted mid-validation, seems to be completely mitigated by behavior (1). And this behavior is tested in the mempool_limit test introduced in this PR.

As far as I can tell from reviewing 3ea71fe (#28251), behavior (2) is designed to allow package validation to succeed in a case where we have, say, a child C with two parents A and B, and we submit a package [A, B, C] and potentially trigger this outcome:

  • A is accepted singly
  • B is accepted singly, but the mempool overflows and A is trimmed
  • C is rejected for missing inputs

So by preventing mempool limiting from taking place mid-package, the choice of what to evict from the mempool is deferred until after C is able to make it in, potentially changing the lowest-descendant-score transaction in the mempool from A to something else (as A's descendant score might be pretty high after C is accepted).

However, I don't think this PR introduced any test for this second behavior, of ensuring success is possible even if an earlier parent might be trimmed mid-validation.

Does this summary sound right to others? Have I overlooked other behavior changes/bugfixes that were introduced here?

@glozow
Copy link
Member Author

glozow commented Oct 25, 2024

As I understand it, I think there are two key behaviors introduced in this PR

However, the specific issue that @glozow described above (#28251 (comment)), where we need to ensure that package validation fails if an input is evicted mid-validation, seems to be completely mitigated by behavior (1).

As far as I can tell from reviewing 3ea71fe (#28251), behavior (2) is designed to allow package validation to succeed in a case where...

However, I don't think this PR introduced any test for this second behavior,

This looks accurate to me. I've looked a little at the tests (it's been a while 😅) and can't really point to one for deferring evictions. Will review #31152.

@glozow glozow deleted the 2023-08-mid-package-trim branch October 25, 2024 00:26
glozow added a commit to glozow/bitcoin that referenced this pull request Jul 14, 2025
This test was introduced in bitcoin#28251 to ensure that (1) the mempool is not
trimmed in the middle of a package evaluation and (2) the m_view cache
is updated when evictions and replacements happen so coins are no longer
visible in subsequent package transactions.

(1) is prevented in multiple ways: TrimToSize() cannot be called while a
changeset is outstanding.
(2) also has coverage through test_mid_package_replacement.

This test is also brittle: it creates a transaction that itself is not
enough to trigger eviction, but will be pushed out immediately by the
package spending from it. While the current magic number 2000 works, we
do not have a way to query remaining space in the mempool if mempool
data structures change, and it can differ across platforms.
glozow added a commit to glozow/bitcoin that referenced this pull request Jul 14, 2025
This test was introduced in bitcoin#28251 to ensure that the mempool is not
trimmed in the middle of a package evaluation and the m_view cache
is updated when evictions and replacements happen so coins are no longer
visible in subsequent package transactions. These two things have
coverage in other tests as well, and are pretty unlikely to happen.

This test is also brittle: it requires evaluation of the parents in a
particular order, and creates a transaction that itself is not
enough to trigger eviction but will be pushed out immediately by the
package spending from it. While the current magic number 2000 works, we
do not have a way to query remaining space in the mempool if mempool
data structures change, and it can differ across platforms.
glozow added a commit to glozow/bitcoin that referenced this pull request Jul 16, 2025
This test was introduced in bitcoin#28251 to ensure that the mempool is not
trimmed in the middle of a package evaluation and the m_view cache
is updated when evictions and replacements happen so coins are no longer
visible in subsequent package transactions. These two things have
coverage in other tests as well, and are pretty unlikely to happen.

This test is also brittle: it requires evaluation of the parents in a
particular order, and creates a transaction that itself is not
enough to trigger eviction but will be pushed out immediately by the
package spending from it. While the current magic number 2000 works, we
do not have a way to query remaining space in the mempool if mempool
data structures change, and it can differ across platforms.
delta1 added a commit to delta1/elements that referenced this pull request Jul 22, 2025
sdaftuar pushed a commit to sdaftuar/bitcoin that referenced this pull request Jul 28, 2025
This test was introduced in bitcoin#28251 to ensure that the mempool is not
trimmed in the middle of a package evaluation and the m_view cache
is updated when evictions and replacements happen so coins are no longer
visible in subsequent package transactions. These two things have
coverage in other tests as well, and are pretty unlikely to happen.

This test is also brittle: it requires evaluation of the parents in a
particular order, and creates a transaction that itself is not
enough to trigger eviction but will be pushed out immediately by the
package spending from it. While the current magic number 2000 works, we
do not have a way to query remaining space in the mempool if mempool
data structures change, and it can differ across platforms.
fanquake added a commit that referenced this pull request Jul 29, 2025
…iews

b6d4688 [doc] reword comments in test_mid_package_replacement (glozow)
f3a613a [cleanup] delete brittle test_mid_package_eviction (glozow)
c3cd7fc [doc] remove references to now-nonexistent Finalize() function (glozow)
d8140f5 don't make a copy of m_non_base_coins (glozow)
98ba2b1 [doc] MemPoolAccept coins views (glozow)
ba02c30 [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 f2f6098..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 line https://github.com/bitcoin/bitcoin/blob/b53fab1467fde73c40402e2022b25edfff1e4668/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 f2f6098..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:
    reACK b6d4688
  sdaftuar:
    utACK b6d4688
  marcofleon:
    ACK b6d4688

Tree-SHA512: 79c68e263013b1153520f5453e6b579b8fe7e1d6a9952b1ac2c3c3c017034e6d21d7000a140bba4cc9d2ce50ea3a84cc6f91fd5febc52d7b3fa4f797955d987d
DashCoreAutoGuix pushed a commit to DashCoreAutoGuix/dash that referenced this pull request Aug 3, 2025
…valuation

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@faeed68 on top of the v3 PR.

ACKs for top commit:
  instagibbs:
    reACK bitcoin@32c1dd1

Tree-SHA512: 61e7f69db4712e5e5bfa27d037ab66bdd97f1bf60a8d9ffb96adb1f0609af012c810d681102ee5c7baec7b5fe8cb7c304a60c63ccc445d00d86a2b7f0e7ddb90
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants