diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-06 01:53:03 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-06 01:53:03 +0000 |
| commit | ef9f06d43fc6577c15df2256abbb25b20d4227b8 (patch) | |
| tree | 7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/compactblock.cpp | |
| parent | be63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff) | |
| parent | ceae3d076b5ad11ee84729ae43f14e9312896646 (diff) | |
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.cpp | 175 |
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 | |||
| 11 | uint256 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 | |||
| 19 | uint64 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 | |||
| 39 | bool 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 | |||
| 78 | bool 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 | |||
| 161 | bool 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 | } | ||