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:
Diffstat (limited to 'src/compactblock.cpp')
-rw-r--r--src/compactblock.cpp155
1 files changed, 155 insertions, 0 deletions
diff --git a/src/compactblock.cpp b/src/compactblock.cpp
new file mode 100644
index 0000000..3999ab7
--- /dev/null
+++ b/src/compactblock.cpp
@@ -0,0 +1,155 @@
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 unsigned char nonceBytes[sizeof(uint64)];
52 RAND_bytes(nonceBytes, sizeof(nonceBytes));
53 memcpy(&out.shortIdNonce, nonceBytes, sizeof(uint64));
54
55 out.vPrefilledIndex.clear();
56 out.vPrefilledTx.clear();
57 out.vShortTxId.clear();
58
59 // Only the coinbase is prefilled (index 0) -- see file comment in
60 // compactblock.h for why that's enough for this chain's scale.
61 out.vPrefilledIndex.push_back(0);
62 out.vPrefilledTx.push_back(block.vtx[0]);
63
64 for (size_t i = 1; i < block.vtx.size(); i++)
65 out.vShortTxId.push_back(ComputeShortTxId(out.hashPrevBlock, out.shortIdNonce, block.vtx[i].GetHash()));
66
67 return true;
68}
69
70bool TryReconstructBlock(const CCmpctBlock& cmpct, CBlock& outBlock, std::vector<unsigned int>& vMissingIndex)
71{
72 vMissingIndex.clear();
73
74 size_t nTotal = cmpct.vPrefilledIndex.size() + cmpct.vShortTxId.size();
75 if (cmpct.vPrefilledIndex.size() != cmpct.vPrefilledTx.size() || nTotal == 0)
76 return false; // malformed
77
78 outBlock.SetNull();
79 outBlock.nVersion = cmpct.nVersion;
80 outBlock.hashPrevBlock = cmpct.hashPrevBlock;
81 outBlock.hashMerkleRoot = cmpct.hashMerkleRoot;
82 outBlock.nTime = cmpct.nTime;
83 outBlock.nBits = cmpct.nBits;
84 outBlock.nNonce = cmpct.nNonce;
85 outBlock.vtx.assign(nTotal, CTransaction());
86
87 std::vector<bool> vFilled(nTotal, false);
88 for (size_t i = 0; i < cmpct.vPrefilledIndex.size(); i++)
89 {
90 unsigned int idx = cmpct.vPrefilledIndex[i];
91 if (idx >= nTotal)
92 return false; // malformed
93 outBlock.vtx[idx] = cmpct.vPrefilledTx[i];
94 vFilled[idx] = true;
95 }
96
97 // Index every mempool tx by its short ID under this block's keying, so
98 // resolving each block position is a map lookup rather than a linear
99 // scan of the mempool per short ID.
100 std::map<uint64, const CTransaction*> mapShortIdToTx;
101 for (std::map<uint256, CTransaction>::const_iterator mi = mapTransactions.begin();
102 mi != mapTransactions.end(); ++mi)
103 {
104 uint64 sid = ComputeShortTxId(cmpct.hashPrevBlock, cmpct.shortIdNonce, mi->first);
105 // A short-ID collision between two distinct mempool transactions is
106 // possible (48 bits, birthday bound ~2^24 mempool entries) though
107 // very unlikely at this chain's scale; on collision, prefer keeping
108 // neither mapped rather than risk resolving to the wrong tx --
109 // that position just falls through to getblocktxn like a genuine
110 // miss, which is always correct, only occasionally not optimal.
111 if (mapShortIdToTx.count(sid))
112 mapShortIdToTx[sid] = NULL;
113 else
114 mapShortIdToTx[sid] = &mi->second;
115 }
116
117 size_t shortIdPos = 0;
118 for (size_t idx = 0; idx < nTotal; idx++)
119 {
120 if (vFilled[idx])
121 continue;
122 if (shortIdPos >= cmpct.vShortTxId.size())
123 return false; // malformed: more unfilled positions than short IDs
124 uint64 sid = cmpct.vShortTxId[shortIdPos++];
125
126 std::map<uint64, const CTransaction*>::const_iterator found = mapShortIdToTx.find(sid);
127 if (found != mapShortIdToTx.end() && found->second != NULL)
128 {
129 outBlock.vtx[idx] = *found->second;
130 vFilled[idx] = true;
131 }
132 else
133 {
134 vMissingIndex.push_back(idx);
135 }
136 }
137
138 return vMissingIndex.empty();
139}
140
141bool FillMissingAndFinish(CBlock& outBlock, const std::vector<unsigned int>& vMissingIndex,
142 const std::vector<CTransaction>& vTxn)
143{
144 if (vMissingIndex.size() != vTxn.size())
145 return false; // peer didn't answer what was asked
146
147 for (size_t i = 0; i < vMissingIndex.size(); i++)
148 {
149 unsigned int idx = vMissingIndex[i];
150 if (idx >= outBlock.vtx.size())
151 return false; // malformed
152 outBlock.vtx[idx] = vTxn[i];
153 }
154 return true;
155}