|
| 1 | +// --------------------------------------------------------------------------- |
| 2 | +// DGB+DOGE merged-mining (D0) — aux-pow parent-coinbase serialization PARITY |
| 3 | +// BASELINE KAT. Fenced / test-only — consumes the shared aux module, modifies |
| 4 | +// nothing. |
| 5 | +// |
| 6 | +// PURPOSE. The v36-standardize Scrypt value-type extraction will lift the |
| 7 | +// parent-coinbase member out of ltc::coin into a neutral Scrypt-family base so |
| 8 | +// that a non-LTC parent (DGB-as-parent under -DAUX_DOGE=ON) can occupy it |
| 9 | +// without the dgb -> ltc_coin build edge. Today that member is hard-typed: |
| 10 | +// |
| 11 | +// src/impl/doge/coin/auxpow.hpp:77 ltc::coin::MutableTransaction m_tx; |
| 12 | +// src/impl/doge/coin/auxpow.hpp:84 ::Serialize(s, TX_NO_WITNESS(m_tx)); |
| 13 | +// |
| 14 | +// This KAT freezes the CURRENT, PRE-TEMPLATING wire image of the whole |
| 15 | +// doge::coin::CMerkleTx (the struct that carries m_tx) so the extraction PR can |
| 16 | +// prove its type swap is byte-neutral: re-run after templating must reproduce |
| 17 | +// the golden BYTE-FOR-BYTE. A single moved byte fails here. |
| 18 | +// |
| 19 | +// ANCHOR. Golden captured against the shared aux module at master HEAD |
| 20 | +// git rev-parse origin/master == f732061bc |
| 21 | +// recorded below as AUX_MODULE_MASTER_SHA. A later rebase that silently |
| 22 | +// re-shapes the serializer cannot move the golden without tripping this test; |
| 23 | +// if the anchor SHA and the live module diverge in field order, that is a HARD |
| 24 | +// STOP back to integrator, not a golden update. |
| 25 | +// |
| 26 | +// CROSS-TYPE INVARIANT. The extraction's whole safety claim is that the parent |
| 27 | +// coinbase serializes identically whichever Scrypt-family type holds it. We |
| 28 | +// assert that directly: the same canonical coinbase fields, serialized once |
| 29 | +// through ltc::coin::MutableTransaction (the real member type) and once through |
| 30 | +// dgb::coin::MutableTransaction (a stand-in for the future neutral / DGB-parent |
| 31 | +// type), produce identical witness-stripped bytes. Both extend |
| 32 | +// bitcoin_family::coin::base_transaction, so a divergence would mean the shared |
| 33 | +// base is NOT actually shared — exactly the thing extraction must not break. |
| 34 | +// |
| 35 | +// Per-coin isolation note: this test legitimately links the ltc OBJECT lib |
| 36 | +// because it exercises the -DAUX_DOGE dual-parent consumption path, whose |
| 37 | +// shared module (ltc-doge's domain) already includes ltc/coin/transaction.hpp |
| 38 | +// by design. We CONSUME the module; we do not modify it. MUST appear in BOTH |
| 39 | +// test/CMakeLists.txt AND the build.yml --target allowlist or it becomes a |
| 40 | +// NOT_BUILT sentinel that reds master (cf. DGB #137). |
| 41 | +// --------------------------------------------------------------------------- |
| 42 | + |
| 43 | +#include <gtest/gtest.h> |
| 44 | + |
| 45 | +#include <impl/doge/coin/auxpow.hpp> // the REAL shared module (SSOT) |
| 46 | +#include <impl/ltc/coin/transaction.hpp> // ltc::coin::MutableTransaction (member type) |
| 47 | +#include <impl/dgb/coin/transaction.hpp> // dgb::coin::MutableTransaction + dgb::coin::TX_NO_WITNESS (neutral stand-in) |
| 48 | + |
| 49 | +#include <core/pack.hpp> |
| 50 | +#include <core/uint256.hpp> |
| 51 | +#include <core/opscript.hpp> |
| 52 | + |
| 53 | +#include <cstdint> |
| 54 | +#include <string> |
| 55 | +#include <vector> |
| 56 | + |
| 57 | +namespace { |
| 58 | + |
| 59 | +// Anchor — see file header. Update ONLY together with a re-verified golden. |
| 60 | +constexpr const char* AUX_MODULE_MASTER_SHA = "f732061bc"; |
| 61 | + |
| 62 | +std::vector<unsigned char> unhex(const std::string& h) { |
| 63 | + std::vector<unsigned char> v; v.reserve(h.size() / 2); |
| 64 | + auto nyb = [](char c) -> int { return (c <= '9') ? c - '0' : (c | 0x20) - 'a' + 10; }; |
| 65 | + for (size_t i = 0; i + 1 < h.size(); i += 2) |
| 66 | + v.push_back(static_cast<unsigned char>((nyb(h[i]) << 4) | nyb(h[i + 1]))); |
| 67 | + return v; |
| 68 | +} |
| 69 | +std::string tohex(const std::vector<unsigned char>& v) { |
| 70 | + static const char* H = "0123456789abcdef"; |
| 71 | + std::string s; s.reserve(v.size() * 2); |
| 72 | + for (unsigned char b : v) { s.push_back(H[b >> 4]); s.push_back(H[b & 0xf]); } |
| 73 | + return s; |
| 74 | +} |
| 75 | +template <typename T> |
| 76 | +std::string pack_hex(const T& value) { |
| 77 | + auto packed = pack(value); |
| 78 | + auto sp = packed.get_span(); |
| 79 | + std::vector<unsigned char> v( |
| 80 | + reinterpret_cast<const unsigned char*>(sp.data()), |
| 81 | + reinterpret_cast<const unsigned char*>(sp.data()) + sp.size()); |
| 82 | + return tohex(v); |
| 83 | +} |
| 84 | + |
| 85 | +OPScript script_of(const std::vector<unsigned char>& bytes) { |
| 86 | + return OPScript(bytes.data(), bytes.data() + bytes.size()); |
| 87 | +} |
| 88 | + |
| 89 | +// --- canonical, deterministic parent coinbase fields ----------------------- |
| 90 | +// A minimal but representative coinbase: null prevout, a fixed coinbase |
| 91 | +// scriptSig, one P2PKH-shaped payout, version 1, locktime 0. Shared verbatim |
| 92 | +// between the ltc- and dgb-typed builders below so the only variable is the |
| 93 | +// transaction TYPE, not the data. |
| 94 | +const std::vector<unsigned char> CB_SCRIPT = unhex("03a1b2c3041122334455667788"); |
| 95 | +const std::vector<unsigned char> PK_SCRIPT = |
| 96 | + unhex(std::string("76a914") + std::string(40, '3') + "88ac"); |
| 97 | +constexpr int32_t TX_VERSION = 1; |
| 98 | +constexpr uint32_t TX_LOCKTIME = 0; |
| 99 | +constexpr uint32_t CB_SEQUENCE = 0xffffffffu; |
| 100 | +constexpr int64_t CB_VALUE = 5000000000ll; // 50.00000000 |
| 101 | + |
| 102 | +template <typename MTx> |
| 103 | +MTx build_parent_coinbase() { |
| 104 | + MTx tx; |
| 105 | + tx.version = TX_VERSION; |
| 106 | + tx.locktime = TX_LOCKTIME; |
| 107 | + |
| 108 | + typename decltype(tx.vin)::value_type in; |
| 109 | + in.prevout.hash = uint256(); // null |
| 110 | + in.prevout.index = 0xffffffffu; // coinbase marker |
| 111 | + in.scriptSig = script_of(CB_SCRIPT); |
| 112 | + in.sequence = CB_SEQUENCE; |
| 113 | + tx.vin.push_back(in); |
| 114 | + |
| 115 | + typename decltype(tx.vout)::value_type out; |
| 116 | + out.value = CB_VALUE; |
| 117 | + out.scriptPubKey = script_of(PK_SCRIPT); |
| 118 | + tx.vout.push_back(out); |
| 119 | + |
| 120 | + return tx; |
| 121 | +} |
| 122 | + |
| 123 | +// Fixed surrounding CMerkleTx fields. |
| 124 | +const uint256 BLOCK_HASH = |
| 125 | + uint256S("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); |
| 126 | +const uint256 MERKLE_BRANCH_0 = |
| 127 | + uint256S("ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100"); |
| 128 | +constexpr uint32_t MERKLE_INDEX = 1; |
| 129 | + |
| 130 | +doge::coin::CMerkleTx build_merkle_tx() { |
| 131 | + doge::coin::CMerkleTx mt; |
| 132 | + mt.m_tx = build_parent_coinbase<ltc::coin::MutableTransaction>(); |
| 133 | + mt.m_block_hash = BLOCK_HASH; |
| 134 | + mt.m_merkle_link.m_branch = {MERKLE_BRANCH_0}; |
| 135 | + mt.m_merkle_link.m_index = MERKLE_INDEX; |
| 136 | + return mt; |
| 137 | +} |
| 138 | + |
| 139 | +// --------------------------------------------------------------------------- |
| 140 | +// GOLDENS — captured against master f732061bc (see header). DO NOT hand-edit; |
| 141 | +// regenerate only with a documented module change and integrator sign-off. |
| 142 | +// --------------------------------------------------------------------------- |
| 143 | +// Witness-stripped parent coinbase (the m_tx member payload, == tx_id_type). |
| 144 | +const std::string PARENT_COINBASE_NOSEG = |
| 145 | + "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d03a1b2c3041122334455667788ffffffff0100f2052a010000001976a914333333333333333333333333333333333333333388ac00000000"; |
| 146 | +// Full doge::coin::CMerkleTx wire image: |
| 147 | +// TX_NO_WITNESS(m_tx) ++ m_block_hash(32, LE) ++ merkle_link(branch ++ index) |
| 148 | +const std::string MERKLE_TX_WIRE = |
| 149 | + "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d03a1b2c3041122334455667788ffffffff0100f2052a010000001976a914333333333333333333333333333333333333333388ac00000000ffeeddccbbaa99887766554433221100ffeeddccbbaa998877665544332211000100112233445566778899aabbccddeeff00112233445566778899aabbccddeeff01000000"; // index = 1 (uint32 LE) |
| 150 | + |
| 151 | +} // namespace |
| 152 | + |
| 153 | +// 1) The member payload: witness-stripped parent coinbase, ltc-typed (real type). |
| 154 | +TEST(DGB_AuxParentCoinbaseParity, LtcTypedMemberPayloadGolden) { |
| 155 | + auto tx = build_parent_coinbase<ltc::coin::MutableTransaction>(); |
| 156 | + EXPECT_EQ(pack_hex(ltc::coin::TX_NO_WITNESS(tx)), PARENT_COINBASE_NOSEG); |
| 157 | +} |
| 158 | + |
| 159 | +// 2) CROSS-TYPE INVARIANT: the dgb-typed (neutral stand-in) coinbase serializes |
| 160 | +// BYTE-IDENTICALLY to the ltc-typed one. This is exactly what the extraction |
| 161 | +// must preserve when m_tx's type is lifted to the Scrypt-family base. |
| 162 | +TEST(DGB_AuxParentCoinbaseParity, LtcVsDgbTypeByteIdentical) { |
| 163 | + auto ltc_tx = build_parent_coinbase<ltc::coin::MutableTransaction>(); |
| 164 | + auto dgb_tx = build_parent_coinbase<dgb::coin::MutableTransaction>(); |
| 165 | + const auto ltc_bytes = pack_hex(ltc::coin::TX_NO_WITNESS(ltc_tx)); |
| 166 | + const auto dgb_bytes = pack_hex(dgb::coin::TX_NO_WITNESS(dgb_tx)); |
| 167 | + EXPECT_EQ(ltc_bytes, dgb_bytes); |
| 168 | + EXPECT_EQ(ltc_bytes, PARENT_COINBASE_NOSEG); |
| 169 | +} |
| 170 | + |
| 171 | +// 3) Full shared-module CMerkleTx wire image, anchored to AUX_MODULE_MASTER_SHA. |
| 172 | +// Pins the pre-templating layout the extraction must reproduce verbatim. |
| 173 | +TEST(DGB_AuxParentCoinbaseParity, MerkleTxWireGoldenAnchored) { |
| 174 | + ASSERT_STREQ(AUX_MODULE_MASTER_SHA, "f732061bc"); // golden provenance guard |
| 175 | + auto mt = build_merkle_tx(); |
| 176 | + EXPECT_EQ(pack_hex(mt), MERKLE_TX_WIRE); |
| 177 | +} |
| 178 | + |
| 179 | +// 4) Round-trip: Unserialize(serialize(mt)) re-serializes to the same bytes. |
| 180 | +// A field-order/width bug that happened to be self-consistent on write but |
| 181 | +// not on read would diverge here. |
| 182 | +TEST(DGB_AuxParentCoinbaseParity, MerkleTxRoundTripStable) { |
| 183 | + auto mt = build_merkle_tx(); |
| 184 | + auto packed = pack(mt); |
| 185 | + doge::coin::CMerkleTx rt; |
| 186 | + packed >> rt; |
| 187 | + EXPECT_EQ(pack_hex(rt), MERKLE_TX_WIRE); |
| 188 | +} |
0 commit comments