upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/compactblock.cpp
blob: 1ca6118249321b3f3e719506b5a2d2ffeeebf6c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright (c) 2026 Bitflash developers
// Distributed under the MIT/X11 software license.
//
// See compactblock.h.

#include "headers.h"
#include "compactblock.h"
#include <sodium.h>
#include <map>

uint256 CCmpctBlock::HeaderHash() const
{
    // Same technique CBlock::GetHash() uses: the header fields are laid out
    // contiguously and in the same order here as in CBlock, so hashing the
    // raw member range gives the identical block-identity hash.
    return Hash(BEGIN(nVersion), END(nNonce));
}

uint64 ComputeShortTxId(const uint256& hashPrevBlock, uint64 shortIdNonce, const uint256& txid)
{
    // Derive a 16-byte SipHash key from (hashPrevBlock, shortIdNonce), so
    // short IDs are specific to this block: they can't be precomputed
    // before the block (and its freshly-chosen nonce) exist, and the same
    // transaction gets a different short ID in every block it appears in
    // (relevant across reorgs/re-announcements).
    unsigned char keyMaterial[32 + sizeof(uint64)];
    memcpy(keyMaterial, BEGIN(hashPrevBlock), 32);
    memcpy(keyMaterial + 32, &shortIdNonce, sizeof(uint64));
    uint256 keyHash = Hash((const char*)keyMaterial, (const char*)keyMaterial + sizeof(keyMaterial));

    unsigned char out[crypto_shorthash_BYTES]; // 8 bytes
    crypto_shorthash(out, (const unsigned char*)BEGIN(txid), 32, (const unsigned char*)BEGIN(keyHash));

    uint64 id = 0;
    memcpy(&id, out, sizeof(uint64));
    return id & 0xFFFFFFFFFFFFULL; // low 48 bits -- matches the wire field width
}

bool BuildCompactBlock(const CBlock& block, CCmpctBlock& out)
{
    if (block.vtx.empty())
        return false; // every real block has at least the coinbase

    out.nVersion      = block.nVersion;
    out.hashPrevBlock  = block.hashPrevBlock;
    out.hashMerkleRoot = block.hashMerkleRoot;
    out.nTime          = block.nTime;
    out.nBits          = block.nBits;
    out.nNonce         = block.nNonce;

    // Upstream ignored the RAND_bytes return value here. On failure nonceBytes
    // is left uninitialised and an indeterminate shortIdNonce goes out on the
    // wire -- which is precisely the value this file relies on being
    // unpredictable to stop short IDs being precomputed or ground against.
    // Fail closed instead, matching every other RAND_bytes call site in this
    // tree (bip32.cpp, db.cpp): the caller falls back to full-block relay,
    // which is always correct.
    unsigned char nonceBytes[sizeof(uint64)];
    if (RAND_bytes(nonceBytes, sizeof(nonceBytes)) != 1)
        return false;
    memcpy(&out.shortIdNonce, nonceBytes, sizeof(uint64));

    out.vPrefilledIndex.clear();
    out.vPrefilledTx.clear();
    out.vShortTxId.clear();

    // Only the coinbase is prefilled (index 0) -- see file comment in
    // compactblock.h for why that's enough for this chain's scale.
    out.vPrefilledIndex.push_back(0);
    out.vPrefilledTx.push_back(block.vtx[0]);

    for (size_t i = 1; i < block.vtx.size(); i++)
        out.vShortTxId.push_back(ComputeShortTxId(out.hashPrevBlock, out.shortIdNonce, block.vtx[i].GetHash()));

    return true;
}

bool TryReconstructBlock(const CCmpctBlock& cmpct, CBlock& outBlock, std::vector<unsigned int>& vMissingIndex)
{
    vMissingIndex.clear();

    size_t nTotal = cmpct.vPrefilledIndex.size() + cmpct.vShortTxId.size();
    if (cmpct.vPrefilledIndex.size() != cmpct.vPrefilledTx.size() || nTotal == 0)
        return false; // malformed

    // nTotal comes straight off the wire once this is wired into the P2P
    // layer, and serialize.h only bounds a vector length by MAX_SIZE
    // (0x02000000), so an unbounded assign() below would let one 60-odd-byte
    // message ask this node for ~33M default-constructed CTransactions. The
    // smallest a transaction can serialise to is 10 bytes (nVersion, empty
    // vin, empty vout, nLockTime), so no block within MAX_BLOCK_SIZE can hold
    // more than MAX_BLOCK_SIZE/10 of them; anything above that is not a block
    // we could ever accept. Rejecting here just falls back to full-block
    // relay, which is always correct.
    if (nTotal > MAX_BLOCK_SIZE / 10)
        return false; // implausible tx count -- refuse to size a vector from it

    outBlock.SetNull();
    outBlock.nVersion      = cmpct.nVersion;
    outBlock.hashPrevBlock  = cmpct.hashPrevBlock;
    outBlock.hashMerkleRoot = cmpct.hashMerkleRoot;
    outBlock.nTime          = cmpct.nTime;
    outBlock.nBits          = cmpct.nBits;
    outBlock.nNonce         = cmpct.nNonce;
    outBlock.vtx.assign(nTotal, CTransaction());

    std::vector<bool> vFilled(nTotal, false);
    for (size_t i = 0; i < cmpct.vPrefilledIndex.size(); i++)
    {
        unsigned int idx = cmpct.vPrefilledIndex[i];
        if (idx >= nTotal)
            return false; // malformed
        outBlock.vtx[idx] = cmpct.vPrefilledTx[i];
        vFilled[idx] = true;
    }

    // Index every mempool tx by its short ID under this block's keying, so
    // resolving each block position is a map lookup rather than a linear
    // scan of the mempool per short ID.
    std::map<uint64, const CTransaction*> mapShortIdToTx;
    for (std::map<uint256, CTransaction>::const_iterator mi = mapTransactions.begin();
         mi != mapTransactions.end(); ++mi)
    {
        uint64 sid = ComputeShortTxId(cmpct.hashPrevBlock, cmpct.shortIdNonce, mi->first);
        // A short-ID collision between two distinct mempool transactions is
        // possible (48 bits, birthday bound ~2^24 mempool entries) though
        // very unlikely at this chain's scale; on collision, prefer keeping
        // neither mapped rather than risk resolving to the wrong tx --
        // that position just falls through to getblocktxn like a genuine
        // miss, which is always correct, only occasionally not optimal.
        if (mapShortIdToTx.count(sid))
            mapShortIdToTx[sid] = NULL;
        else
            mapShortIdToTx[sid] = &mi->second;
    }

    size_t shortIdPos = 0;
    for (size_t idx = 0; idx < nTotal; idx++)
    {
        if (vFilled[idx])
            continue;
        if (shortIdPos >= cmpct.vShortTxId.size())
            return false; // malformed: more unfilled positions than short IDs
        uint64 sid = cmpct.vShortTxId[shortIdPos++];

        std::map<uint64, const CTransaction*>::const_iterator found = mapShortIdToTx.find(sid);
        if (found != mapShortIdToTx.end() && found->second != NULL)
        {
            outBlock.vtx[idx] = *found->second;
            vFilled[idx] = true;
        }
        else
        {
            vMissingIndex.push_back(idx);
        }
    }

    return vMissingIndex.empty();
}

bool FillMissingAndFinish(CBlock& outBlock, const std::vector<unsigned int>& vMissingIndex,
                           const std::vector<CTransaction>& vTxn)
{
    if (vMissingIndex.size() != vTxn.size())
        return false; // peer didn't answer what was asked

    for (size_t i = 0; i < vMissingIndex.size(); i++)
    {
        unsigned int idx = vMissingIndex[i];
        if (idx >= outBlock.vtx.size())
            return false; // malformed
        outBlock.vtx[idx] = vTxn[i];
    }
    return true;
}