diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-05 03:16:00 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-05 03:16:00 +0000 |
| commit | 60538c5a935787521a5ad8c2a7dce52b5f250f3a (patch) | |
| tree | b78632a9d3b15b7a99567fe3c213d0bb00fd1731 /src/test_compactblock.cpp | |
| parent | 152c859b18b222a7c11a82b5485110e902267d40 (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/test_compactblock.cpp')
| -rw-r--r-- | src/test_compactblock.cpp | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/src/test_compactblock.cpp b/src/test_compactblock.cpp new file mode 100644 index 0000000..f4b5583 --- /dev/null +++ b/src/test_compactblock.cpp | |||
| @@ -0,0 +1,133 @@ | |||
| 1 | // Standalone test of compactblock.cpp's core logic: build a compact block, | ||
| 2 | // reconstruct it from a simulated mempool, including the missing-tx path. | ||
| 3 | // Not wired into the P2P layer -- proves BuildCompactBlock/ | ||
| 4 | // TryReconstructBlock/FillMissingAndFinish are correct in isolation. | ||
| 5 | |||
| 6 | #include "headers.h" | ||
| 7 | #include "compactblock.h" | ||
| 8 | #include <cstdio> | ||
| 9 | #include <cassert> | ||
| 10 | |||
| 11 | // main.cpp defines this; we're not linking main.cpp, so define it here. | ||
| 12 | map<uint256, CTransaction> mapTransactions; | ||
| 13 | string GetAppDir() { return "."; } // util.h's debug-log path helper needs this | ||
| 14 | |||
| 15 | static int g_fail = 0; | ||
| 16 | #define CHECK(cond, msg) do { \ | ||
| 17 | if (cond) printf(" ok %s\n", msg); \ | ||
| 18 | else { printf(" FAIL %s\n", msg); g_fail++; } \ | ||
| 19 | } while (0) | ||
| 20 | |||
| 21 | // Build a syntactically-valid-enough CTransaction that GetHash()s uniquely | ||
| 22 | // per `nSeed` -- doesn't need to be consensus-valid, this test only | ||
| 23 | // exercises short-ID computation and reconstruction, not script validation. | ||
| 24 | static CTransaction MakeTx(int nSeed) | ||
| 25 | { | ||
| 26 | CTransaction tx; | ||
| 27 | tx.nVersion = 1; | ||
| 28 | tx.nLockTime = nSeed; // cheapest way to make each tx's serialization (and hash) unique | ||
| 29 | return tx; | ||
| 30 | } | ||
| 31 | |||
| 32 | int main() | ||
| 33 | { | ||
| 34 | // Build a block: coinbase + 20 regular transactions. | ||
| 35 | CBlock block; | ||
| 36 | block.nVersion = 1; | ||
| 37 | block.hashPrevBlock = uint256("0x00000000000000000000000000000000000000000000000000000000000abc"); | ||
| 38 | block.nTime = 1234567890; | ||
| 39 | block.nBits = 0x1f0fffff; | ||
| 40 | block.nNonce = 42; | ||
| 41 | block.vtx.push_back(MakeTx(0)); // coinbase | ||
| 42 | for (int i = 1; i <= 20; i++) | ||
| 43 | block.vtx.push_back(MakeTx(i)); | ||
| 44 | block.hashMerkleRoot = block.BuildMerkleTree(); | ||
| 45 | |||
| 46 | printf("build_and_reconstruct_full_mempool\n"); | ||
| 47 | { | ||
| 48 | CCmpctBlock cmpct; | ||
| 49 | CHECK(BuildCompactBlock(block, cmpct), "BuildCompactBlock succeeds"); | ||
| 50 | CHECK(cmpct.vPrefilledIndex.size() == 1 && cmpct.vPrefilledIndex[0] == 0, "only the coinbase is prefilled"); | ||
| 51 | CHECK(cmpct.vShortTxId.size() == 20, "one short ID per remaining tx"); | ||
| 52 | |||
| 53 | // Simulate a peer with every transaction already in its mempool. | ||
| 54 | mapTransactions.clear(); | ||
| 55 | for (size_t i = 1; i < block.vtx.size(); i++) | ||
| 56 | mapTransactions[block.vtx[i].GetHash()] = block.vtx[i]; | ||
| 57 | |||
| 58 | CBlock rebuilt; | ||
| 59 | vector<unsigned int> vMissing; | ||
| 60 | CHECK(TryReconstructBlock(cmpct, rebuilt, vMissing), "fully reconstructs when mempool has everything"); | ||
| 61 | CHECK(vMissing.empty(), "nothing reported missing"); | ||
| 62 | CHECK(rebuilt.GetHash() == block.GetHash(), "reconstructed block hash matches original exactly"); | ||
| 63 | CHECK(rebuilt.vtx.size() == block.vtx.size(), "same transaction count"); | ||
| 64 | bool fAllMatch = true; | ||
| 65 | for (size_t i = 0; i < block.vtx.size(); i++) | ||
| 66 | if (rebuilt.vtx[i].GetHash() != block.vtx[i].GetHash()) fAllMatch = false; | ||
| 67 | CHECK(fAllMatch, "every transaction in the right position"); | ||
| 68 | } | ||
| 69 | |||
| 70 | printf("partial_mempool_then_getblocktxn_completes_it\n"); | ||
| 71 | { | ||
| 72 | CCmpctBlock cmpct; | ||
| 73 | BuildCompactBlock(block, cmpct); | ||
| 74 | |||
| 75 | // Simulate a peer missing 3 of the 20 non-coinbase transactions. | ||
| 76 | mapTransactions.clear(); | ||
| 77 | vector<unsigned int> expectMissing; | ||
| 78 | for (size_t i = 1; i < block.vtx.size(); i++) | ||
| 79 | { | ||
| 80 | if (i == 5 || i == 12 || i == 19) { expectMissing.push_back((unsigned int)i); continue; } | ||
| 81 | mapTransactions[block.vtx[i].GetHash()] = block.vtx[i]; | ||
| 82 | } | ||
| 83 | |||
| 84 | CBlock rebuilt; | ||
| 85 | vector<unsigned int> vMissing; | ||
| 86 | CHECK(!TryReconstructBlock(cmpct, rebuilt, vMissing), "reports incomplete when mempool is missing some tx"); | ||
| 87 | CHECK(vMissing == expectMissing, "missing indices are exactly the ones not in mempool"); | ||
| 88 | |||
| 89 | // Simulate the getblocktxn round trip: peer sends back the missing txs. | ||
| 90 | vector<CTransaction> vTxn; | ||
| 91 | foreach(unsigned int idx, vMissing) | ||
| 92 | vTxn.push_back(block.vtx[idx]); | ||
| 93 | |||
| 94 | CHECK(FillMissingAndFinish(rebuilt, vMissing, vTxn), "FillMissingAndFinish succeeds with matching response"); | ||
| 95 | CHECK(rebuilt.GetHash() == block.GetHash(), "block hash matches after filling in the missing tx"); | ||
| 96 | } | ||
| 97 | |||
| 98 | printf("mismatched_blocktxn_response_rejected\n"); | ||
| 99 | { | ||
| 100 | CCmpctBlock cmpct; | ||
| 101 | BuildCompactBlock(block, cmpct); | ||
| 102 | mapTransactions.clear(); | ||
| 103 | mapTransactions[block.vtx[1].GetHash()] = block.vtx[1]; // only one in mempool | ||
| 104 | |||
| 105 | CBlock rebuilt; | ||
| 106 | vector<unsigned int> vMissing; | ||
| 107 | TryReconstructBlock(cmpct, rebuilt, vMissing); | ||
| 108 | CHECK(vMissing.size() == 19, "19 missing when mempool has only 1 of 20"); | ||
| 109 | |||
| 110 | vector<CTransaction> vWrongCount; | ||
| 111 | vWrongCount.push_back(block.vtx[2]); // way fewer than vMissing.size() | ||
| 112 | CHECK(!FillMissingAndFinish(rebuilt, vMissing, vWrongCount), "rejects a response with the wrong tx count"); | ||
| 113 | } | ||
| 114 | |||
| 115 | printf("different_blocks_give_different_short_ids_for_same_tx\n"); | ||
| 116 | { | ||
| 117 | // Same transaction, two different (simulated) blocks -- short IDs | ||
| 118 | // must differ, since they're keyed per-block (hashPrevBlock + | ||
| 119 | // fresh nonce each time), not just derived from the txid alone. | ||
| 120 | CTransaction tx = MakeTx(99); | ||
| 121 | uint256 prevA = uint256("0x1111111111111111111111111111111111111111111111111111111111111111"); | ||
| 122 | uint256 prevB = uint256("0x2222222222222222222222222222222222222222222222222222222222222222"); | ||
| 123 | uint64 id1 = ComputeShortTxId(prevA, 0xAAAABBBBCCCCDDDDULL, tx.GetHash()); | ||
| 124 | uint64 id2 = ComputeShortTxId(prevB, 0xAAAABBBBCCCCDDDDULL, tx.GetHash()); | ||
| 125 | uint64 id3 = ComputeShortTxId(prevA, 0x1111222233334444ULL, tx.GetHash()); | ||
| 126 | CHECK(id1 != id2, "different hashPrevBlock -> different short ID"); | ||
| 127 | CHECK(id1 != id3, "different nonce -> different short ID"); | ||
| 128 | CHECK(id1 <= 0xFFFFFFFFFFFFULL, "short ID fits in 48 bits"); | ||
| 129 | } | ||
| 130 | |||
| 131 | printf("\n%s (%d failures)\n", g_fail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED", g_fail); | ||
| 132 | return g_fail == 0 ? 0 : 1; | ||
| 133 | } | ||