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
Add package evaluation fuzzer #28450
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
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. |
Awesome, Concept ACK. |
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.
concept ACK, really nice 🚀
f06cf5b
to
ad63201
Compare
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.
Concept ACK
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.
Concept ACK
3654ad4
to
28f45d6
Compare
rebased on master, removed arg round-trip, un-marked WIP |
Concept ACK, I guess this also picks up #25778 ? |
Any interest in using |
@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.
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 |
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.
future work: making the tx >40kWu sometimes, along with other invariant checks, would have likely allowed the fuzzer to catch #28472
28f45d6
to
93d7c5c
Compare
2302596
to
bf7436c
Compare
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.
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
// 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; |
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.
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)."
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.
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?
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.
Also... wondering why allow num_txs to be less than 2 anyway?
- it can get rejected by ProcessNewPackage
- it can cause the existing mempool to be somewhat more interesting maybe
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.
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
// 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; | ||
} |
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.
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
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.
done, had to keep the outpoints_value check outside though since it needs all generated outpoints
} | ||
// TODO vary transaction sizes to catch size-related issues | ||
auto tx = MakeTransactionRef(tx_mut); | ||
// Restore previously removed outpoints, except in-package outpoints |
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.
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?
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.
yes
src/test/fuzz/package_eval.cpp
Outdated
// 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); | ||
} | ||
} |
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.
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
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.
taken, thanks
CI failure is a functional test so I'd assume unrelated |
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:
^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? |
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. |
2ecdc21
to
3707ed7
Compare
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.
Can't find the actual place to reply(thanks github), removed outpoints_supply entirely as it's vestigial Took all suggestions, pushed |
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.
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
3707ed7
to
fb4d71e
Compare
Removed the |
fb4d71e
to
6e46822
Compare
Going to hold off on all subsequent nits to get this in to un-draft #26711 and start building on top |
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 |
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.
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.
// Transactions may be entered and booted any number of times | |
// Transactions may be entered and booted any number of times | |
m_added.erase(tx); |
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.
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.
6e46822
to
70879e4
Compare
src/test/fuzz/package_eval.cpp
Outdated
auto package_submit = !single_submit; | ||
|
||
const auto result_package = WITH_LOCK(::cs_main, | ||
return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/!package_submit)); |
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.
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.
70879e4
to
262ab8e
Compare
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 |
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.
reACK 262ab8e
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.
tACK 262ab8e
Will generate and submit inputs to qa-assets once this is merged
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
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:
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.