From 60538c5a935787521a5ad8c2a7dce52b5f250f3a Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Sat, 5 Sep 2026 03:16:00 +0000 Subject: 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. --- src/compactblock.h | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/compactblock.h (limited to 'src/compactblock.h') diff --git a/src/compactblock.h b/src/compactblock.h new file mode 100644 index 0000000..745ee3b --- /dev/null +++ b/src/compactblock.h @@ -0,0 +1,113 @@ +// Copyright (c) 2026 Bitflash developers +// Distributed under the MIT/X11 software license. +// +// Compact block relay (BIP152-style). Instead of sending a peer's full set +// of transactions for a block they're about to receive -- almost all of +// which they already have in their mempool from normal tx relay -- send a +// short (48-bit, keyed-hash) ID per transaction instead. A peer that already +// has every referenced transaction reconstructs the full block locally from +// its mempool without any of the transaction bytes crossing the wire at +// all; only genuinely missing transactions (typically just the coinbase) +// need a follow-up round trip. +// +// Compatibility: this is purely additive. A peer that never sends +// "sendcmpct" is never sent a compact block -- it keeps getting full +// "block" messages exactly as before (see the getdata handler in +// main.cpp). Old, unupgraded nodes on the live network are completely +// unaffected; ProcessMessage's unknown-command handling means they simply +// never see the new message types. No coordinated network upgrade needed. +// +// Deliberately scoped down from full BIP152: no high-bandwidth (unsolicited +// push) mode, no differential-index encoding for prefilled transactions, +// only the current chain tip is ever offered in compact form (an old block +// is unlikely to still overlap much with any peer's current mempool, so the +// bandwidth win doesn't apply -- full "block" is sent for those, same as +// today). Only the coinbase is prefilled; every other transaction position +// carries a short ID. + +#ifndef BITFLASH_COMPACTBLOCK_H +#define BITFLASH_COMPACTBLOCK_H + +// Requires CBlock/CTransaction already visible -- #include "headers.h" (or +// main.h) before this file. Deliberately doesn't include main.h itself: +// main.h has no include guard (matches this codebase's existing headers, it +// relies on being pulled in exactly once via headers.h), so a second +// #include "main.h" here would break every translation unit that already +// brought it in via headers.h first. +#include + +// The compact representation of a block: header fields, a per-block nonce +// (keys the short-ID hash so IDs can't be precomputed/ground across blocks), +// the coinbase in full, and a 48-bit short ID for every other transaction +// in block order. +class CCmpctBlock +{ +public: + // header + int nVersion; + uint256 hashPrevBlock; + uint256 hashMerkleRoot; + unsigned int nTime; + unsigned int nBits; + unsigned int nNonce; + + // Keys the short-ID hash for this block specifically -- chosen fresh + // per block (not reused from nNonce, which is public PoW data an + // attacker already knows and could otherwise grind against). + uint64 shortIdNonce; + + // Sent in full. In this implementation: always exactly the coinbase, + // at index 0. + std::vector vPrefilledIndex; + std::vector vPrefilledTx; + + // One short ID per NON-prefilled transaction, in ascending block-index + // order (i.e. the Nth entry here belongs to the Nth block position that + // isn't in vPrefilledIndex). + std::vector vShortTxId; + + IMPLEMENT_SERIALIZE + ( + READWRITE(this->nVersion); + nVersion = this->nVersion; + READWRITE(hashPrevBlock); + READWRITE(hashMerkleRoot); + READWRITE(nTime); + READWRITE(nBits); + READWRITE(nNonce); + READWRITE(shortIdNonce); + READWRITE(vPrefilledIndex); + READWRITE(vPrefilledTx); + READWRITE(vShortTxId); + ) + + uint256 HeaderHash() const; +}; + +// Compute the 48-bit short ID for a transaction under a given block's +// short-ID nonce. Keyed (via the block's hashPrevBlock + shortIdNonce) so +// IDs from one block can't be precomputed before that block exists, and +// differ block to block even for the same transaction. +uint64 ComputeShortTxId(const uint256& hashPrevBlock, uint64 shortIdNonce, const uint256& txid); + +// Build the compact form of `block` (which must have vtx[0] as the +// coinbase, as every block on this chain does). +bool BuildCompactBlock(const CBlock& block, CCmpctBlock& out); + +// Attempt to reconstruct a full block from a received compact block using +// the local mempool. On full success, returns true and fills `outBlock`. +// On partial success (some transactions not found locally), returns false +// and fills `vMissingIndex` with the block-position indices that still need +// to be requested via "getblocktxn" -- outBlock is left partially built +// (coinbase and any resolved positions filled; caller must not use it until +// FillMissingAndFinish completes it). +bool TryReconstructBlock(const CCmpctBlock& cmpct, CBlock& outBlock, std::vector& vMissingIndex); + +// Complete a partially-reconstructed block (from a prior TryReconstructBlock +// that returned false) using the transactions received in a "blocktxn" +// response. vTxn must be in the same order as the indices this node +// requested via "getblocktxn" for this block. +bool FillMissingAndFinish(CBlock& outBlock, const std::vector& vMissingIndex, + const std::vector& vTxn); + +#endif -- cgit v1.2.3