|
| 1 | +// --------------------------------------------------------------------------- |
| 2 | +// DGB+DOGE merged-mining (phase DB) — KAT for the DGB-side AuxPoW coinbase |
| 3 | +// merged-mining commitment builder. Fenced / test-only. |
| 4 | +// |
| 5 | +// Pins the EXACT byte layout emitted by dgb::coin::build_aux_mm_commitment for |
| 6 | +// fixed (aux_merkle_root, merkle_size, merkle_nonce) inputs: |
| 7 | +// |
| 8 | +// fa be 6d 6d || aux_root(32, big-endian) || size(4 LE) || nonce(4 LE) |
| 9 | +// |
| 10 | +// NON-CIRCULAR. The expected commitment bytes are hand-constructed in each |
| 11 | +// case from the raw inputs (magic literal + reversed root bytes + manually |
| 12 | +// little-endian-split size/nonce), NOT by re-invoking the builder. The test is |
| 13 | +// therefore a true layout anchor: a re-shaped builder cannot self-confirm. |
| 14 | +// |
| 15 | +// SSOT DELEGATION GUARD. Each case ALSO asserts the DGB entry point is |
| 16 | +// byte-identical to the canonical cross-coin producer |
| 17 | +// c2pool::merged::build_auxpow_commitment. Per the integrator adjudication |
| 18 | +// (core builder = MM SSOT), DGB owns no build body of its own; this pins |
| 19 | +// DGBs chain slot + goldens to the one canonical builder (delegation), not a |
| 20 | +// fenced copy. Mirrors the NMC SSOT drift-guard auxpow_merkle_test.cpp. |
| 21 | +// |
| 22 | +// Mirrors the established DGB aux-test style (unhex/tohex helpers) of the |
| 23 | +// sibling src/impl/dgb/test/aux_doge_db_commitment_bind_test.cpp. MUST appear |
| 24 | +// in BOTH test/CMakeLists.txt AND the build.yml --target allowlist or it |
| 25 | +// becomes a NOT_BUILT sentinel that reds master (cf. DGB #137 / #143). |
| 26 | +// --------------------------------------------------------------------------- |
| 27 | + |
| 28 | +#include <gtest/gtest.h> |
| 29 | + |
| 30 | +#include <impl/dgb/coin/aux_doge_mm_commitment.hpp> // the fenced builder under test |
| 31 | +#include <c2pool/merged/merged_mining.hpp> // SSOT drift-guard: build_auxpow_commitment |
| 32 | +#include <core/uint256.hpp> |
| 33 | + |
| 34 | +#include <cstdint> |
| 35 | +#include <string> |
| 36 | +#include <vector> |
| 37 | + |
| 38 | +namespace { |
| 39 | + |
| 40 | +std::vector<unsigned char> unhex(const std::string& h) { |
| 41 | + std::vector<unsigned char> v; v.reserve(h.size() / 2); |
| 42 | + auto nyb = [](char c) -> int { return (c <= '9') ? c - '0' : (c | 0x20) - 'a' + 10; }; |
| 43 | + for (size_t i = 0; i + 1 < h.size(); i += 2) |
| 44 | + v.push_back(static_cast<unsigned char>((nyb(h[i]) << 4) | nyb(h[i + 1]))); |
| 45 | + return v; |
| 46 | +} |
| 47 | +std::string tohex(const std::vector<unsigned char>& v) { |
| 48 | + static const char* H = "0123456789abcdef"; |
| 49 | + std::string s; s.reserve(v.size() * 2); |
| 50 | + for (unsigned char b : v) { s.push_back(H[b >> 4]); s.push_back(H[b & 0xf]); } |
| 51 | + return s; |
| 52 | +} |
| 53 | + |
| 54 | +// Build a uint256 whose internal little-endian byte image == root_le[0..31]. |
| 55 | +// (base_uint(vector) reads the vector little-endian, so on a little-endian host |
| 56 | +// reinterpret_cast<uint8_t*>(pn)[i] == root_le[i].) Requires exactly 32 bytes. |
| 57 | +uint256 root_from_le_bytes(const std::vector<unsigned char>& root_le) { |
| 58 | + EXPECT_EQ(root_le.size(), 32u); |
| 59 | + return uint256(root_le); |
| 60 | +} |
| 61 | + |
| 62 | +// NON-CIRCULAR expected blob: magic || reverse(root_le) || size_LE || nonce_LE. |
| 63 | +// Hand-assembled from the raw inputs — does NOT call the builder. |
| 64 | +std::vector<unsigned char> expected_commitment( |
| 65 | + const std::vector<unsigned char>& root_le, |
| 66 | + uint32_t size, |
| 67 | + uint32_t nonce) |
| 68 | +{ |
| 69 | + std::vector<unsigned char> e; |
| 70 | + // magic |
| 71 | + e.push_back(0xfa); e.push_back(0xbe); e.push_back(0x6d); e.push_back(0x6d); |
| 72 | + // root, big-endian (reverse of the internal little-endian image) |
| 73 | + for (int i = 31; i >= 0; --i) e.push_back(root_le[i]); |
| 74 | + // size, little-endian |
| 75 | + e.push_back(static_cast<unsigned char>(size & 0xFF)); |
| 76 | + e.push_back(static_cast<unsigned char>((size >> 8) & 0xFF)); |
| 77 | + e.push_back(static_cast<unsigned char>((size >> 16) & 0xFF)); |
| 78 | + e.push_back(static_cast<unsigned char>((size >> 24) & 0xFF)); |
| 79 | + // nonce, little-endian |
| 80 | + e.push_back(static_cast<unsigned char>(nonce & 0xFF)); |
| 81 | + e.push_back(static_cast<unsigned char>((nonce >> 8) & 0xFF)); |
| 82 | + e.push_back(static_cast<unsigned char>((nonce >> 16) & 0xFF)); |
| 83 | + e.push_back(static_cast<unsigned char>((nonce >> 24) & 0xFF)); |
| 84 | + return e; |
| 85 | +} |
| 86 | + |
| 87 | +} // namespace |
| 88 | + |
| 89 | +// 0) Structural: blob is exactly 44 bytes and starts with the fabe6d6d magic. |
| 90 | +TEST(DGB_AuxDogeMMCommitment, SizeAndMagic) { |
| 91 | + auto root_le = unhex(std::string(64, '0')); |
| 92 | + auto blob = dgb::coin::build_aux_mm_commitment(root_from_le_bytes(root_le), 0, 0); |
| 93 | + ASSERT_EQ(blob.size(), 44u); |
| 94 | + EXPECT_EQ(blob.size(), dgb::coin::AUX_MM_COMMITMENT_SIZE); |
| 95 | + EXPECT_EQ(tohex({blob.begin(), blob.begin() + 4}), "fabe6d6d"); |
| 96 | + // SSOT delegation: structural blob is the core builders output verbatim. |
| 97 | + EXPECT_EQ(tohex(blob), |
| 98 | + tohex(c2pool::merged::build_auxpow_commitment( |
| 99 | + root_from_le_bytes(root_le), 0, 0))); |
| 100 | +} |
| 101 | + |
| 102 | +// 1) All-zero root, size=1, nonce=0 — canonical degenerate (single-aux) case. |
| 103 | +TEST(DGB_AuxDogeMMCommitment, KAT_ZeroRoot_Size1_Nonce0) { |
| 104 | + auto root_le = unhex(std::string(64, '0')); |
| 105 | + const uint32_t size = 1, nonce = 0; |
| 106 | + auto got = dgb::coin::build_aux_mm_commitment(root_from_le_bytes(root_le), size, nonce); |
| 107 | + auto want = expected_commitment(root_le, size, nonce); |
| 108 | + // SSOT delegation: DGB entry point forwards verbatim to the core builder. |
| 109 | + auto ssot = c2pool::merged::build_auxpow_commitment( |
| 110 | + root_from_le_bytes(root_le), size, nonce); |
| 111 | + EXPECT_EQ(tohex(got), tohex(ssot)); |
| 112 | + EXPECT_EQ(tohex(got), tohex(want)); |
| 113 | + // Fully pinned literal: magic || 32 zero bytes || 01000000 || 00000000 |
| 114 | + EXPECT_EQ(tohex(got), |
| 115 | + "fabe6d6d" |
| 116 | + "0000000000000000000000000000000000000000000000000000000000000000" |
| 117 | + "01000000" |
| 118 | + "00000000"); |
| 119 | +} |
| 120 | + |
| 121 | +// 2) Distinct-byte root, multi-byte LE size + nonce — exercises root reversal |
| 122 | +// and full little-endian width of both 32-bit fields. |
| 123 | +TEST(DGB_AuxDogeMMCommitment, KAT_PatternedRoot_SizeNonceLE) { |
| 124 | + // Internal little-endian image: byte i == i (00 01 02 ... 1f). |
| 125 | + std::vector<unsigned char> root_le; |
| 126 | + for (int i = 0; i < 32; ++i) root_le.push_back(static_cast<unsigned char>(i)); |
| 127 | + const uint32_t size = 0x04030201u, nonce = 0xdeadbeefu; |
| 128 | + auto got = dgb::coin::build_aux_mm_commitment(root_from_le_bytes(root_le), size, nonce); |
| 129 | + auto want = expected_commitment(root_le, size, nonce); |
| 130 | + // SSOT delegation: DGB entry point forwards verbatim to the core builder. |
| 131 | + auto ssot = c2pool::merged::build_auxpow_commitment( |
| 132 | + root_from_le_bytes(root_le), size, nonce); |
| 133 | + EXPECT_EQ(tohex(got), tohex(ssot)); |
| 134 | + EXPECT_EQ(tohex(got), tohex(want)); |
| 135 | + // Fully pinned literal: root big-endian is 1f..00; size LE=01020304; nonce LE=efbeadde. |
| 136 | + EXPECT_EQ(tohex(got), |
| 137 | + "fabe6d6d" |
| 138 | + "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100" |
| 139 | + "01020304" |
| 140 | + "efbeadde"); |
| 141 | +} |
| 142 | + |
| 143 | +// 3) Real-shaped DOGE-style root, max size, mid nonce. |
| 144 | +TEST(DGB_AuxDogeMMCommitment, KAT_RealishRoot_MaxSize) { |
| 145 | + // Internal little-endian image of a non-symmetric 32-byte root. |
| 146 | + auto root_le = unhex( |
| 147 | + "112233445566778899aabbccddeeff00" |
| 148 | + "fedcba98765432100123456789abcdef"); |
| 149 | + const uint32_t size = 0xffffffffu, nonce = 0x00c0ffeeu; |
| 150 | + auto got = dgb::coin::build_aux_mm_commitment(root_from_le_bytes(root_le), size, nonce); |
| 151 | + auto want = expected_commitment(root_le, size, nonce); |
| 152 | + // SSOT delegation: DGB entry point forwards verbatim to the core builder. |
| 153 | + auto ssot = c2pool::merged::build_auxpow_commitment( |
| 154 | + root_from_le_bytes(root_le), size, nonce); |
| 155 | + EXPECT_EQ(tohex(got), tohex(ssot)); |
| 156 | + EXPECT_EQ(tohex(got), tohex(want)); |
| 157 | + // Fully pinned literal. |
| 158 | + EXPECT_EQ(tohex(got), |
| 159 | + "fabe6d6d" |
| 160 | + "efcdab89674523011032547698badcfe00ffeeddccbbaa998877665544332211" |
| 161 | + "ffffffff" |
| 162 | + "eeffc000"); |
| 163 | +} |
0 commit comments