upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/compactblock.cpp
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-06 01:53:03 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-06 01:53:03 +0000
commitef9f06d43fc6577c15df2256abbb25b20d4227b8 (patch)
tree7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/compactblock.cpp
parentbe63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff)
parentceae3d076b5ad11ee84729ae43f14e9312896646 (diff)
Merge branch 'chore/main-catchup-2026-08-26' into 'main'HEADmain
Bring main onto the line this project actually ships (incl. the 1.2.23 compactblock port) Closes #6, #7, #8, #11, #35, #34, #29, #28, #27, and #16 See merge request !16
Diffstat (limited to 'src/compactblock.cpp')
-rw-r--r--src/compactblock.cpp175
1 files changed, 175 insertions, 0 deletions
diff --git a/src/compactblock.cpp b/src/compactblock.cpp
new file mode 100644
index 0000000..1ca6118
--- /dev/null
+++ b/src/compactblock.cpp
@@ -0,0 +1,175 @@
1// Copyright (c) 2026 Bitflash developers
2// Distributed under the MIT/X11 software license.
3//
4// See compactblock.h.
5
6#include "headers.h"
7#include "compactblock.h"
8#include <sodium.h>
9#include <map>
10
11uint256 CCmpctBlock::HeaderHash() const
12{
13 // Same technique CBlock::GetHash() uses: the header fields are laid out
14 // contiguously and in the same order here as in CBlock, so hashing the
15 // raw member range gives the identical block-identity hash.
16 return Hash(BEGIN(nVersion), END(nNonce));
17}
18
19uint64 ComputeShortTxId(const uint256& hashPrevBlock, uint64 shortIdNonce, const uint256& txid)
20{
21 // Derive a 16-byte SipHash key from (hashPrevBlock, shortIdNonce), so
22 // short IDs are specific to this block: they can't be precomputed
23 // before the block (and its freshly-chosen nonce) exist, and the same
24 // transaction gets a different short ID in every block it appears in
25 // (relevant across reorgs/re-announcements).
26 unsigned char keyMaterial[32 + sizeof(uint64)];
27 memcpy(keyMaterial, BEGIN(hashPrevBlock), 32);
28 memcpy(keyMaterial + 32, &shortIdNonce, sizeof(uint64));
29 uint256 keyHash = Hash((const char*)keyMaterial, (const char*)keyMaterial + sizeof(keyMaterial));
30
31 unsigned char out[crypto_shorthash_BYTES]; // 8 bytes
32 crypto_shorthash(out, (const unsigned char*)BEGIN(txid), 32, (const unsigned char*)BEGIN(keyHash));
33
34 uint64 id = 0;
35 memcpy(&id, out, sizeof(uint64));
36 return id & 0xFFFFFFFFFFFFULL; // low 48 bits -- matches the wire field width
37}
38
39bool BuildCompactBlock(const CBlock& block, CCmpctBlock& out)
40{
41 if (block.vtx.empty())
42 return false; // every real block has at least the coinbase
43
44 out.nVersion = block.nVersion;
45 out.hashPrevBlock = block.hashPrevBlock;
46 out.hashMerkleRoot = block.hashMerkleRoot;
47 out.nTime = block.nTime;
48 out.nBits = block.nBits;
49 out.nNonce = block.nNonce;
50
51 // Upstream ignored the RAND_bytes return value here. On failure nonceBytes
52 // is left uninitialised and an indeterminate shortIdNonce goes out on the
53 // wire -- which is precisely the value this file relies on being
54 // unpredictable to stop short IDs being precomputed or ground against.
55 // Fail closed instead, matching every other RAND_bytes call site in this
56 // tree (bip32.cpp, db.cpp): the caller falls back to full-block relay,
57 // which is always correct.
58 unsigned char nonceBytes[sizeof(uint64)];
59 if (RAND_bytes(nonceBytes, sizeof(nonceBytes)) != 1)
60 return false;
61 memcpy(&out.shortIdNonce, nonceBytes, sizeof(uint64));
62
63 out.vPrefilledIndex.clear();
64 out.vPrefilledTx.clear();
65 out.vShortTxId.clear();
66
67 // Only the coinbase is prefilled (index 0) -- see file comment in
68 // compactblock.h for why that's enough for this chain's scale.
69 out.vPrefilledIndex.push_back(0);
70 out.vPrefilledTx.push_back(block.vtx[0]);
71
72 for (size_t i = 1; i < block.vtx.size(); i++)
73 out.vShortTxId.push_back(ComputeShortTxId(out.hashPrevBlock, out.shortIdNonce, block.vtx[i].GetHash()));
74
75 return true;
76}
77
78bool TryReconstructBlock(const CCmpctBlock& cmpct, CBlock& outBlock, std::vector<unsigned int>& vMissingIndex)
79{
80 vMissingIndex.clear();
81
82 size_t nTotal = cmpct.vPrefilledIndex.size() + cmpct.vShortTxId.size();
83 if (cmpct.vPrefilledIndex.size() != cmpct.vPrefilledTx.size() || nTotal == 0)
84 return false; // malformed
85
86 // nTotal comes straight off the wire once this is wired into the P2P
87 // layer, and serialize.h only bounds a vector length by MAX_SIZE
88 // (0x02000000), so an unbounded assign() below would let one 60-odd-byte
89 // message ask this node for ~33M default-constructed CTransactions. The
90 // smallest a transaction can serialise to is 10 bytes (nVersion, empty
91 // vin, empty vout, nLockTime), so no block within MAX_BLOCK_SIZE can hold
92 // more than MAX_BLOCK_SIZE/10 of them; anything above that is not a block
93 // we could ever accept. Rejecting here just falls back to full-block
94 // relay, which is always correct.
95 if (nTotal > MAX_BLOCK_SIZE / 10)
96 return false; // implausible tx count -- refuse to size a vector from it
97
98 outBlock.SetNull();
99 outBlock.nVersion = cmpct.nVersion;
100 outBlock.hashPrevBlock = cmpct.hashPrevBlock;
101 outBlock.hashMerkleRoot = cmpct.hashMerkleRoot;
102 outBlock.nTime = cmpct.nTime;
103 outBlock.nBits = cmpct.nBits;
104 outBlock.nNonce = cmpct.nNonce;
105 outBlock.vtx.assign(nTotal, CTransaction());
106
107 std::vector<bool> vFilled(nTotal, false);
108 for (size_t i = 0; i < cmpct.vPrefilledIndex.size(); i++)
109 {
110 unsigned int idx = cmpct.vPrefilledIndex[i];
111 if (idx >= nTotal)
112 return false; // malformed
113 outBlock.vtx[idx] = cmpct.vPrefilledTx[i];
114 vFilled[idx] = true;
115 }
116
117 // Index every mempool tx by its short ID under this block's keying, so
118 // resolving each block position is a map lookup rather than a linear
119 // scan of the mempool per short ID.
120 std::map<uint64, const CTransaction*> mapShortIdToTx;
121 for (std::map<uint256, CTransaction>::const_iterator mi = mapTransactions.begin();
122 mi != mapTransactions.end(); ++mi)
123 {
124 uint64 sid = ComputeShortTxId(cmpct.hashPrevBlock, cmpct.shortIdNonce, mi->first);
125 // A short-ID collision between two distinct mempool transactions is
126 // possible (48 bits, birthday bound ~2^24 mempool entries) though
127 // very unlikely at this chain's scale; on collision, prefer keeping
128 // neither mapped rather than risk resolving to the wrong tx --
129 // that position just falls through to getblocktxn like a genuine
130 // miss, which is always correct, only occasionally not optimal.
131 if (mapShortIdToTx.count(sid))
132 mapShortIdToTx[sid] = NULL;
133 else
134 mapShortIdToTx[sid] = &mi->second;
135 }
136
137 size_t shortIdPos = 0;
138 for (size_t idx = 0; idx < nTotal; idx++)
139 {
140 if (vFilled[idx])
141 continue;
142 if (shortIdPos >= cmpct.vShortTxId.size())
143 return false; // malformed: more unfilled positions than short IDs
144 uint64 sid = cmpct.vShortTxId[shortIdPos++];
145
146 std::map<uint64, const CTransaction*>::const_iterator found = mapShortIdToTx.find(sid);
147 if (found != mapShortIdToTx.end() && found->second != NULL)
148 {
149 outBlock.vtx[idx] = *found->second;
150 vFilled[idx] = true;
151 }
152 else
153 {
154 vMissingIndex.push_back(idx);
155 }
156 }
157
158 return vMissingIndex.empty();
159}
160
161bool FillMissingAndFinish(CBlock& outBlock, const std::vector<unsigned int>& vMissingIndex,
162 const std::vector<CTransaction>& vTxn)
163{
164 if (vMissingIndex.size() != vTxn.size())
165 return false; // peer didn't answer what was asked
166
167 for (size_t i = 0; i < vMissingIndex.size(); i++)
168 {
169 unsigned int idx = vMissingIndex[i];
170 if (idx >= outBlock.vtx.size())
171 return false; // malformed
172 outBlock.vtx[idx] = vTxn[i];
173 }
174 return true;
175}