Compact block harness (tests)

https://github.com/bitcoin/bitcoin/pull/33300

Host: marcofleon  -  PR author: Crypt-iQ

The PR branch HEAD was ed813c48f826d083becf93c741b483774c850c86 at the time of this review club meeting.

Notes

  • This PR adds a fuzz harness for testing compact block relay, along with a few test infrastructure changes to improve speed and stability. Earlier review clubs on fuzzing include: #17860, #18521, #21142, #30605.

  • The harness simulates P2P network behavior by having three peers send various messages (CMPCTBLOCK, BLOCKTXN, HEADERS, SENDCMPCT, TX) to the test node, triggering its compact block logic.

  • The goal is to find bugs and ensure assertions hold. See #33296 for an example of a bug found by this very fuzz test. Nice!

Compact Blocks Protocol Overview

  • BIP 152 introduced the compact block relay protocol, which reduces bandwidth and latency when propagating blocks by sending an encoded version of a block under the assumption that most of the transactions are already in our peer’s mempool.

  • Compact blocks consist of:
    • A header
    • Short transaction identifiers (6 bytes instead of 32 bytes)
    • Prefilled transactions
      • A vector where each entry is a differentially-encoded uint16_t index and the complete serialized transaction.
      • The current code only prefills the coinbase. However, the protocol allows for the inclusion of other transactions that we might expect our peer is missing. See this TODO.
  • The receiving node reconstructs the block using transactions from its mempool. If any transactions are missing, it requests them via a GETBLOCKTXN message.

  • There are two modes:
    • low-bandwidth: block sent only after peer requests it via INV/GETDATA
    • high-bandwidth: block sent to peers without request as soon as it is received, even before full validation is completed

Fuzz Testing

  • Fuzzing is a testing technique that provides quasi-random inputs to code to discover bugs and unexpected behavior. Fuzzers use feedback like code coverage as a guide for mutation of those inputs, allowing them to explore deeper paths within the target code.

  • See our fuzzing docs to start fuzzing with libFuzzer and AFL++.

  • Check out this PR’s test coverage (blockencodings.cpp and net_processing.cpp are probably most useful to look at). Walking through a coverage report to see which branches are hit or not hit is a good way to start evaluating a fuzz harness.

  • An effective fuzz test should be deterministic, to make bugs reproducible. If you’re able to get the AFL++ fuzzer running, you will see a stability metric. The goal is to get to 100%, perfect determinism! After gathering some inputs, you can also run the fuzz determinism script over the corpus to check for potential sources of instability.

  • The faster (more iterations per second) a fuzz harness is, the more quickly it will discover new code paths and potential bugs. You’ll see this metric as exec/s when fuzzing. Any operation that significantly slows down an iteration (like reading from and writing to disk) should be optimized or avoided if possible.

Fun Exercise

  • If you get the cmpctblock harness running, revert PR #33296 and see if you can reproduce the crash on this Assume statement.

  • Yeah I know, fuzzing is cool.

Questions

  1. Did you review the PR? Concept ACK, approach ACK, tested ACK, or NACK? What was your review approach? Were you able to get the fuzz test running?

  2. Where in the codebase are the main compact block helpers and processing logic? Name some of the relevant classes and functions. (Hint: nothing like a little search for NetMsgType::CMPCTBLOCK to get you started)

  3. The fuzz test sends SENDCMPCT messages with high_bandwidth randomly set. How many high bandwidth peers are allowed and does the fuzz harness test this limit? More generally, why would a peer choose to be high or low bandwidth?

  4. Compare -testdatadir and the new -fuzzcopydatadir. Why is the latter useful for performance, and why isn’t a fresh TestingSetup that mines blocks on each iteration acceptable here?

  5. Look at create_block in the harness. How many transactions do the generated blocks contain, and where do they come from? What compact block scenarios might be missed with only a few transactions in a block?

  6. In commit 254e13c FinalizeHeader is moved to util.h so it can be used by any fuzz test. What is the purpose of this function and why doesn’t it slow down the fuzzer?

  7. Commit ed813c4 sorts m_dirty_blockindex by block hash instead of pointer address. What non-determinism does this fix? The author notes this slows production code for no production benefit. Why can’t EnableFuzzDeterminism() be used here? How do you think this non-determinism should be best handled (if not the way the PR currently does)?

  8. The harness adds prefilled transactions to a compact block beyond the coinbase. Walk through the index calculations for prefilling transactions at indices 1 and 3 in a 4-transaction block. Do you think this code could be simplified or cleaned up, and if so, what do you suggest?

Meeting Log

  117:00 <marcofleon> #startmeeting
  217:00 <corebot`> marcofleon: Meeting started at 2025-10-08T17:00+0000
  317:00 <corebot`> marcofleon: Current chairs: marcofleon
  417:00 <corebot`> marcofleon: Useful commands: #action #info #idea #link #topic #motion #vote #close #endmeeting
  517:00 <corebot`> marcofleon: See also: https://hcoop-meetbot.readthedocs.io/en/stable/
  617:00 <corebot`> marcofleon: Participants should now identify themselves with '#here' or with an alias like '#here FirstLast'
  717:00 <marcofleon> hey everyone!
  817:01 <stickies-v> hi!
  917:01 <stringintech> Hi!
 1017:01 <kevkevin> hi
 1117:01 <dzxzg> hi
 1217:01 <monlovesmango> hello!
 1317:01 <danielabrozzoni> hi :)
 1417:01 <marcofleon> we're reviewing #33300 today
 1517:01 <corebot`> https://github.com/bitcoin/bitcoin/issues/33300 | fuzz: compact block harness by Crypt-iQ · Pull Request #33300 · bitcoin/bitcoin · GitHub
 1617:02 <marcofleon> feel free to jump in whenever as we go through the questions, or circle back to previous ones, etc
 1717:02 <marcofleon> okay first one
 1817:02 <yuvicc> hi
 1917:02 <marcofleon> Did you review the PR? Concept ACK, approach ACK, tested ACK, or NACK? What was your review approach? Were you able to get the fuzz test running?
 2017:02 <stickies-v> if anyone's new the review club today, feel free to say hi - even if you're just lurking!
 2117:03 <marcofleon> oh yeah^
 2217:03 <kevkevin> I did not read the PR but I am lurking today!
 2317:03 <marcofleon> lurkers are welcome
 2417:03 <yuvicc> same here
 2517:03 <stringintech> while trying to get the fuzz test running on mac, i ran into a couple of compile-time and runtime issues. small write-up for the compile fix here: https://stringintech.github.io/blog/p/fuzzing-bitcoin-core-using-afl-on-apple-silicon . i will try to enhance/extend it once i wrap my head around the runtime issues (reported one on the PR page).
 2617:03 <stringintech> and a very light review. mostly to see how random input bytes turn into meaningful data for this fuzz test.
 2717:04 <danielabrozzoni> Concept ACK, I am still finishing my review :) I looked at commits one by one, trying to understand why each change was needed. I was able to run the fuzz tests.
 2817:04 <dzxzg> Concept ACK, I lightly read `strc/test/fuzz/cmpctblock.cpp` and got the fuzz test running
 2917:04 <monlovesmango> i was able to review a bit run the fuzz test. generally ack but have sociwmae fuzz related conceptual questions.
 3017:04 <monlovesmango> a couple*
 3117:05 <stickies-v> also get in my usual cycle of not fuzzing for long enough and then no longer being able to get it to compile on macos arm
 3217:05 <marcofleon> stringintech: nice, yeah I saw your comments. I still need to look into it more to understand but I do wonder why that wasn't showing up on linux
 3317:05 <marcofleon> cool so we got people fuzzing!
 3417:06 <stringintech> marcofleon: yeah, that was also confusing for me...
 3517:06 <marcofleon> Did anybody get to trying the exercise?
 3617:06 <marcofleon> I just think it's fun to see a crash, that's why I ask
 3717:06 <stickies-v> concept ACK, spent too much time thinking about the (not very elegant, but maybe necessary) new startup option to copy datadirs
 3817:07 <marcofleon> monlovesmango: feel free to ask fuzz related questions whenever, I can try my best to answer
 3917:07 <marcofleon> stickies-v: yeah agreed that part was a bit confusing to me too. We'll get into it
 4017:07 <marcofleon> okay second q
 4117:08 <marcofleon> Where in the codebase are the main compact block helpers and processing logic? Name some of the relevant classes and functions. (Hint: nothing like a little search for NetMsgType::CMPCTBLOCK to get you started)
 4217:08 <danielabrozzoni> I tried to see it crash, but couldn't reproduce! It might be that I haven't run the fuzzer for long enough, and my exec/sec are quite low
 4317:08 <monlovesmango> well I don't want to derail anything but usually with fuzz tests you see asserts everywhere, but here I didn't see any. so my question is where/how are we asserting that results are expected?
 4417:09 <stringintech> marcofleon: i could not unfortunately. blocked by the runtime issues. but had a question on this... how much the initial input we start with matters?
 4517:09 <marcofleon> danielabrozzoni: I had that thought after the notes went up unfortunately... "oh maybe it needs to run too long to actually reproduce"
 4617:10 <marcofleon> Okay in terms of the asserts, I think this harness is meant to be catching assumes/asserts we have in production code
 4717:10 <marcofleon> just generally testing compact block logic. But that's good that you're thinking about that monlovesmango because yeah in general you want good oracles for fuzz tests
 4817:10 <stickies-v> marcofleon: `PeerManagerImpl::NewPoWValidBlock` creates a `NetMsgType::CMPCTBLOCK` message and through `m_connman.ForEachNode` sends it to our high-bandwidth peers
 4917:11 <dzxzg> The `PartiallyDownloadedBlock` class for all of the compact block construction object stuff, and a lot of the logic is inside of the `ProcessMessage` NetMsgType::CMPCTBLOCK case
 5017:11 <marcofleon> and then stickies, initial input doesn't matter, the fuzzer will find its way
 5117:12 <monlovesmango> it seemed like class CBlockHeaderAndShortTxIDs in blockencodings.h was putting together the compact block message. is that the question?
 5217:12 <danielabrozzoni> marcofleon: Ah, got it! I'll try to run it for longer, just for fun. Maybe I could also modify the harness so that it will get to the crash quicker, as an exercise
 5317:12 <marcofleon> unless you need very specific strings for serialization or network things etc... so I guess it can matter. but not too often afaik
 5417:12 <monlovesmango> marcofleon: aahhh that makes a lot of sense thanks!
 5517:12 <stringintech> thanks!
 5617:13 <stringintech> for question 2: this might be way too simplified but seems we can divide main parts into 3 categories:
 5717:13 <stringintech> 1) SEND a peer a cmpct block in HIGH bandwidth mode: in `PeerManagerImpl::NewPoWValidBlock()` callback (triggered by `ChainstateManager::AcceptBlock()`) and in `PeerManagerImpl::SendMessages()` (in case not already sent the cmpct block in `PeerManagerImpl::NewPoWValidBlock()` to this peer)
 5817:13 <stringintech> 2) SEND a peer a cmpct block in LOW bandwidth mode: process peer cmpct block request in `PeerManagerImpl::ProcessGetBlockData()` and potentially read the block from disk and construct the cmpct block - if not already done - and send it; constructing the cmpct block happens through `CBlockHeaderAndShortTxIDs` constructor in blockencodings.cpp
 5917:13 <stringintech> 3) RECEIVE a cmpct block from a peer: in `PeerManagerImpl::ProcessMessage()` processing `NetMsgType::CMPCTBLOCK` msg type, we process the cmpct block sent to us and try to construct the full block using txns in our mempool (using `PartiallyDownloadedBlock::InitData()` in blockencodings.cpp) and potentially send a `NetMsgType::GETBLOCKTXN` msg to
 6017:13 <stringintech> the same peer for the txns we did not have where we can later process the response in `PeerManagerImpl::ProcessCompactBlockTxns()` and finally call `PartiallyDownloadedBlock::FillBlock()` to see if we have the full block now.
 6117:13 <marcofleon> Yeah that question was just to get people going through the compact block code
 6217:13 <stringintech> in (1) and (2) peer may respond with a `NetMsgType::GETBLOCKTXN` msg where we potentially send the requested txns of the block in `PeerManagerImpl::SendBlockTransactions()`
 6317:13 <marcofleon> seems like you all got it
 6417:14 <danielabrozzoni> Also `MaybeSetPeerAsAnnouncingHeaderAndIDs` takes care of switching a peer to high bandwidth mode, and if we have too many high bandwidth peers, downgrading a differernt one to low bandwidth
 6517:15 <marcofleon> Nice yes. Should we move on, I don't think I see any errors in the answers?
 6617:15 <marcofleon> The fuzz test sends SENDCMPCT messages with high_bandwidth randomly set. How many high bandwidth peers are allowed and does the fuzz harness test this limit? More generally, why would a peer choose to be high or low bandwidth?
 6717:16 <stringintech> there is no limit to how many peers select us as a HB peer, right?
 6817:16 <stringintech> i found a limit on the other side: how many peers WE can select as HB which is “3” and it is enforced when PeerManagerImpl::BlockChecked() calls`PeerManagerImpl::MaybeSetPeerAsAnnouncingHeaderAndIDs()` (maintains 3 fastest peers as HB).
 6917:16 <stickies-v> stringintech: yeah "high bandwidth peers" is a bit ambiguous i think
 7017:17 <marcofleon> hmm good point. Because it's a two way street in terms of selection
 7117:18 <monlovesmango> I think you would prefer to be high bandwidth to remove latency?
 7217:18 <dzxzg> A peer doesn't choose to be high or low bandwidth, we will ask all of our peers to be low bandwidth compact block peers initially, and then in MaybeSetPeerAsAnnouncing* we'll select high bandwidth peers and send them a SENDCMPCT asking them to be a high bandwidth peers
 7317:18 <marcofleon> Yeah so we can have 3 max. And then I guess we could be a hb peer to all of our peers... although I'd have to confirm
 7417:19 <marcofleon> dzxzg: nice got it
 7517:19 <dzxzg> If someone has asked you to be their high-bandwidth peer, you'll send them a CMPCTBLOCK message in `NewPoWValidBlock()` before you have even completetd block validation
 7617:20 <marcofleon> yeah the sending the block even before validation was new to me before stickies let me know
 7717:20 <marcofleon> monlovesmango: yes that sounds about right
 7817:20 <marcofleon> the block gets sent right away so latency is very low
 7917:21 <monlovesmango> dzxzg: that actually makes a lot of sense
 8017:21 <monlovesmango> (about choosing to be high/low bandwidth)
 8117:22 <marcofleon> Does the fuzz harness test the 3 peer limit in MaybeSetPeerAsAnnouncingHeaderAndIDs?
 8217:23 <stringintech> we are setting up only 3 peers; so no?
 8317:24 <marcofleon> correct
 8417:24 <marcofleon> Would need to be more than 3 to cover that logic
 8517:24 <marcofleon> Compare -testdatadir and the new -fuzzcopydatadir. Why is the latter useful for performance, and why isn’t a fresh TestingSetup that mines blocks on each iteration acceptable here?
 8617:24 <marcofleon> stickies-v: Go off
 8717:25 <dzxzg> lOL
 8817:25 <stickies-v> Wait is that an intentional choice? The choice to connect to 3 peers seemed unrelated to the number of high bandwidth peers?
 8917:25 <marcofleon> No Eugene told me it wasn't intentional
 9017:25 <marcofleon> it's from the processmessage fuzz test I think
 9117:26 <stickies-v> okay makes sense
 9217:26 <marcofleon> But in the coverage report you can see some of the lines not hit in MaybeSet
 9317:26 <marcofleon> so making that more than 3 should hit it eventually
 9417:26 <stickies-v> yeah but then we'd probably have to add assertions that our hb accounting invariants hold
 9517:27 <dzxzg> yeah, it would be fine to increase it past 3, we actually don't even enforce anything related to high bandwidth on the receiving side
 9617:27 <monlovesmango> fresh TestingSetup would take too much time for each fuzz iteration?
 9717:28 <dzxzg> (https://github.com/bitcoin/bitcoin/pull/32606)
 9817:28 <stringintech> is this performance related (considering that mining should not take so long when in fuzzing mode) or that we want all runs to have the same initial state?
 9917:28 <marcofleon> monlovesmango: exactly. Having to mine a new 200 block chain every iteration is just too slow
10017:29 <marcofleon> stringintech: good question, I think it's mainly for performance
10117:29 <marcofleon> the determinism is there too with using the same chain. But this whole commit was mainly done to significantly speed up the test
10217:30 <stickies-v> but isn't the point of `FuzzTargetOptions::init` to do that kind of expensive initialization as a one-off?
10317:30 <marcofleon> dzxzg: I'll have to check that PR out
10417:32 <stringintech> stickies-v: i think that's what we are doing know. mining blocks and store in a static dir in init() and then copy that static dir in each run.
10517:32 <marcofleon> stickies do you mean `initialize_cmpctblock` in the harness?
10617:32 <stringintech> doing now*
10717:33 <stickies-v> well so usually test suites have "fixtures" to do expensive one-off initialization that can be shared across tests, e.g. we have the same in our unit tests using boost
10817:33 <stickies-v> it appeared to me that the `FuzzTargetOptions::init` option serves a similar purpose (but i don't know the fuzzing code well enough)
10917:34 <stickies-v> if we just want to avoid mining a 200 chain for every iteration, why isn't the `.init` option enough for that?
11017:35 <marcofleon> Wait is this in fuzz.cpp? why can't I find this
11117:35 <marcofleon> the fuzztargetoptions::init
11217:35 <marcofleon> oh I see nvm
11317:35 <stickies-v> yes, it's in fuzz/fuzz.cpp, and it's what `.init=initialize_cmpctblock` gets passed to
11417:37 <marcofleon> Yeah so that's what allows us to have the one time initialize functions in fuzz targets
11517:37 <marcofleon> runs once before the iteration
11617:37 <marcofleon> but what's your question?
11717:38 <stickies-v> why don't we just generate the 200 blocks in the .init function and avoid copying datadirs?
11817:38 <stickies-v> also don't mean to derail the review club with my lack of fuzzing knowledge so we can move on from this anytime
11917:38 <marcofleon> I think beacuse the test is stateful
12017:39 <stringintech89> does each run alter its data dir?
12117:39 <marcofleon> the testingsetup is modified in the iteration
12217:40 <marcofleon> Yeah I think so, usually the testing setup stuff would be done in the init as a one off. But for this test specifically, its done in the iteration which is unusual
12317:40 <stickies-v> aha, okay, that's unfortunate
12417:40 <dzxzg> Is the problem that blocks are being mined and then announced?
12517:41 <dzxzg> Maybe there is an obvious reason why this is dumb, but it really seems like there should be a way to have a memory-only chainstate object, and then we could just reset to that at the end of each fuzz epoch(not sure if epoch is the right word)
12617:41 <dzxzg> That would also make this fuzz test faster by avoiding hitting the disk at all.
12717:42 <dzxzg> Or maybe it's not a bad idea, just easier said than done to implement that
12817:42 <marcofleon> My first question when I saw this test was why can't we do this in memory. And Niklas was saying its just the validation code doesn't allow it. We need to do some refactoring
12917:43 <stringintech89> just asking out of curiosity: wouldn't testing memory only, reduce coverage?
13017:43 <dzxzg> marcofleon: Figures 😿
13117:43 <marcofleon> This datadir commit is something I need to think about more too. For now let's move on. I think the approach might be okay though
13217:43 <marcofleon> Look at create_block in the harness. How many transactions do the generated blocks contain, and where do they come from? What compact block scenarios might be missed with only a few transactions in a block?
13317:44 <marcofleon> stringintech89: yeah it would, by quite a bit I believe
13417:44 <stringintech89> thanks
13517:44 <dzxzg> stringintech89: It would reduce coverage of this particular fuzz test, but that coverage is redundant I think this test should just fuzz compact block logic which doesn't do anything special with the disk, I think existing validation fuzzers should cover that stuff
13617:45 <dzxzg> (IMO)
13717:45 <stringintech89> ah makes sense! thanks
13817:46 <stringintech89> for the next question: max 3 txns: coinbase, at most one from mempool, at most one not in mempool. was wondering if including more than one from mempool (or more than one not in mempool) would cover different code paths and lead to more coverage.
13917:46 <monlovesmango> it seems like create_block only includes 2 txs max? one from mempool and other adhoc created
14017:46 <monlovesmango> oh duh, forgot coinbase :)
14117:46 <marcofleon> haha yes coinbase too. 1-3 txs
14217:47 <dzxzg> Why not more?
14317:48 <marcofleon> I think crypt-iQ was saying he might change that part of the test a bit
14417:49 <marcofleon> Or just generally improve it, I don't see why there couldn't be some more
14517:49 <dzxzg> marcofleon: Makes sense, thanks
14617:50 <marcofleon> I'm gonna switch question 6 and 7 for now in the interest of time if that's okay. Becauase i like 7 better :)
14717:50 <marcofleon> Commit ed813c4 sorts m_dirty_blockindex by block hash instead of pointer address. What non-determinism does this fix? The author notes this slows production code for no production benefit. Why can’t EnableFuzzDeterminism() be used here? How do you think this non-determinism should be best handled (if not the way the PR currently does)?
14817:50 <monlovesmango> what compact block scenarios might be missed with this low tx count?
14917:50 <dzxzg> the second half of question 5, this doesn't exercise some of the prefill index stuff, where prefills have an index counting from the last prefill
15017:50 <dzxzg> for example
15117:51 <marcofleon> oh yes! My bad
15217:51 <marcofleon> I was also thinking of shortid collisions
15317:51 <dzxzg> oh yeah that too
15417:51 <monlovesmango> dzxzg: marcofleon: great answers thanks :)
15517:52 <dzxzg> We definitely want to have coverage of shortid collisions!
15617:54 <stringintech89> for question 7: it tries to make the order we write changes to block index db deterministic
15717:55 <stringintech89> and if we want to include the new sort in the set type itself, EnableFuzzDeterminism() does not work and we would need macros perhaps.
15817:55 <monlovesmango> why does block index db need to be deterministic?
15917:56 <monlovesmango> or why do the writes need to deterministic?
16017:56 <marcofleon> Yeah basically the iteration order won't be deterministic with just a normal std::set which orders based on memory address. so it's changed to block hash to make it the same order every time
16117:57 <stringintech89> but as marcofleon mentioned on the PR page, maybe we can include a runtime sorting logic just when fuzzing; then EnableFuzzDeterminism() can be used perhaps
16217:57 <eugenesiegel> hi, sorry I thought this review club was at a different time. runtime sorting doesn't work because the std::sort call itself will be non-deterministic
16317:58 <eugenesiegel> different executions will have different memory addresses so the compare function will be called a different amount of times
16417:59 <marcofleon> Oh that makes sense. so it doesn't actually solve the non-determinism
16517:59 <dzxzg> Just drawing it out a bit more to make sure I understand why we care if it's in the same order every run, we write from the set of dirty block indexes in the order of the set, and if we don't write blockindexes in the same order every time, we would have different behavior running the same seed two times?
16617:59 <marcofleon> also monlovesmango: I think it's just for general stability
16718:00 <marcofleon> in gneral fuzz tests should be as deterministic as possible. But not sure if it's worth it in this case, because this non-determinism might not super impactful?
16818:00 <monlovesmango> ok. so just for my understanding is there anything explicity comparing this between executions?
16918:00 <monlovesmango> ohhh ok so when there is a failure it can be preproduced, got it
17018:01 <eugenesiegel> dzxzg: we won't technically have different behavior since iirc the db is a key-value store. The issue is that the fuzzer will think it's gaining (or losing) coverage when nothing has actually happened
17118:01 <monlovesmango> reproduced*
17218:01 <marcofleon> But I could be wrong there, it could be for reproducing a potential crash, this would get in the way of reproducing it
17318:01 <dzxzg> I wonder what the perf cost is to normal node running of adding the pointer accesses
17418:01 <marcofleon> okay we've reached the end, thank you for coming! we can of course continue discussing
17518:01 <marcofleon> #endmeeting
17618:01 <corebot`> marcofleon: Meeting ended at 2025-10-08T18:01+0000
17718:01 <corebot`> marcofleon: Raw log: https://achow101.com/ircmeetings/2025/bitcoin-core-pr-reviews.2025-10-08_17_00.log.json
17818:01 <corebot`> marcofleon: Formatted log: https://achow101.com/ircmeetings/2025/bitcoin-core-pr-reviews.2025-10-08_17_00.log.html
17918:01 <corebot`> marcofleon: Minutes: https://achow101.com/ircmeetings/2025/bitcoin-core-pr-reviews.2025-10-08_17_00.html
18018:02 <dzxzg> eugenesiegel: Makes sense, thanks
18118:02 <monlovesmango> thanks marcofleon and all!
18218:02 <dzxzg> Thanks marcofleon!
18318:02 <monlovesmango> I did have another question if anyone has time to answer
18418:02 <eugenesiegel> monlovesmango: I'm still around to answer any questions
18518:02 <monlovesmango> for this code chunk https://github.com/Crypt-iQ/bitcoin/blob/ed813c48f826d083becf93c741b483774c850c86/src/test/fuzz/cmpctblock.cpp#L308C16-L320C18
18618:03 <stringintech89> Thank you marcofleon
18718:03 <monlovesmango> why is the loop not to i<=num_txs?
18818:04 <dzxzg> that would go one past the end of the block
18918:04 <dzxzg> the reason it starts at i = 1 is to skip the coinbase
19018:04 <eugenesiegel> yup, what dzxzg said
19118:04 <monlovesmango> dzxzg: thanks that makes sense
19218:05 <monlovesmango> easy question :)
19318:07 <marcofleon> wait so I can understand the `m_dirty_blockindex` determinism more
19418:07 <marcofleon> It's not about the order it's written to leveldb then?
19518:08 <marcofleon> wait nvm I think I just reread your message, you're saying std::sort itself is non deterministic
19618:08 <marcofleon> I thought it said std::set for a second
19718:11 <eugenesiegel> sorry, I think my answer was confusing. if there is a different order written to leveldb, the internal MemTable is non-deterministic, but since these are key-value pairs it shouldn't? matter on-disk
19818:14 <eugenesiegel> If we insert into `m_dirty_blockindex` without sorting on insert, then `m_dirty_blockindex` is in random order. this means the sort function will call whatever std::less function a variable number of times each run for the same input. I'm completely ok with removing the commit
19918:16 <stringintech89> eugenesiegel: can you please elaborate a bit on this "the issue is that the fuzzer will think it's gaining (or losing) coverage when nothing has actually happened"
20018:19 <marcofleon> eugenesiegel: thanks I got it, that makes more sense. I'm gonna play around with the determinism script a bit and see what comes up
20118:20 <eugenesiegel> stringintech89: let's say we removed the `m_dirty_blockindex` commit, meaning the harness is now non-deterministic. if the fuzzer is given an input that calls WriteBatchSync and modifies one bit of the input that's uninteresting (doesn't gain or lose coverage), then because the harness is non-deterministic, the fuzzer may actually see an increase
20218:20 <eugenesiegel> in the coverage counters and think it's due to this one bit that was modified
20318:25 <stringintech> eugenesiegel: ahh thank you! i think i got it (though not so clear how fuzzer calculates the coverage). will read more on it and ask more questions if still not clear.
20418:26 <sipa> stringintech: when you compile with fuzzing, the compiler inserts instrumentation for tracking branch and other coverage
20518:26 <sipa> which the fuzzer library then uses to guide the mutations it makes, and the corpus it keeps
20618:31 <eugenesiegel> stringintech: This might be a bit out-dated, but I found this AFL writeup very helpful when learning how coverage feedback with fuzzing worked: https://lcamtuf.coredump.cx/afl/technical_details.txt
20718:37 <stringintech66> thanks a lot sipa!
20818:37 <stringintech66> so in the example eugenesiegel explained, for the two given inputs that have the same coverage of the code paths we are interested in, fuzzer might see different coverage score since different amount of sorting might happen...
20918:39 <stringintech66> thanks a lot for the link eugenesiegel! i will definitely check it out