// Standalone test of compactblock.cpp's core logic: build a compact block, // reconstruct it from a simulated mempool, including the missing-tx path. // Not wired into the P2P layer -- proves BuildCompactBlock/ // TryReconstructBlock/FillMissingAndFinish are correct in isolation. #include "headers.h" #include "compactblock.h" #include #include // main.cpp defines this; we're not linking main.cpp, so define it here. map mapTransactions; string GetAppDir() { return "."; } // util.h's debug-log path helper needs this static int g_fail = 0; #define CHECK(cond, msg) do { \ if (cond) printf(" ok %s\n", msg); \ else { printf(" FAIL %s\n", msg); g_fail++; } \ } while (0) // Build a syntactically-valid-enough CTransaction that GetHash()s uniquely // per `nSeed` -- doesn't need to be consensus-valid, this test only // exercises short-ID computation and reconstruction, not script validation. static CTransaction MakeTx(int nSeed) { CTransaction tx; tx.nVersion = 1; tx.nLockTime = nSeed; // cheapest way to make each tx's serialization (and hash) unique return tx; } int main() { // Build a block: coinbase + 20 regular transactions. CBlock block; block.nVersion = 1; block.hashPrevBlock = uint256("0x00000000000000000000000000000000000000000000000000000000000abc"); block.nTime = 1234567890; block.nBits = 0x1f0fffff; block.nNonce = 42; block.vtx.push_back(MakeTx(0)); // coinbase for (int i = 1; i <= 20; i++) block.vtx.push_back(MakeTx(i)); block.hashMerkleRoot = block.BuildMerkleTree(); printf("build_and_reconstruct_full_mempool\n"); { CCmpctBlock cmpct; CHECK(BuildCompactBlock(block, cmpct), "BuildCompactBlock succeeds"); CHECK(cmpct.vPrefilledIndex.size() == 1 && cmpct.vPrefilledIndex[0] == 0, "only the coinbase is prefilled"); CHECK(cmpct.vShortTxId.size() == 20, "one short ID per remaining tx"); // Simulate a peer with every transaction already in its mempool. mapTransactions.clear(); for (size_t i = 1; i < block.vtx.size(); i++) mapTransactions[block.vtx[i].GetHash()] = block.vtx[i]; CBlock rebuilt; vector vMissing; CHECK(TryReconstructBlock(cmpct, rebuilt, vMissing), "fully reconstructs when mempool has everything"); CHECK(vMissing.empty(), "nothing reported missing"); CHECK(rebuilt.GetHash() == block.GetHash(), "reconstructed block hash matches original exactly"); CHECK(rebuilt.vtx.size() == block.vtx.size(), "same transaction count"); bool fAllMatch = true; for (size_t i = 0; i < block.vtx.size(); i++) if (rebuilt.vtx[i].GetHash() != block.vtx[i].GetHash()) fAllMatch = false; CHECK(fAllMatch, "every transaction in the right position"); } printf("partial_mempool_then_getblocktxn_completes_it\n"); { CCmpctBlock cmpct; BuildCompactBlock(block, cmpct); // Simulate a peer missing 3 of the 20 non-coinbase transactions. mapTransactions.clear(); vector expectMissing; for (size_t i = 1; i < block.vtx.size(); i++) { if (i == 5 || i == 12 || i == 19) { expectMissing.push_back((unsigned int)i); continue; } mapTransactions[block.vtx[i].GetHash()] = block.vtx[i]; } CBlock rebuilt; vector vMissing; CHECK(!TryReconstructBlock(cmpct, rebuilt, vMissing), "reports incomplete when mempool is missing some tx"); CHECK(vMissing == expectMissing, "missing indices are exactly the ones not in mempool"); // Simulate the getblocktxn round trip: peer sends back the missing txs. vector vTxn; foreach(unsigned int idx, vMissing) vTxn.push_back(block.vtx[idx]); CHECK(FillMissingAndFinish(rebuilt, vMissing, vTxn), "FillMissingAndFinish succeeds with matching response"); CHECK(rebuilt.GetHash() == block.GetHash(), "block hash matches after filling in the missing tx"); } printf("mismatched_blocktxn_response_rejected\n"); { CCmpctBlock cmpct; BuildCompactBlock(block, cmpct); mapTransactions.clear(); mapTransactions[block.vtx[1].GetHash()] = block.vtx[1]; // only one in mempool CBlock rebuilt; vector vMissing; TryReconstructBlock(cmpct, rebuilt, vMissing); CHECK(vMissing.size() == 19, "19 missing when mempool has only 1 of 20"); vector vWrongCount; vWrongCount.push_back(block.vtx[2]); // way fewer than vMissing.size() CHECK(!FillMissingAndFinish(rebuilt, vMissing, vWrongCount), "rejects a response with the wrong tx count"); } printf("different_blocks_give_different_short_ids_for_same_tx\n"); { // Same transaction, two different (simulated) blocks -- short IDs // must differ, since they're keyed per-block (hashPrevBlock + // fresh nonce each time), not just derived from the txid alone. CTransaction tx = MakeTx(99); uint256 prevA = uint256("0x1111111111111111111111111111111111111111111111111111111111111111"); uint256 prevB = uint256("0x2222222222222222222222222222222222222222222222222222222222222222"); uint64 id1 = ComputeShortTxId(prevA, 0xAAAABBBBCCCCDDDDULL, tx.GetHash()); uint64 id2 = ComputeShortTxId(prevB, 0xAAAABBBBCCCCDDDDULL, tx.GetHash()); uint64 id3 = ComputeShortTxId(prevA, 0x1111222233334444ULL, tx.GetHash()); CHECK(id1 != id2, "different hashPrevBlock -> different short ID"); CHECK(id1 != id3, "different nonce -> different short ID"); CHECK(id1 <= 0xFFFFFFFFFFFFULL, "short ID fits in 48 bits"); } printf("\n%s (%d failures)\n", g_fail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED", g_fail); return g_fail == 0 ? 0 : 1; }