upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/test_compactblock.cpp
blob: f4b558337e9a96561c0aa8f27c85e6dca3a074d9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// 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 <cstdio>
#include <cassert>

// main.cpp defines this; we're not linking main.cpp, so define it here.
map<uint256, CTransaction> 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<unsigned int> 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<unsigned int> 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<unsigned int> 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<CTransaction> 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<unsigned int> vMissing;
        TryReconstructBlock(cmpct, rebuilt, vMissing);
        CHECK(vMissing.size() == 19, "19 missing when mempool has only 1 of 20");

        vector<CTransaction> 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;
}