Skip to content
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

Add package evaluation fuzzer #28450

Merged

Conversation

instagibbs
Copy link
Member

@instagibbs instagibbs commented Sep 11, 2023

This fuzzer target caught the issue in #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.

@DrahtBot
Copy link
Contributor

DrahtBot commented Sep 11, 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 murchandamus, glozow, dergoegge
Concept ACK darosior, brunoerg, MarcoFalke

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:

  • #26711 (validate package transactions with their in-package ancestor sets by glozow)

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.

@instagibbs
Copy link
Member Author

cc @glozow @MarcoFalke

@darosior
Copy link
Member

Awesome, Concept ACK.

Copy link
Member

@glozow glozow 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, really nice 🚀

src/test/fuzz/package_eval.cpp Outdated Show resolved Hide resolved
Copy link
Member

@dergoegge dergoegge 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

src/test/fuzz/package_eval.cpp Show resolved Hide resolved
Copy link
Contributor

@brunoerg brunoerg 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

@instagibbs instagibbs force-pushed the 2023-08-mid-package-trim-sep-8-fuzz branch from 3654ad4 to 28f45d6 Compare September 13, 2023 18:06
@instagibbs instagibbs changed the title WIP: Add package evaluation fuzzer Add package evaluation fuzzer Sep 13, 2023
@instagibbs
Copy link
Member Author

rebased on master, removed arg round-trip, un-marked WIP

@maflcko
Copy link
Member

maflcko commented Sep 14, 2023

Concept ACK, I guess this also picks up #25778 ?

@glozow
Copy link
Member

glozow commented Sep 14, 2023

Any interest in using CheckPackageMempoolAcceptResult from baf475d? It checks that a result contains the fields we expect based on whether it's supposed to be valid.

@instagibbs
Copy link
Member Author

instagibbs commented Sep 14, 2023

@MarcoFalke oh, had no idea it existed. Yes I think this should subsume it. I kept it a separate fuzz target to allow more specialization for what we're covering.

Any interest in using CheckPackageMempoolAcceptResult from baf475d? It checks that a result contains the fields we expect based on whether it's supposed to be valid.

I might take a subset of that, but a few parts wouldn't be useful since I'm firing off random packages that may or may not be valid, and the mempool may trim things even if they're valid?

edit: In other words, please consider this PR review-ready 👍

for (int i = 0; i < num_out; ++i) {
tx_mut.vout.emplace_back(amount_out, P2WSH_OP_TRUE);
}
// TODO vary transaction sizes to catch size-related issues
Copy link
Member Author

Choose a reason for hiding this comment

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

future work: making the tx >40kWu sometimes, along with other invariant checks, would have likely allowed the fuzzer to catch #28472

@instagibbs instagibbs force-pushed the 2023-08-mid-package-trim-sep-8-fuzz branch from 28f45d6 to 93d7c5c Compare September 15, 2023 13:45
@instagibbs
Copy link
Member Author

instagibbs commented Sep 15, 2023

removed the second commit to not impede review of the various bugfix PRs individually

rebasing on this should result in no known crashes: #28471
#28472

src/test/fuzz/package_eval.cpp Outdated Show resolved Hide resolved
src/test/fuzz/package_eval.cpp Outdated Show resolved Hide resolved
src/test/fuzz/package_eval.cpp Show resolved Hide resolved
@instagibbs instagibbs force-pushed the 2023-08-mid-package-trim-sep-8-fuzz branch 2 times, most recently from 2302596 to bf7436c Compare September 20, 2023 14:03
Copy link
Member

@glozow glozow left a comment

Choose a reason for hiding this comment

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

A few quick questions, looks correct to me though some bits are a little confusing. I know future updates are planned so feel free to save for later if I'm scope creeping too much.

src/test/fuzz/package_eval.cpp Outdated Show resolved Hide resolved
src/test/fuzz/package_eval.cpp Outdated Show resolved Hide resolved
src/test/fuzz/package_eval.cpp Outdated Show resolved Hide resolved
Comment on lines 250 to 260
// Single-tx packages should be rejected, so do that sometimes, and sometimes send it via single submission
// to allow it into the mempool by itself to make more interesting mempool packages
auto single_submit = txs.size() == 1 && fuzzed_data_provider.ConsumeBool();
auto package_submit = !single_submit;
Copy link
Member

Choose a reason for hiding this comment

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

This was a bit confusing. Perhaps adding this comment beforehand would help clarify things

"When there are multiple transactions in the package, we call ProcessNewPackage(txs, test_accept=false) and AcceptToMemoryPool(txs.back(), test_accept=true). When there is only 1 transaction, we might flip it (the package is a test accept and ATMP is a submission)."

Copy link
Member

Choose a reason for hiding this comment

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

Also... wondering why allow num_txs to be less than 2 anyway? Isn't this test kind of the same as what's in the tx_pool fuzzer?

Copy link
Member Author

Choose a reason for hiding this comment

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

Also... wondering why allow num_txs to be less than 2 anyway?

  1. it can get rejected by ProcessNewPackage
  2. it can cause the existing mempool to be somewhat more interesting maybe

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh I was mistaken, AcceptPackage takes package sizes of 1(I was thinking of BIP331 probably!). This PR could have been a bit simpler I think by removing single accept path.

src/test/fuzz/package_eval.cpp Outdated Show resolved Hide resolved
Comment on lines 218 to 227
// Cache the in-package outpoints being made
for (size_t i = 0; i < tx->vout.size(); ++i) {
package_outpoints.emplace(tx->GetHash(), i);
outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
}
Copy link
Member

Choose a reason for hiding this comment

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

This can also go under if (!last_tx), right? There's no need to add the last tx's outputs since we won't need them

Copy link
Member Author

Choose a reason for hiding this comment

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

done, had to keep the outpoints_value check outside though since it needs all generated outpoints

src/test/fuzz/package_eval.cpp Show resolved Hide resolved
}
// TODO vary transaction sizes to catch size-related issues
auto tx = MakeTransactionRef(tx_mut);
// Restore previously removed outpoints, except in-package outpoints
Copy link
Member

Choose a reason for hiding this comment

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

Question: this means transactions in the package can conflict with each other, right? It's possible to select the same outpoint from outpoints_rbf for 2 parents in the same package?

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

src/test/fuzz/package_eval.cpp Show resolved Hide resolved
Comment on lines 315 to 287
// Add created outpoints, remove spent outpoints
{
// Outpoints that no longer exist at all
std::set<COutPoint> consumed_erased;
// Outpoints that no longer count toward the total supply
std::set<COutPoint> consumed_supply;
for (const auto& removed_tx : removed) {
insert_tx(/*created_by_tx=*/{consumed_erased}, /*consumed_by_tx=*/{outpoints_supply}, /*tx=*/*removed_tx);
}
for (const auto& added_tx : added) {
insert_tx(/*created_by_tx=*/{outpoints_supply, outpoints_rbf}, /*consumed_by_tx=*/{consumed_supply}, /*tx=*/*added_tx);
}
for (const auto& p : consumed_erased) {
outpoints_supply.erase(p);
outpoints_rbf.erase(p);
}
for (const auto& p : consumed_supply) {
outpoints_supply.erase(p);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

This is kinda convoluted... could we just update outpoints_supply and outpoints_rbf directly on the TransactionAddedToMempool and TransactionRemovedFromMempool callbacks instead of the added/removed sets? took a stab in glozow@df433f8

Copy link
Member Author

Choose a reason for hiding this comment

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

taken, thanks

@glozow
Copy link
Member

glozow commented Sep 25, 2023

CI failure is a functional test so I'd assume unrelated

@dergoegge
Copy link
Member

@glozow
Copy link
Member

glozow commented Sep 26, 2023

Made a coverage report: https://dergoegge.github.io/bitcoin-coverage/pr28450/fuzz.coverage/index.html

Looks pretty good to me, noting the main missing bits:

  • same-txid-different-witness
  • policy script checks failing
  • bad-txns-too-many-sigops
  • being granted CPFP carve out and then failing

^which have already been marked as followups.

There's no package-not-child-with-unconfirmed-parents which is by design. Also looks like RBF rules are red, perhaps it'll take a while to hit them?

@dergoegge
Copy link
Member

Also looks like RBF rules are red, perhaps it'll take a while to hit them?

The seed corpus from the report was the result of 5000+ cpu hours, so I'd say it's likely just not able to hit the rbf paths.

@instagibbs instagibbs force-pushed the 2023-08-mid-package-trim-sep-8-fuzz branch from 2ecdc21 to 3707ed7 Compare September 26, 2023 13:37
@instagibbs
Copy link
Member Author

instagibbs commented Sep 26, 2023

Also looks like RBF rules are red, perhaps it'll take a while to hit them?

A couple of those cases would look difficult to hit without intention/design to do so. One I'm unsure how it hasn't been hit, something to look at later especially as we approach package rbf.

I think achow is wondering if this is redundant with outpoints_rbf? Since new transactions are always created using outpoints_rbf inputs.

Can't find the actual place to reply(thanks github), removed outpoints_supply entirely as it's vestigial


Took all suggestions, pushed

Copy link
Member

@glozow glozow left a comment

Choose a reason for hiding this comment

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

ACK 3707ed7
Code and coverage look good. Will run overnight to sanity check that outpointsupdater hasn't broken anything.

Couple more ideas for post-26711 expansions:

  • add nonexistent outpoint to hit missing inputs cases
  • shuffle before submission

src/test/fuzz/package_eval.cpp Outdated Show resolved Hide resolved
src/test/fuzz/package_eval.cpp Outdated Show resolved Hide resolved
src/test/fuzz/package_eval.cpp Outdated Show resolved Hide resolved
@instagibbs instagibbs force-pushed the 2023-08-mid-package-trim-sep-8-fuzz branch from 3707ed7 to fb4d71e Compare September 27, 2023 12:51
@instagibbs
Copy link
Member Author

Removed the Final subroutine to get a slight speedup since it seems unnecessary, leaving just the tx_pool.check() which should catch all issues.

@instagibbs instagibbs force-pushed the 2023-08-mid-package-trim-sep-8-fuzz branch from fb4d71e to 6e46822 Compare September 27, 2023 13:39
@instagibbs
Copy link
Member Author

Going to hold off on all subsequent nits to get this in to un-draft #26711 and start building on top

@instagibbs
Copy link
Member Author

https://github.com/bitcoin/bitcoin/pull/28450/checks?check_run_id=17181111860 multiprocess build having an issue of some kind


void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
{
// Transactions may be entered and booted any number of times
Copy link
Member

Choose a reason for hiding this comment

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

I think this can cause a false positive crash on line 281. In the "mempool full" case where a tx gets added and then removed in TrimToSize(), it'll fire TransactionAddedToMempool and TransactionRemovedFromMempool. Then accepted will be false but added won't be empty.

Suggested change
// Transactions may be entered and booted any number of times
// Transactions may be entered and booted any number of times
m_added.erase(tx);

Copy link
Member Author

Choose a reason for hiding this comment

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

Sounds right, surprised I haven't hit this since it quite often chooses tiny mempool sizes too small for even a single transaction to enter.

Taken.

@instagibbs instagibbs force-pushed the 2023-08-mid-package-trim-sep-8-fuzz branch from 6e46822 to 70879e4 Compare September 27, 2023 17:24
src/test/fuzz/package_eval.cpp Outdated Show resolved Hide resolved
auto package_submit = !single_submit;

const auto result_package = WITH_LOCK(::cs_main,
return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/!package_submit));
Copy link
Contributor

Choose a reason for hiding this comment

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

I also found the new variable package_submit confusing as well. I don't see the point of creating an additional variable, especially since it’s only passed once and negated.

src/test/fuzz/package_eval.cpp Outdated Show resolved Hide resolved
src/test/fuzz/package_eval.cpp Outdated Show resolved Hide resolved
@instagibbs instagibbs force-pushed the 2023-08-mid-package-trim-sep-8-fuzz branch from 70879e4 to 262ab8e Compare September 27, 2023 20:27
@murchandamus
Copy link
Contributor

ACK 262ab8e

Note that this is the first time I review something from the package relay project, so take that for whatever it’s worth

Copy link
Member

@glozow glozow left a comment

Choose a reason for hiding this comment

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

reACK 262ab8e

Copy link
Member

@dergoegge dergoegge left a comment

Choose a reason for hiding this comment

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

tACK 262ab8e

Will generate and submit inputs to qa-assets once this is merged

@glozow glozow merged commit 6619d6a into bitcoin:master Sep 28, 2023
16 checks passed
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants