partially_downloaded_block.cpp raw
1 // Copyright (c) 2023-present The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or https://opensource.org/license/mit.
4
5 #include <blockencodings.h>
6 #include <consensus/merkle.h>
7 #include <consensus/validation.h>
8 #include <primitives/block.h>
9 #include <primitives/transaction.h>
10 #include <test/fuzz/FuzzedDataProvider.h>
11 #include <test/fuzz/fuzz.h>
12 #include <test/fuzz/util.h>
13 #include <test/fuzz/util/mempool.h>
14 #include <test/util/setup_common.h>
15 #include <test/util/time.h>
16 #include <test/util/txmempool.h>
17 #include <txmempool.h>
18 #include <util/check.h>
19 #include <util/time.h>
20 #include <util/translation.h>
21
22 #include <cstddef>
23 #include <cstdint>
24 #include <limits>
25 #include <memory>
26 #include <optional>
27 #include <set>
28 #include <vector>
29
30 namespace {
31 const TestingSetup* g_setup;
32 } // namespace
33
34 void initialize_pdb()
35 {
36 static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
37 g_setup = testing_setup.get();
38 }
39
40 PartiallyDownloadedBlock::IsBlockMutatedFn FuzzedIsBlockMutated(bool result)
41 {
42 return [result](const CBlock& block, bool) {
43 return result;
44 };
45 }
46
47 FUZZ_TARGET(partially_downloaded_block, .init = initialize_pdb)
48 {
49 SeedRandomStateForTest(SeedRand::ZEROS);
50 FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
51 FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};
52
53 auto block{ConsumeDeserializable<CBlock>(fuzzed_data_provider, TX_WITH_WITNESS)};
54 if (!block || block->vtx.size() == 0 ||
55 block->vtx.size() >= std::numeric_limits<uint16_t>::max()) {
56 return;
57 }
58
59 CBlockHeaderAndShortTxIDs cmpctblock{*block, fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
60
61 bilingual_str error;
62 CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};
63 Assert(error.empty());
64 PartiallyDownloadedBlock pdb{&pool};
65
66 // Set of available transactions (mempool or extra_txn)
67 std::set<uint16_t> available;
68 // The coinbase is always available
69 available.insert(0);
70
71 std::vector<std::pair<Wtxid, CTransactionRef>> extra_txn;
72 for (size_t i = 1; i < block->vtx.size(); ++i) {
73 auto tx{block->vtx[i]};
74
75 bool add_to_extra_txn{fuzzed_data_provider.ConsumeBool()};
76 bool add_to_mempool{fuzzed_data_provider.ConsumeBool()};
77
78 if (add_to_extra_txn) {
79 extra_txn.emplace_back(tx->GetWitnessHash(), tx);
80 available.insert(i);
81 }
82
83 if (add_to_mempool && !pool.exists(tx->GetHash())) {
84 LOCK2(cs_main, pool.cs);
85 TryAddToMempool(pool, ConsumeTxMemPoolEntry(fuzzed_data_provider, *tx));
86 available.insert(i);
87 }
88 }
89
90 auto init_status{pdb.InitData(cmpctblock, extra_txn)};
91
92 std::vector<CTransactionRef> missing;
93 // Whether we skipped a transaction that should be included in `missing`.
94 // FillBlock should never return READ_STATUS_OK if that is the case.
95 bool skipped_missing{false};
96 for (size_t i = 0; i < cmpctblock.BlockTxCount(); i++) {
97 // If init_status == READ_STATUS_OK then a available transaction in the
98 // compact block (i.e. IsTxAvailable(i) == true) implies that we marked
99 // that transaction as available above (i.e. available.contains(i)).
100 // The reverse is not true, due to possible compact block short id
101 // collisions (i.e. available.contains(i) does not imply
102 // IsTxAvailable(i) == true).
103 if (init_status == READ_STATUS_OK) {
104 assert(!pdb.IsTxAvailable(i) || available.contains(i));
105 }
106
107 bool skip{fuzzed_data_provider.ConsumeBool()};
108 if (!pdb.IsTxAvailable(i) && !skip) {
109 missing.push_back(block->vtx[i]);
110 }
111
112 skipped_missing |= (!pdb.IsTxAvailable(i) && skip);
113 }
114
115 bool segwit_active{fuzzed_data_provider.ConsumeBool()};
116
117 // Mock IsBlockMutated
118 bool fail_block_mutated{fuzzed_data_provider.ConsumeBool()};
119 pdb.m_check_block_mutated_mock = FuzzedIsBlockMutated(fail_block_mutated);
120
121 CBlock reconstructed_block;
122 auto fill_status{pdb.FillBlock(reconstructed_block, missing, segwit_active)};
123 switch (fill_status) {
124 case READ_STATUS_OK:
125 assert(!skipped_missing);
126 assert(!fail_block_mutated);
127 assert(block->GetHash() == reconstructed_block.GetHash());
128 break;
129 case READ_STATUS_FAILED:
130 assert(fail_block_mutated);
131 break;
132 case READ_STATUS_INVALID:
133 break;
134 }
135 }
136