fuzz coverage

Coverage Report

Created: 2025-09-17 22:41

/Users/eugenesiegel/btc/bitcoin/src/blockencodings.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) 2016-2022 The Bitcoin Core developers
2
// Distributed under the MIT software license, see the accompanying
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5
#include <blockencodings.h>
6
#include <chainparams.h>
7
#include <common/system.h>
8
#include <consensus/consensus.h>
9
#include <consensus/validation.h>
10
#include <crypto/sha256.h>
11
#include <crypto/siphash.h>
12
#include <logging.h>
13
#include <random.h>
14
#include <streams.h>
15
#include <txmempool.h>
16
#include <validation.h>
17
18
#include <unordered_map>
19
20
CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, const uint64_t nonce) :
21
2.77M
        nonce(nonce),
22
2.77M
        shorttxids(block.vtx.size() - 1), prefilledtxn(1), header(block) {
23
2.77M
    FillShortTxIDSelector();
24
    //TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase
25
2.77M
    prefilledtxn[0] = {0, block.vtx[0]};
26
7.72M
    for (size_t i = 1; i < block.vtx.size(); 
i++4.94M
) {
27
4.94M
        const CTransaction& tx = *block.vtx[i];
28
4.94M
        shorttxids[i - 1] = GetShortID(tx.GetWitnessHash());
29
4.94M
    }
30
2.77M
}
31
32
5.24M
void CBlockHeaderAndShortTxIDs::FillShortTxIDSelector() const {
33
5.24M
    DataStream stream{};
34
5.24M
    stream << header << nonce;
35
5.24M
    CSHA256 hasher;
36
5.24M
    hasher.Write((unsigned char*)&(*stream.begin()), stream.end() - stream.begin());
37
5.24M
    uint256 shorttxidhash;
38
5.24M
    hasher.Finalize(shorttxidhash.begin());
39
5.24M
    shorttxidk0 = shorttxidhash.GetUint64(0);
40
5.24M
    shorttxidk1 = shorttxidhash.GetUint64(1);
41
5.24M
}
42
43
37.3M
uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const Wtxid& wtxid) const {
44
37.3M
    static_assert(SHORTTXIDS_LENGTH == 6, "shorttxids calculation assumes 6-byte shorttxids");
45
37.3M
    return SipHashUint256(shorttxidk0, shorttxidk1, wtxid.ToUint256()) & 0xffffffffffffL;
46
37.3M
}
47
48
/* Reconstructing a compact block is in the hot-path for block relay,
49
 * so we want to do it as quickly as possible. Because this often
50
 * involves iterating over the entire mempool, we put all the data we
51
 * need (ie the wtxid and a reference to the actual transaction data)
52
 * in a vector and iterate over the vector directly. This allows optimal
53
 * CPU caching behaviour, at a cost of only 40 bytes per transaction.
54
 */
55
ReadStatus PartiallyDownloadedBlock::InitData(const CBlockHeaderAndShortTxIDs& cmpctblock, const std::vector<std::pair<Wtxid, CTransactionRef>>& extra_txn)
56
532k
{
57
532k
    LogDebug(BCLog::CMPCTBLOCK, "Initializing PartiallyDownloadedBlock for block %s using a cmpctblock of %u bytes\n", cmpctblock.header.GetHash().ToString(), GetSerializeSize(cmpctblock));
Line
Count
Source
381
532k
#define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__)
Line
Count
Source
373
532k
    do {                                                              \
374
532k
        if (LogAcceptCategory((category), (level))) {                 \
375
0
            bool rate_limit{level >= BCLog::Level::Info};             \
376
0
            LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \
Line
Count
Source
350
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__)
377
0
        }                                                             \
378
532k
    } while (0)
58
532k
    if (cmpctblock.header.IsNull() || (cmpctblock.shorttxids.empty() && 
cmpctblock.prefilledtxn.empty()21.4k
))
59
0
        return READ_STATUS_INVALID;
60
532k
    if (cmpctblock.shorttxids.size() + cmpctblock.prefilledtxn.size() > MAX_BLOCK_WEIGHT / MIN_SERIALIZABLE_TRANSACTION_WEIGHT)
61
0
        return READ_STATUS_INVALID;
62
63
532k
    if (!header.IsNull() || !txn_available.empty()) 
return READ_STATUS_INVALID0
;
64
65
532k
    header = cmpctblock.header;
66
532k
    txn_available.resize(cmpctblock.BlockTxCount());
67
68
532k
    int32_t lastprefilledindex = -1;
69
1.07M
    for (size_t i = 0; i < cmpctblock.prefilledtxn.size(); 
i++538k
) {
70
538k
        if (cmpctblock.prefilledtxn[i].tx->IsNull())
71
0
            return READ_STATUS_INVALID;
72
73
538k
        lastprefilledindex += cmpctblock.prefilledtxn[i].index + 1; //index is a uint16_t, so can't overflow here
74
538k
        if (lastprefilledindex > std::numeric_limits<uint16_t>::max())
75
0
            return READ_STATUS_INVALID;
76
538k
        if ((uint32_t)lastprefilledindex > cmpctblock.shorttxids.size() + i) {
77
            // If we are inserting a tx at an index greater than our full list of shorttxids
78
            // plus the number of prefilled txn we've inserted, then we have txn for which we
79
            // have neither a prefilled txn or a shorttxid!
80
0
            return READ_STATUS_INVALID;
81
0
        }
82
538k
        txn_available[lastprefilledindex] = cmpctblock.prefilledtxn[i].tx;
83
538k
    }
84
532k
    prefilled_count = cmpctblock.prefilledtxn.size();
85
86
    // Calculate map of txids -> positions and check mempool to see what we have (or don't)
87
    // Because well-formed cmpctblock messages will have a (relatively) uniform distribution
88
    // of short IDs, any highly-uneven distribution of elements can be safely treated as a
89
    // READ_STATUS_FAILED.
90
532k
    std::unordered_map<uint64_t, uint16_t> shorttxids(cmpctblock.shorttxids.size());
91
532k
    uint16_t index_offset = 0;
92
1.52M
    for (size_t i = 0; i < cmpctblock.shorttxids.size(); 
i++994k
) {
93
1.50M
        while (txn_available[i + index_offset])
94
512k
            index_offset++;
95
994k
        shorttxids[cmpctblock.shorttxids[i]] = i + index_offset;
96
        // To determine the chance that the number of entries in a bucket exceeds N,
97
        // we use the fact that the number of elements in a single bucket is
98
        // binomially distributed (with n = the number of shorttxids S, and p =
99
        // 1 / the number of buckets), that in the worst case the number of buckets is
100
        // equal to S (due to std::unordered_map having a default load factor of 1.0),
101
        // and that the chance for any bucket to exceed N elements is at most
102
        // buckets * (the chance that any given bucket is above N elements).
103
        // Thus: P(max_elements_per_bucket > N) <= S * (1 - cdf(binomial(n=S,p=1/S), N)).
104
        // If we assume blocks of up to 16000, allowing 12 elements per bucket should
105
        // only fail once per ~1 million block transfers (per peer and connection).
106
994k
        if (shorttxids.bucket_size(shorttxids.bucket(cmpctblock.shorttxids[i])) > 12)
107
0
            return READ_STATUS_FAILED;
108
994k
    }
109
    // TODO: in the shortid-collision case, we should instead request both transactions
110
    // which collided. Falling back to full-block-request here is overkill.
111
532k
    if (shorttxids.size() != cmpctblock.shorttxids.size())
112
10.0k
        return READ_STATUS_FAILED; // Short ID collision
113
114
522k
    std::vector<bool> have_txn(txn_available.size());
115
522k
    {
116
522k
    LOCK(pool->cs);
Line
Count
Source
259
522k
#define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
Line
Count
Source
11
522k
#define UNIQUE_NAME(name) PASTE2(name, __COUNTER__)
Line
Count
Source
9
522k
#define PASTE2(x, y) PASTE(x, y)
Line
Count
Source
8
522k
#define PASTE(x, y) x ## y
117
1.57M
    for (const auto& [wtxid, txit] : pool->txns_randomized) {
118
1.57M
        uint64_t shortid = cmpctblock.GetShortID(wtxid);
119
1.57M
        std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(shortid);
120
1.57M
        if (idit != shorttxids.end()) {
121
570k
            if (!have_txn[idit->second]) {
122
570k
                txn_available[idit->second] = txit->GetSharedTx();
123
570k
                have_txn[idit->second]  = true;
124
570k
                mempool_count++;
125
570k
            } else {
126
                // If we find two mempool txn that match the short id, just request it.
127
                // This should be rare enough that the extra bandwidth doesn't matter,
128
                // but eating a round-trip due to FillBlock failure would be annoying
129
0
                if (txn_available[idit->second]) {
130
0
                    txn_available[idit->second].reset();
131
0
                    mempool_count--;
132
0
                }
133
0
            }
134
570k
        }
135
        // Though ideally we'd continue scanning for the two-txn-match-shortid case,
136
        // the performance win of an early exit here is too good to pass up and worth
137
        // the extra risk.
138
1.57M
        if (mempool_count == shorttxids.size())
139
120k
            break;
140
1.57M
    }
141
522k
    }
142
143
31.1M
    for (size_t i = 0; i < extra_txn.size(); 
i++30.5M
) {
144
30.7M
        uint64_t shortid = cmpctblock.GetShortID(extra_txn[i].first);
145
30.7M
        std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(shortid);
146
30.7M
        if (idit != shorttxids.end()) {
147
74.4k
            if (!have_txn[idit->second]) {
148
72.8k
                txn_available[idit->second] = extra_txn[i].second;
149
72.8k
                have_txn[idit->second]  = true;
150
72.8k
                mempool_count++;
151
72.8k
                extra_count++;
152
72.8k
            } else {
153
                // If we find two mempool/extra txn that match the short id, just
154
                // request it.
155
                // This should be rare enough that the extra bandwidth doesn't matter,
156
                // but eating a round-trip due to FillBlock failure would be annoying
157
                // Note that we don't want duplication between extra_txn and mempool to
158
                // trigger this case, so we compare witness hashes first
159
1.57k
                if (txn_available[idit->second] &&
160
1.57k
                        txn_available[idit->second]->GetWitnessHash() != extra_txn[i].second->GetWitnessHash()) {
161
0
                    txn_available[idit->second].reset();
162
0
                    mempool_count--;
163
0
                    extra_count--;
164
0
                }
165
1.57k
            }
166
74.4k
        }
167
        // Though ideally we'd continue scanning for the two-txn-match-shortid case,
168
        // the performance win of an early exit here is too good to pass up and worth
169
        // the extra risk.
170
30.7M
        if (mempool_count == shorttxids.size())
171
188k
            break;
172
30.7M
    }
173
174
522k
    LogDebug(BCLog::CMPCTBLOCK, "Initialized PartiallyDownloadedBlock for block %s using a cmpctblock of %u bytes\n", cmpctblock.header.GetHash().ToString(), GetSerializeSize(cmpctblock));
Line
Count
Source
381
522k
#define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__)
Line
Count
Source
373
522k
    do {                                                              \
374
522k
        if (LogAcceptCategory((category), (level))) {                 \
375
0
            bool rate_limit{level >= BCLog::Level::Info};             \
376
0
            LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \
Line
Count
Source
350
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__)
377
0
        }                                                             \
378
522k
    } while (0)
175
176
522k
    return READ_STATUS_OK;
177
532k
}
178
179
bool PartiallyDownloadedBlock::IsTxAvailable(size_t index) const
180
1.39M
{
181
1.39M
    if (header.IsNull()) 
return false0
;
182
183
1.39M
    assert(index < txn_available.size());
184
1.39M
    return txn_available[index] != nullptr;
185
1.39M
}
186
187
ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<CTransactionRef>& vtx_missing, bool segwit_active)
188
244k
{
189
244k
    if (header.IsNull()) 
return READ_STATUS_INVALID131
;
190
191
244k
    uint256 hash = header.GetHash();
192
244k
    block = header;
193
244k
    block.vtx.resize(txn_available.size());
194
195
244k
    unsigned int tx_missing_size = 0;
196
244k
    size_t tx_missing_offset = 0;
197
883k
    for (size_t i = 0; i < txn_available.size(); 
i++639k
) {
198
682k
        if (!txn_available[i]) {
199
48.5k
            if (vtx_missing.size() <= tx_missing_offset)
200
43.6k
                return READ_STATUS_INVALID;
201
4.94k
            block.vtx[i] = vtx_missing[tx_missing_offset++];
202
4.94k
            tx_missing_size += block.vtx[i]->GetTotalSize();
203
4.94k
        } else
204
634k
            block.vtx[i] = std::move(txn_available[i]);
205
682k
    }
206
207
    // Make sure we can't call FillBlock again.
208
200k
    header.SetNull();
209
200k
    txn_available.clear();
210
211
200k
    if (vtx_missing.size() != tx_missing_offset)
212
593
        return READ_STATUS_INVALID;
213
214
    // Check for possible mutations early now that we have a seemingly good block
215
200k
    IsBlockMutatedFn check_mutated{m_check_block_mutated_mock ? 
m_check_block_mutated_mock0
: IsBlockMutated};
216
200k
    if (check_mutated(/*block=*/block,
217
200k
                       /*check_witness_root=*/segwit_active)) {
218
207
        return READ_STATUS_FAILED; // Possible Short ID collision
219
207
    }
220
221
200k
    LogDebug(BCLog::CMPCTBLOCK, "Successfully reconstructed block %s with %u txn prefilled, %u txn from mempool (incl at least %u from extra pool) and %u txn (%u bytes) requested\n", hash.ToString(), prefilled_count, mempool_count, extra_count, vtx_missing.size(), tx_missing_size);
Line
Count
Source
381
200k
#define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__)
Line
Count
Source
373
200k
    do {                                                              \
374
200k
        if (LogAcceptCategory((category), (level))) {                 \
375
0
            bool rate_limit{level >= BCLog::Level::Info};             \
376
0
            LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \
Line
Count
Source
350
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__)
377
0
        }                                                             \
378
200k
    } while (0)
222
200k
    if (vtx_missing.size() < 5) {
223
200k
        for (const auto& tx : vtx_missing) {
224
4.14k
            LogDebug(BCLog::CMPCTBLOCK, "Reconstructed block %s required tx %s\n", hash.ToString(), tx->GetHash().ToString());
Line
Count
Source
381
4.14k
#define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__)
Line
Count
Source
373
4.14k
    do {                                                              \
374
4.14k
        if (LogAcceptCategory((category), (level))) {                 \
375
0
            bool rate_limit{level >= BCLog::Level::Info};             \
376
0
            LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \
Line
Count
Source
350
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__)
377
0
        }                                                             \
378
4.14k
    } while (0)
225
4.14k
        }
226
200k
    }
227
228
200k
    return READ_STATUS_OK;
229
200k
}