diff options
Diffstat (limited to 'src/compactblock.cpp')
| -rw-r--r-- | src/compactblock.cpp | 155 |
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 | |||
| 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 | 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 | |||
| 70 | bool 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 | |||
| 141 | bool 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 | } | ||