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-05 03:16:00 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-05 03:16:00 +0000
commit60538c5a935787521a5ad8c2a7dce52b5f250f3a (patch)
treeb78632a9d3b15b7a99567fe3c213d0bb00fd1731 /src/compactblock.cpp
parent152c859b18b222a7c11a82b5485110e902267d40 (diff)
compactblock: import BIP152-style compact block relay from upstream 1.2.23
Brings src/compactblock.{cpp,h} and test_compactblock.cpp across verbatim from the upstream-v1.2.23 tag, and gives them a build. 1.2.23 is not a newer release of the line this fork ships. It is a lightweight tag (7ab5a8c) cut 2026-07-30, on a lineage that upstream/main (e0be70e, 2026-08-20) does not contain and no remote branch carries -- 272 commits that are not ours, against 376 of ours that are not theirs, from a merge-base at 56df983. Merging that tag would have deleted 18 files from src/, selftest.{cpp,h} and the whole 14-test suite among them, so the content is ported file by file instead. Compact block relay is the piece worth having: a peer that advertises sendcmpct gets 48-bit keyed short IDs per transaction rather than the transaction bytes, and reconstructs the block from its own mempool. It is purely additive on the wire -- a peer that never sends sendcmpct keeps receiving full block messages, so no coordinated upgrade is needed. This commit is the primitives and their unit test only. Nothing calls BuildCompactBlock or TryReconstructBlock yet: the P2P message handlers (sendcmpct/cmpctblock/getblocktxn/blocktxn) live in 1.2.23's main.cpp and are deliberately left for a follow-up, so that no attacker-controlled bytes reach this code until that wiring is reviewed on its own. The files needed no API adaptation -- mapTransactions, BEGIN/END and IMPLEMENT_SERIALIZE are unchanged across the divergence, and it compiled clean first time. Upstream never wired test_compactblock.cpp into its own Makefile, so it had never been built there; it links standalone the way test_bip32 does, supplying its own mapTransactions and GetAppDir to keep main.cpp out of the link. TEST_LIBS is overridable for the same reason NODE_LIBS is: the pinned dep tree ships libsodium as a static archive only. All 17 checks in test_compactblock pass, all 14 selftests still pass, and the consensus constants are unchanged against e4f898c.
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}