Coverage Report

Created: 2026-07-23 20:35

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 <random.h>
13
#include <streams.h>
14
#include <txmempool.h>
15
#include <util/log.h>
16
#include <validation.h>
17
18
#include <unordered_map>
19
20
CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, uint64_t nonce)
21
79.6k
    : nonce(nonce),
22
79.6k
      shorttxids(block.vtx.size() - 1),
23
79.6k
      prefilledtxn(1),
24
79.6k
      header(block)
25
79.6k
{
26
79.6k
    FillShortTxIDSelector();
27
    // TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase
28
79.6k
    prefilledtxn[0] = {0, block.vtx[0]};
29
127k
    for (size_t i = 1; i < block.vtx.size(); i++) {
30
48.0k
        const CTransaction& tx = *block.vtx[i];
31
48.0k
        shorttxids[i - 1] = GetShortID(tx.GetWitnessHash());
32
48.0k
    }
33
79.6k
}
34
35
void CBlockHeaderAndShortTxIDs::FillShortTxIDSelector() const
36
100k
{
37
100k
    DataStream stream{};
38
100k
    stream << header << nonce;
39
100k
    CSHA256 hasher;
40
100k
    hasher.Write((unsigned char*)&(*stream.begin()), stream.end() - stream.begin());
41
100k
    uint256 shorttxidhash;
42
100k
    hasher.Finalize(shorttxidhash.begin());
43
100k
    m_hasher.emplace(shorttxidhash.GetUint64(0), shorttxidhash.GetUint64(1));
44
100k
}
45
46
uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const Wtxid& wtxid) const
47
124k
{
48
124k
    static_assert(SHORTTXIDS_LENGTH == 6, "shorttxids calculation assumes 6-byte shorttxids");
49
124k
    return (*Assert(m_hasher))(wtxid.ToUint256()) & 0xffffffffffffL;
50
124k
}
51
52
/* Reconstructing a compact block is in the hot-path for block relay,
53
 * so we want to do it as quickly as possible. Because this often
54
 * involves iterating over the entire mempool, we put all the data we
55
 * need (ie the wtxid and a reference to the actual transaction data)
56
 * in a vector and iterate over the vector directly. This allows optimal
57
 * CPU caching behaviour, at a cost of only 40 bytes per transaction.
58
 */
59
ReadStatus PartiallyDownloadedBlock::InitData(const CBlockHeaderAndShortTxIDs& cmpctblock, const std::vector<std::pair<Wtxid, CTransactionRef>>& extra_txn)
60
17.9k
{
61
17.9k
    LogDebug(BCLog::CMPCTBLOCK, "Initializing PartiallyDownloadedBlock for block %s using a cmpctblock of %u bytes\n", cmpctblock.header.GetHash().ToString(), GetSerializeSize(cmpctblock));
62
17.9k
    if (cmpctblock.header.IsNull() || (cmpctblock.shorttxids.empty() && cmpctblock.prefilledtxn.empty()))
63
0
        return READ_STATUS_INVALID;
64
17.9k
    if (cmpctblock.shorttxids.size() + cmpctblock.prefilledtxn.size() > MAX_BLOCK_WEIGHT / MIN_SERIALIZABLE_TRANSACTION_WEIGHT)
65
0
        return READ_STATUS_INVALID;
66
67
17.9k
    if (!header.IsNull() || !txn_available.empty()) return READ_STATUS_INVALID;
68
69
17.9k
    header = cmpctblock.header;
70
17.9k
    txn_available.resize(cmpctblock.BlockTxCount());
71
72
17.9k
    int32_t lastprefilledindex = -1;
73
35.8k
    for (size_t i = 0; i < cmpctblock.prefilledtxn.size(); i++) {
74
17.9k
        if (cmpctblock.prefilledtxn[i].tx->IsNull())
75
0
            return READ_STATUS_INVALID;
76
77
17.9k
        lastprefilledindex += cmpctblock.prefilledtxn[i].index + 1; //index is a uint16_t, so can't overflow here
78
17.9k
        if (lastprefilledindex > std::numeric_limits<uint16_t>::max())
79
0
            return READ_STATUS_INVALID;
80
17.9k
        if ((uint32_t)lastprefilledindex > cmpctblock.shorttxids.size() + i) {
81
            // If we are inserting a tx at an index greater than our full list of shorttxids
82
            // plus the number of prefilled txn we've inserted, then we have txn for which we
83
            // have neither a prefilled txn or a shorttxid!
84
2
            return READ_STATUS_INVALID;
85
2
        }
86
17.9k
        txn_available[lastprefilledindex] = cmpctblock.prefilledtxn[i].tx;
87
17.9k
    }
88
17.9k
    prefilled_count = cmpctblock.prefilledtxn.size();
89
90
    // Calculate map of txids -> positions and check mempool to see what we have (or don't)
91
    // Because well-formed cmpctblock messages will have a (relatively) uniform distribution
92
    // of short IDs, any highly-uneven distribution of elements can be safely treated as a
93
    // READ_STATUS_FAILED.
94
17.9k
    std::unordered_map<uint64_t, uint16_t> shorttxids(cmpctblock.shorttxids.size());
95
17.9k
    uint16_t index_offset = 0;
96
30.6k
    for (size_t i = 0; i < cmpctblock.shorttxids.size(); i++) {
97
13.6k
        while (txn_available[i + index_offset])
98
924
            index_offset++;
99
12.7k
        shorttxids[cmpctblock.shorttxids[i]] = i + index_offset;
100
        // To determine the chance that the number of entries in a bucket exceeds N,
101
        // we use the fact that the number of elements in a single bucket is
102
        // binomially distributed (with n = the number of shorttxids S, and p =
103
        // 1 / the number of buckets), that in the worst case the number of buckets is
104
        // equal to S (due to std::unordered_map having a default load factor of 1.0),
105
        // and that the chance for any bucket to exceed N elements is at most
106
        // buckets * (the chance that any given bucket is above N elements).
107
        // Thus: P(max_elements_per_bucket > N) <= S * (1 - cdf(binomial(n=S,p=1/S), N)).
108
        // If we assume blocks of up to 16000, allowing 12 elements per bucket should
109
        // only fail once per ~1 million block transfers (per peer and connection).
110
12.7k
        if (shorttxids.bucket_size(shorttxids.bucket(cmpctblock.shorttxids[i])) > 12)
111
0
            return READ_STATUS_FAILED;
112
12.7k
    }
113
17.9k
    if (shorttxids.size() != cmpctblock.shorttxids.size())
114
0
        return READ_STATUS_FAILED; // Short ID collision
115
116
17.9k
    enum class TxSource : uint8_t { NONE, MEMPOOL, EXTRA, COLLIDED };
117
17.9k
    std::vector<TxSource> tx_source(txn_available.size(), TxSource::NONE);
118
17.9k
    {
119
17.9k
    LOCK(pool->cs);
120
74.3k
    for (const auto& [wtxid, txit] : pool->txns_randomized) {
121
74.3k
        uint64_t shortid = cmpctblock.GetShortID(wtxid);
122
74.3k
        std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(shortid);
123
74.3k
        if (idit != shorttxids.end()) {
124
10.9k
            if (tx_source[idit->second] == TxSource::NONE) {
125
10.9k
                txn_available[idit->second] = txit->GetSharedTx();
126
10.9k
                tx_source[idit->second] = TxSource::MEMPOOL;
127
10.9k
                mempool_count++;
128
10.9k
            } else if (tx_source[idit->second] != TxSource::COLLIDED) {
129
                // If we find two mempool txn that match the short id, just request it.
130
                // This should be rare enough that the extra bandwidth doesn't matter,
131
                // but eating a round-trip due to FillBlock failure would be annoying
132
0
                txn_available[idit->second].reset();
133
0
                mempool_count--;
134
0
                tx_source[idit->second] = TxSource::COLLIDED;
135
0
            }
136
10.9k
        }
137
        // Though ideally we'd continue scanning for the two-txn-match-shortid case,
138
        // the performance win of an early exit here is too good to pass up and worth
139
        // the extra risk.
140
74.3k
        if (mempool_count == shorttxids.size())
141
619
            break;
142
74.3k
    }
143
17.9k
    }
144
145
19.7k
    for (size_t i = 0; i < extra_txn.size(); i++) {
146
2.51k
        uint64_t shortid = cmpctblock.GetShortID(extra_txn[i].first);
147
2.51k
        std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(shortid);
148
2.51k
        if (idit != shorttxids.end()) {
149
309
            if (tx_source[idit->second] == TxSource::NONE) {
150
294
                txn_available[idit->second] = extra_txn[i].second;
151
294
                tx_source[idit->second] = TxSource::EXTRA;
152
294
                mempool_count++;
153
294
                extra_count++;
154
294
            } else if (tx_source[idit->second] != TxSource::COLLIDED &&
155
15
                       txn_available[idit->second]->GetWitnessHash() != extra_txn[i].second->GetWitnessHash()) {
156
                // If we find two mempool/extra txn that match the short id, just
157
                // request it.
158
                // This should be rare enough that the extra bandwidth doesn't matter,
159
                // but eating a round-trip due to FillBlock failure would be annoying
160
                // Note that we don't want duplication between extra_txn and mempool to
161
                // trigger this case, so we compare witness hashes first
162
5
                txn_available[idit->second].reset();
163
5
                mempool_count--;
164
5
                extra_count -= (tx_source[idit->second] == TxSource::EXTRA);
165
5
                tx_source[idit->second] = TxSource::COLLIDED;
166
5
            }
167
309
        }
168
        // Though ideally we'd continue scanning for the two-txn-match-shortid case,
169
        // the performance win of an early exit here is too good to pass up and worth
170
        // the extra risk.
171
2.51k
        if (mempool_count == shorttxids.size())
172
658
            break;
173
2.51k
    }
174
175
17.9k
    LogDebug(BCLog::CMPCTBLOCK, "Initialized PartiallyDownloadedBlock for block %s using a cmpctblock of %u bytes\n", cmpctblock.header.GetHash().ToString(), GetSerializeSize(cmpctblock));
176
177
17.9k
    return READ_STATUS_OK;
178
17.9k
}
179
180
bool PartiallyDownloadedBlock::IsTxAvailable(size_t index) const
181
30.6k
{
182
30.6k
    if (header.IsNull()) return false;
183
184
30.6k
    assert(index < txn_available.size());
185
30.6k
    return txn_available[index] != nullptr;
186
30.6k
}
187
188
ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<CTransactionRef>& vtx_missing, bool segwit_active)
189
17.8k
{
190
17.8k
    if (header.IsNull()) return READ_STATUS_INVALID;
191
192
17.8k
    block = header;
193
17.8k
    block.vtx.resize(txn_available.size());
194
195
17.8k
    size_t tx_missing_offset = 0;
196
48.3k
    for (size_t i = 0; i < txn_available.size(); i++) {
197
30.4k
        if (!txn_available[i]) {
198
1.34k
            if (tx_missing_offset >= vtx_missing.size()) {
199
2
                return READ_STATUS_INVALID;
200
2
            }
201
1.34k
            block.vtx[i] = vtx_missing[tx_missing_offset++];
202
29.1k
        } else {
203
29.1k
            block.vtx[i] = std::move(txn_available[i]);
204
29.1k
        }
205
30.4k
    }
206
207
    // Make sure we can't call FillBlock again.
208
17.8k
    header.SetNull();
209
17.8k
    txn_available.clear();
210
211
17.8k
    if (vtx_missing.size() != tx_missing_offset) {
212
0
        return READ_STATUS_INVALID;
213
0
    }
214
215
    // Check for possible mutations early now that we have a seemingly good block
216
17.8k
    IsBlockMutatedFn check_mutated{m_check_block_mutated_mock ? m_check_block_mutated_mock : IsBlockMutated};
217
17.8k
    if (check_mutated(/*block=*/block, /*check_witness_root=*/segwit_active)) {
218
5
        return READ_STATUS_FAILED; // Possible Short ID collision
219
5
    }
220
221
17.8k
    if (util::log::ShouldDebugLog(BCLog::CMPCTBLOCK)) {
222
17.8k
        const uint256 hash{block.GetHash()};
223
17.8k
        uint32_t tx_missing_size{0};
224
17.8k
        for (const auto& tx : vtx_missing) tx_missing_size += tx->ComputeTotalSize();
225
17.8k
        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);
226
17.8k
        if (vtx_missing.size() < 5) {
227
17.8k
            for (const auto& tx : vtx_missing) {
228
602
                LogDebug(BCLog::CMPCTBLOCK, "Reconstructed block %s required tx %s\n", hash.ToString(), tx->GetHash().ToString());
229
602
            }
230
17.8k
        }
231
17.8k
    }
232
233
17.8k
    return READ_STATUS_OK;
234
17.8k
}