upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/test_compactblock.cpp
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-05 22:46:52 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-05 22:46:52 +0000
commitceae3d076b5ad11ee84729ae43f14e9312896646 (patch)
tree7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/test_compactblock.cpp
parentbe63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff)
parent66e0b9158d050c46b1ff2509faeee3f4573cac1c (diff)
Bring main onto the line this project actually ships
main had drifted to a history with no ancestor in common with the tree we build and deploy. It sat at be63e83 (2026-08-09) with 28 commits; the shipped line, deploy/soak-2026-08-21, had 383. The two shared nothing -- git merge-base returns no result at all -- so anyone cloning this repository landed on a default branch that was neither current nor related to the running node. The 28 commits on main were audited before this merge: every one is already present in the tree by content, including the two that are hardest to see because they were rewritten afterwards -- the payout reorg-safety work (PayoutBlockStillOnChain, since evolved to re-check on every retry rather than once at maturity) and the nostr relay certificate verification (SSL_set1_host, now with a Windows root-store path). Nothing on main is lost by this merge. Recorded as a merge rather than a force-push: main only ever moves forward, its old history stays reachable as the first parent, and this is revertible. The tree is taken wholesale from the merged branch, so main's content is now exactly what was built and tested. What this brings onto main: - compactblock: BIP152-style compact block relay ported from upstream 1.2.23, primitives and unit test only, with the RAND_bytes failure path closed and the reconstruction allocation bounded. - upstream/main 17741d9: isolated testnet parameters and genesis, optional 2 MB large pages for RandomX. Mainnet genesis verified byte-identical through upstream's BuildGenesisBlock() refactor. - deploy: every image built on bookworm so binaries run on Debian 12, and the container build now honours the RandomX and libsecp256k1 pins it had been silently ignoring since before 1.2.15. Gates at this tree: 15/15 selftests, compactblock's 17 checks, consensus constants unchanged against e4f898c apart from the pre-existing MAX_BLOCK_SIZE_GEN, mainnet genesis intact, money-log check ok.
Diffstat (limited to 'src/test_compactblock.cpp')
-rw-r--r--src/test_compactblock.cpp133
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.
12map<uint256, CTransaction> mapTransactions;
13string GetAppDir() { return "."; } // util.h's debug-log path helper needs this
14
15static 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.
24static 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
32int 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}