Coverage Report

Created: 2026-10-19 15:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/blockencodings.cpp
Line
Count
Source
1
// Copyright (c) 2016-present 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/categories.h>
13
#include <random.h>
14
#include <streams.h>
15
#include <txmempool.h>
16
#include <util/log.h>
17
#include <validation.h>
18
19
#include <unordered_map>
20
21
CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, uint64_t nonce)
22
74.6k
    : nonce(nonce),
23
74.6k
      shorttxids(block.vtx.size() - 1),
24
74.6k
      prefilledtxn(1),
25
74.6k
      header(block)
26
74.6k
{
27
74.6k
    FillShortTxIDSelector();
28
    // TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase
29
74.6k
    prefilledtxn[0] = {0, block.vtx[0]};
30
126k
    for (size_t i = 1; i < block.vtx.size(); i++) {
31
52.1k
        const CTransaction& tx = *block.vtx[i];
32
52.1k
        shorttxids[i - 1] = GetShortID(tx.GetWitnessHash());
33
52.1k
    }
34
74.6k
}
35
36
void CBlockHeaderAndShortTxIDs::FillShortTxIDSelector() const
37
96.5k
{
38
96.5k
    DataStream stream{};
39
96.5k
    stream << header << nonce;
40
96.5k
    CSHA256 hasher;
41
96.5k
    hasher.Write((unsigned char*)&(*stream.begin()), stream.end() - stream.begin());
42
96.5k
    uint256 shorttxidhash;
43
96.5k
    hasher.Finalize(shorttxidhash.begin());
44
96.5k
    m_hasher.emplace(shorttxidhash.GetUint64(0), shorttxidhash.GetUint64(1));
45
96.5k
}
46
47
uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const Wtxid& wtxid) const
48
154k
{
49
154k
    static_assert(SHORTTXIDS_LENGTH == 6, "shorttxids calculation assumes 6-byte shorttxids");
50
154k
    return (*Assert(m_hasher))(wtxid.ToUint256()) & 0xffffffffffffL;
51
154k
}
52
53
/* Reconstructing a compact block is in the hot-path for block relay,
54
 * so we want to do it as quickly as possible. Because this often
55
 * involves iterating over the entire mempool, we put all the data we
56
 * need (ie the wtxid and a reference to the actual transaction data)
57
 * in a vector and iterate over the vector directly. This allows optimal
58
 * CPU caching behaviour, at a cost of only 40 bytes per transaction.
59
 */
60
ReadStatus PartiallyDownloadedBlock::InitData(const CBlockHeaderAndShortTxIDs& cmpctblock, const std::vector<std::pair<Wtxid, CTransactionRef>>& extra_txn)
61
18.4k
{
62
18.4k
    LogDebug(BCLog::CMPCTBLOCK, "Initializing PartiallyDownloadedBlock for block %s using a cmpctblock of %u bytes\n", cmpctblock.header.GetHash().ToString(), GetSerializeSize(cmpctblock));
63
18.4k
    if (cmpctblock.header.IsNull() || (cmpctblock.shorttxids.empty() && cmpctblock.prefilledtxn.empty()))
64
0
        return READ_STATUS_INVALID;
65
18.4k
    if (cmpctblock.shorttxids.size() + cmpctblock.prefilledtxn.size() > MAX_BLOCK_WEIGHT / MIN_SERIALIZABLE_TRANSACTION_WEIGHT)
66
0
        return READ_STATUS_INVALID;
67
68
18.4k
    if (!header.IsNull() || !txn_available.empty()) return READ_STATUS_INVALID;
69
70
18.4k
    header = cmpctblock.header;
71
18.4k
    txn_available.resize(cmpctblock.BlockTxCount());
72
73
18.4k
    const bool debug_log{util::log::ShouldDebugLog(BCLog::CMPCTBLOCK)};
74
    // Track the prefills which were not already present in the mempool or extrapool.
75
18.4k
    std::map<Wtxid, size_t> leftover_prefills;
76
77
18.4k
    int32_t lastprefilledindex = -1;
78
18.4k
    prefilled_count = cmpctblock.prefilledtxn.size();
79
36.9k
    for (size_t i = 0; i < prefilled_count; i++) {
80
18.4k
        if (cmpctblock.prefilledtxn[i].tx->IsNull())
81
0
            return READ_STATUS_INVALID;
82
83
18.4k
        lastprefilledindex += cmpctblock.prefilledtxn[i].index + 1; //index is a uint16_t, so can't overflow here
84
18.4k
        if (lastprefilledindex > std::numeric_limits<uint16_t>::max())
85
0
            return READ_STATUS_INVALID;
86
18.4k
        if ((uint32_t)lastprefilledindex > cmpctblock.shorttxids.size() + i) {
87
            // If we are inserting a tx at an index greater than our full list of shorttxids
88
            // plus the number of prefilled txn we've inserted, then we have txn for which we
89
            // have neither a prefilled txn or a shorttxid!
90
2
            return READ_STATUS_INVALID;
91
2
        }
92
93
18.4k
        if (debug_log) {
94
18.4k
            const CTransactionRef& tx{cmpctblock.prefilledtxn[i].tx};
95
18.4k
            const size_t tx_size{tx->ComputeTotalSize()};
96
18.4k
            prefilled_size += tx_size;
97
98
18.4k
            leftover_prefills.emplace(tx->GetWitnessHash(), tx_size);
99
18.4k
        }
100
18.4k
        txn_available[lastprefilledindex] = cmpctblock.prefilledtxn[i].tx;
101
18.4k
    }
102
103
104
    // Calculate map of txids -> positions and check mempool to see what we have (or don't)
105
    // Because well-formed cmpctblock messages will have a (relatively) uniform distribution
106
    // of short IDs, any highly-uneven distribution of elements can be safely treated as a
107
    // READ_STATUS_FAILED.
108
18.4k
    std::unordered_map<uint64_t, uint16_t> shorttxids(cmpctblock.shorttxids.size());
109
18.4k
    uint16_t index_offset = 0;
110
33.0k
    for (size_t i = 0; i < cmpctblock.shorttxids.size(); i++) {
111
15.5k
        while (txn_available[i + index_offset])
112
1.02k
            index_offset++;
113
14.5k
        shorttxids[cmpctblock.shorttxids[i]] = i + index_offset;
114
        // To determine the chance that the number of entries in a bucket exceeds N,
115
        // we use the fact that the number of elements in a single bucket is
116
        // binomially distributed (with n = the number of shorttxids S, and p =
117
        // 1 / the number of buckets), that in the worst case the number of buckets is
118
        // equal to S (due to std::unordered_map having a default load factor of 1.0),
119
        // and that the chance for any bucket to exceed N elements is at most
120
        // buckets * (the chance that any given bucket is above N elements).
121
        // Thus: P(max_elements_per_bucket > N) <= S * (1 - cdf(binomial(n=S,p=1/S), N)).
122
        // If we assume blocks of up to 16000, allowing 12 elements per bucket should
123
        // only fail once per ~1 million block transfers (per peer and connection).
124
14.5k
        if (shorttxids.bucket_size(shorttxids.bucket(cmpctblock.shorttxids[i])) > 12)
125
0
            return READ_STATUS_FAILED;
126
14.5k
    }
127
18.4k
    if (shorttxids.size() != cmpctblock.shorttxids.size())
128
0
        return READ_STATUS_FAILED; // Short ID collision
129
130
18.4k
    enum class TxSource : uint8_t { NONE, MEMPOOL, EXTRA, COLLIDED };
131
18.4k
    std::vector<TxSource> tx_source(txn_available.size(), TxSource::NONE);
132
18.4k
    size_t available_count = 0;
133
18.4k
    {
134
18.4k
    LOCK(pool->cs);
135
18.4k
    if (debug_log) {
136
36.9k
        for (auto it{leftover_prefills.begin()}; it != leftover_prefills.end();) {
137
18.4k
            if (pool->GetIter(it->first)) {
138
0
                ++redundant_prefilled_mempool_count;
139
0
                redundant_prefilled_mempool_size += it->second;
140
0
                it = leftover_prefills.erase(it);
141
18.4k
            } else {
142
18.4k
                ++it;
143
18.4k
            }
144
18.4k
        }
145
18.4k
    }
146
147
99.7k
    for (const auto& [wtxid, txit] : pool->txns_randomized) {
148
99.7k
        uint64_t shortid = cmpctblock.GetShortID(wtxid);
149
99.7k
        std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(shortid);
150
99.7k
        if (idit != shorttxids.end()) {
151
12.6k
            if (tx_source[idit->second] == TxSource::NONE) {
152
12.6k
                txn_available[idit->second] = txit->GetSharedTx();
153
12.6k
                tx_source[idit->second] = TxSource::MEMPOOL;
154
12.6k
                ++available_count;
155
12.6k
            } else if (tx_source[idit->second] != TxSource::COLLIDED) {
156
                // If we find two mempool txn that match the short id, just request it.
157
                // This should be rare enough that the extra bandwidth doesn't matter,
158
                // but eating a round-trip due to FillBlock failure would be annoying
159
0
                txn_available[idit->second].reset();
160
0
                --available_count;
161
0
                tx_source[idit->second] = TxSource::COLLIDED;
162
0
            }
163
12.6k
        }
164
        // Though ideally we'd continue scanning for the two-txn-match-shortid case,
165
        // the performance win of an early exit here is too good to pass up and worth
166
        // the extra risk.
167
99.7k
        if (available_count == shorttxids.size())
168
665
            break;
169
99.7k
    }
170
18.4k
    }
171
172
20.9k
    for (size_t i = 0; i < extra_txn.size(); i++) {
173
3.11k
        uint64_t shortid = cmpctblock.GetShortID(extra_txn[i].first);
174
3.11k
        std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(shortid);
175
3.11k
        if (idit != shorttxids.end()) {
176
304
            if (tx_source[idit->second] == TxSource::NONE) {
177
289
                txn_available[idit->second] = extra_txn[i].second;
178
289
                tx_source[idit->second] = TxSource::EXTRA;
179
289
                ++available_count;
180
289
            } else if (tx_source[idit->second] != TxSource::COLLIDED &&
181
15
                       txn_available[idit->second]->GetWitnessHash() != extra_txn[i].second->GetWitnessHash()) {
182
                // If we find two mempool/extra txn that match the short id, just
183
                // request it.
184
                // This should be rare enough that the extra bandwidth doesn't matter,
185
                // but eating a round-trip due to FillBlock failure would be annoying
186
                // Note that we don't want duplication between extra_txn and mempool to
187
                // trigger this case, so we compare witness hashes first
188
5
                txn_available[idit->second].reset();
189
5
                --available_count;
190
5
                tx_source[idit->second] = TxSource::COLLIDED;
191
5
            }
192
304
        }
193
        // Though ideally we'd continue scanning for the two-txn-match-shortid case,
194
        // the performance win of an early exit here is too good to pass up and worth
195
        // the extra risk.
196
3.11k
        if (available_count == shorttxids.size())
197
659
            break;
198
3.11k
    }
199
200
201
18.4k
    if (debug_log) {
202
18.4k
        for (const auto& [id, tx] : extra_txn) {
203
14.3k
            if (!tx) continue;
204
14.2k
            const auto it{leftover_prefills.find(id)};
205
14.2k
            if (it == leftover_prefills.end()) continue;
206
0
            ++redundant_prefilled_extrapool_count;
207
0
            redundant_prefilled_extrapool_size += it->second;
208
0
            leftover_prefills.erase(it);
209
0
        }
210
211
18.4k
        Assume(txn_available.size() == tx_source.size());
212
51.5k
        for (size_t i = 0; i < txn_available.size(); i++) {
213
33.0k
            switch (tx_source[i]) {
214
12.6k
                case TxSource::MEMPOOL:
215
12.6k
                    ++mempool_count;
216
12.6k
                    Assume(txn_available[i]);
217
12.6k
                    mempool_size += txn_available[i]->ComputeTotalSize();
218
12.6k
                    break;
219
287
                case TxSource::EXTRA:
220
287
                    ++extra_count;
221
287
                    Assume(txn_available[i]);
222
287
                    extra_size += txn_available[i]->ComputeTotalSize();
223
287
                    break;
224
20.0k
                default:
225
20.0k
                    break;
226
33.0k
            }
227
33.0k
        }
228
18.4k
    }
229
230
18.4k
    LogDebug(BCLog::CMPCTBLOCK, "Initialized PartiallyDownloadedBlock for block %s using a cmpctblock of %u bytes\n", cmpctblock.header.GetHash().ToString(), GetSerializeSize(cmpctblock));
231
232
18.4k
    return READ_STATUS_OK;
233
18.4k
}
234
235
bool PartiallyDownloadedBlock::IsTxAvailable(size_t index) const
236
33.0k
{
237
33.0k
    if (header.IsNull()) return false;
238
239
33.0k
    assert(index < txn_available.size());
240
33.0k
    return txn_available[index] != nullptr;
241
33.0k
}
242
243
ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<CTransactionRef>& vtx_missing, bool segwit_active)
244
18.4k
{
245
18.4k
    if (header.IsNull()) return READ_STATUS_INVALID;
246
247
18.4k
    block = header;
248
18.4k
    block.vtx.resize(txn_available.size());
249
250
18.4k
    size_t tx_missing_offset = 0;
251
51.2k
    for (size_t i = 0; i < txn_available.size(); i++) {
252
32.8k
        if (!txn_available[i]) {
253
1.47k
            if (tx_missing_offset >= vtx_missing.size()) {
254
2
                return READ_STATUS_INVALID;
255
2
            }
256
1.46k
            block.vtx[i] = vtx_missing[tx_missing_offset++];
257
31.3k
        } else {
258
31.3k
            block.vtx[i] = std::move(txn_available[i]);
259
31.3k
        }
260
32.8k
    }
261
262
    // Make sure we can't call FillBlock again.
263
18.4k
    header.SetNull();
264
18.4k
    txn_available.clear();
265
266
18.4k
    if (vtx_missing.size() != tx_missing_offset) {
267
0
        return READ_STATUS_INVALID;
268
0
    }
269
270
    // Check for possible mutations early now that we have a seemingly good block
271
18.4k
    IsBlockMutatedFn check_mutated{m_check_block_mutated_mock ? m_check_block_mutated_mock : IsBlockMutated};
272
18.4k
    if (check_mutated(/*block=*/block, /*check_witness_root=*/segwit_active)) {
273
5
        return READ_STATUS_FAILED; // Possible Short ID collision
274
5
    }
275
276
18.4k
    if (util::log::ShouldDebugLog(BCLog::CMPCTBLOCK)) {
277
18.4k
        const uint256 hash{block.GetHash()};
278
18.4k
        uint32_t tx_missing_size{0};
279
18.4k
        for (const auto& tx : vtx_missing) { tx_missing_size += tx->ComputeTotalSize(); }
280
18.4k
        LogDebug(BCLog::CMPCTBLOCK,
281
18.4k
            "Successfully reconstructed block %s with %u txn prefilled (%u bytes), "
282
18.4k
            "%u txn from mempool (%u bytes), "
283
18.4k
            "%u txn from extrapool (%u bytes), "
284
18.4k
            "and %u txn requested (%u bytes)",
285
18.4k
            hash.ToString(),
286
18.4k
            prefilled_count, prefilled_size,
287
18.4k
            mempool_count, mempool_size,
288
18.4k
            extra_count, extra_size,
289
18.4k
            vtx_missing.size(), tx_missing_size);
290
18.4k
        LogDebug(BCLog::CMPCTBLOCK,
291
18.4k
            "Block %s prefill redundancy statistics: "
292
18.4k
            "%u txn (%u bytes) of the prefill were redundant, "
293
18.4k
            "%u txn (%u bytes) were present in the mempool, "
294
18.4k
            "%u txn (%u bytes) were present in the extrapool.",
295
18.4k
            hash.ToString(),
296
18.4k
            redundant_prefilled_mempool_count + redundant_prefilled_extrapool_count,
297
18.4k
            redundant_prefilled_mempool_size + redundant_prefilled_extrapool_size,
298
18.4k
            redundant_prefilled_mempool_count, redundant_prefilled_mempool_size,
299
18.4k
            redundant_prefilled_extrapool_count, redundant_prefilled_extrapool_size);
300
301
18.4k
        if (util::log::ShouldTraceLog(BCLog::CMPCTBLOCK)) {
302
18.4k
            const std::string missing_txids{util::Join(vtx_missing, ", ", [](const auto& tx) { return tx->GetHash().ToString(); })};
303
18.4k
            LogTrace(BCLog::CMPCTBLOCK, "Reconstructed block %s required requesting the following transactions: %s\n", hash.ToString(), missing_txids);
304
18.4k
        }
305
18.4k
    }
306
307
18.4k
    return READ_STATUS_OK;
308
18.4k
}