diff --git a/test/test_doge_chain.cpp b/test/test_doge_chain.cpp index 844b13a5c..3f559a6ad 100644 --- a/test/test_doge_chain.cpp +++ b/test/test_doge_chain.cpp @@ -474,6 +474,192 @@ TEST(AuxPowStructuredTest, BatchStopsAtTruncatedHeader) { EXPECT_EQ(result.size(), 1u) << "Truncated tail header must be dropped, first kept"; } +// ─── Known-Answer Vector: real Dogecoin mainnet AuxPoW block #371337 ───────── +// +// Provenance: Dogecoin block 371,337 — the FIRST AuxPoW block on mainnet +// (mainnet AuxPoW activation height). DOGE_371337_EXTHEADER below is that +// block's extended header exactly as it travels on the P2P 'headers' wire: +// CPureBlockHeader(80) + CAuxPow(proof) + tx_count(CompactSize = 0) +// +// It is assembled from Dogecoin Core's OWN decode of the on-chain block, NOT +// from c2pool's Serialize — so unlike the round-trip tests above it can catch +// a field-order or witness-mode error in OUR parser. Every component is +// double-SHA256 hash-verified against an externally published value, so the +// bytes cannot be silently wrong: +// * sha256d(first 80 bytes) == canonical block hash +// 60323982f9c5ff1b5a954eac9dc1269352835f47c2c5222691d80f0d50dcf053 +// * sha256d(parent coinbase) == its known txid +// e5422732b20e9e7ecc243427abbe296e9528d308bb111aae8d30c3465e442de8 +// * sha256d(parent 80B hdr) == merkle_tx.block_hash +// 45df41e40aba5b2a03d08bd1202a1c02ef3954d8aa22ea6c5ae62fd00f290ea9 +// All asserted field values are read off the block explorer, independent of +// the code under test. +namespace { +std::vector hex_to_bytes(const std::string& h) { + auto nib = [](char c) -> int { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; + }; + std::vector out; + out.reserve(h.size() / 2); + for (size_t i = 0; i + 1 < h.size(); i += 2) + out.push_back(static_cast((nib(h[i]) << 4) | nib(h[i + 1]))); + return out; +} + +// Dogecoin mainnet block #371337 extended header (headers-message form, +// tx_count = 0). See provenance note above. 536 bytes. +const char* const DOGE_371337_EXTHEADER = + "020162000d6f03470d329026cd1fc720c0609cd378ca8691a117bd1aa46f01fb" + "09b1a8468a15bf6f0b0e83f2e5036684169eafb9406468d4f075c999fb5b2a78" + "fbb827ee41fb11548441361b0000000001000000010000000000000000000000" + "000000000000000000000000000000000000000000ffffffff380345bf09fabe" + "6d6d980ba42120410de0554d42a5b5ee58167bcd86bf7591f429005f24da45fb" + "51cf0800000000000000cdb1f1ff0e000000ffffffff01800c0c2a0100000019" + "76a914aa3750aa18b8a0f3f0590731e1fab934856680cf88ac00000000a90e29" + "0fd02fe65a6cea22aad85439ef021c2a20d18bd0032a5bba0ae441df4503a979" + "a636db2450363972d211aee67b71387a3daaa3051be0fd260c5acd4739cd52a4" + "18d29d8a0e56c8714c95a0dc24e1c9624480ec497fe2441941f3fee8f9481a33" + "70c334178415c83d1d0c2deeec727c2330617a47691fc5e79203669312d10000" + "0000036fa40307b3a439538195245b0de56a2c1db6ba3a64f8bdd2071d00bc48" + "c841b5e77b98e5c7d6f06f92dec5cf6d61277ecb9a0342406f49f34c51ee8ce4" + "abd678038129485de14238bd1ca12cd2de12ff0e383aee542d90437cd664ce13" + "9446a00000000002000000d2ec7dfeb7e8f43fe77aba3368df95ac2088034420" + "402730ee0492a2084217083411b3fc91033bfdeea339bc11b9efc986e161c703" + "e07a9045338c165673f09940fb11548b54021b58cc9ae500"; +} // namespace + +TEST(AuxPowKnownAnswerTest, Mainnet371337SkipParserRecoversChildHeader) { + auto blob = hex_to_bytes(DOGE_371337_EXTHEADER); + ASSERT_EQ(blob.size(), 536u); + + const uint8_t* pos = blob.data(); + const uint8_t* end = blob.data() + blob.size(); + auto hdr = doge::coin::parse_doge_header(pos, end); + + EXPECT_EQ(hdr.m_version, 0x00620102u); + EXPECT_TRUE(doge::coin::is_auxpow_version(static_cast(hdr.m_version))); + EXPECT_EQ(hdr.m_timestamp, 1410464577u); + EXPECT_EQ(hdr.m_bits, 0x1b364184u); + EXPECT_EQ(hdr.m_nonce, 0u); + uint256 prev; prev.SetHex("46a8b109fb016fa41abd17a19186ca78d39c60c020c71fcd2690320d47036f0d"); + uint256 root; root.SetHex("ee27b8fb782a5bfb99c975f0d4686440b9af9e16846603e5f2830e0b6fbf158a"); + EXPECT_EQ(hdr.m_previous_block, prev); + EXPECT_EQ(hdr.m_merkle_root, root); + EXPECT_EQ(pos, end) << "skip parser must consume header + auxpow + tx_count exactly"; +} + +TEST(AuxPowKnownAnswerTest, Mainnet371337StructuredDecodesRealProof) { + auto blob = hex_to_bytes(DOGE_371337_EXTHEADER); + PackStream ps(std::span( + reinterpret_cast(blob.data()), blob.size())); + doge::coin::CAuxPow out; + bool has_aux = false; + auto hdr = doge::coin::parse_aux_header(ps, out, has_aux); + + ASSERT_TRUE(has_aux); + EXPECT_EQ(hdr.m_version, 0x00620102u); + + // Parent coinbase tx (witness-stripped tx_id_type), decoded field-for-field. + ASSERT_EQ(out.m_merkle_tx.m_tx.vin.size(), 1u); + EXPECT_TRUE(out.m_merkle_tx.m_tx.vin[0].prevout.hash.IsNull()); + EXPECT_EQ(out.m_merkle_tx.m_tx.vin[0].prevout.index, 0xffffffffu); + EXPECT_EQ(out.m_merkle_tx.m_tx.vin[0].sequence, 0xffffffffu); + EXPECT_EQ(out.m_merkle_tx.m_tx.vin[0].scriptSig.m_data, + hex_to_bytes("0345bf09fabe6d6d980ba42120410de0554d42a5b5ee58167bcd86bf7591f429005f24da45fb51cf0800000000000000cdb1f1ff0e000000")); + ASSERT_EQ(out.m_merkle_tx.m_tx.vout.size(), 1u); + EXPECT_EQ(out.m_merkle_tx.m_tx.vout[0].value, 5000400000LL); + EXPECT_EQ(out.m_merkle_tx.m_tx.vout[0].scriptPubKey.m_data, + hex_to_bytes("76a914aa3750aa18b8a0f3f0590731e1fab934856680cf88ac")); + + // CMerkleTx.block_hash == the parent block the coinbase was included in. + uint256 pblk; pblk.SetHex("45df41e40aba5b2a03d08bd1202a1c02ef3954d8aa22ea6c5ae62fd00f290ea9"); + EXPECT_EQ(out.m_merkle_tx.m_block_hash, pblk); + + // Coinbase merkle branch: 3 levels, leaf index 0. + ASSERT_EQ(out.m_merkle_tx.m_merkle_link.m_branch.size(), 3u); + uint256 mb0; mb0.SetHex("cd3947cd5a0c26fde01b05a3aa3d7a38717be6ae11d27239365024db36a679a9"); + EXPECT_EQ(out.m_merkle_tx.m_merkle_link.m_branch[0], mb0); + EXPECT_EQ(out.m_merkle_tx.m_merkle_link.m_index, 0u); + + // Chain (aux) merkle branch: 3 levels, chain slot index 0. + ASSERT_EQ(out.m_chain_merkle_link.m_branch.size(), 3u); + uint256 cmb0; cmb0.SetHex("b541c848bc001d07d2bdf8643abab61d2c6ae50d5b2495815339a4b30703a46f"); + EXPECT_EQ(out.m_chain_merkle_link.m_branch[0], cmb0); + EXPECT_EQ(out.m_chain_merkle_link.m_index, 0u); + + // Parent (LTC) 80-byte block header. + EXPECT_EQ(out.m_parent_block_header.m_version, 2u); + EXPECT_EQ(out.m_parent_block_header.m_timestamp, 1410464576u); + EXPECT_EQ(out.m_parent_block_header.m_bits, 0x1b02548bu); + EXPECT_EQ(out.m_parent_block_header.m_nonce, 3852127320u); + + // The whole proof is consumed; only the tx_count CompactSize byte remains. + EXPECT_EQ(ps.cursor_size(), 1u) << "structured parse must stop exactly at tx_count"; +} + +// ─── Known-Answer Vector: self-hosted regtest/testnet4alpha AuxPoW (h=20) ── +// +// Provenance: a real AuxPoW block mined at height 20 on a self-hosted Dogecoin +// regtest/testnet4alpha seed (VM 215) and ACCEPTED by dogecoind — the +// operator-ratified fixture path (public testnet4 was rejected as contaminated +// with ultrafast blocks). DOGE_REGTEST_H20_CAUXPOW is the 217-byte CAuxPow +// proof exactly as dogecoind's OWN serializer emitted it: parent coinbase +// (witness-stripped) + CMerkleTx tail + chain merkle link + parent 80B header. +// It is a bare CAuxPow — NOT an extended header (no 80B child prefix, no +// tx_count) — so it is fed straight to the c2pool CAuxPow Unserialize path, +// the same one parse_aux_header() drives. Independent serializer => a real +// cross-implementation check, not a c2pool self-round-trip. +namespace { +const char* const DOGE_REGTEST_H20_CAUXPOW = + "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2cfabe6d6df091be03e588d8f33a0bc14145cb034dbf68e0a8ca774f0f842027a1366275900100000000000000ffffffff0000000000106303778e4f91a2984bb151de0bf0126c6cde013e9e11f86f7a942818f635f00000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000013175e533f07d6a5e7ab3df1fb0e67283b2b52daf0aaba1efbb2a22c77e96695000000000000000000000001"; +} // namespace + +TEST(AuxPowKnownAnswerTest, RegtestH20CAuxPowDecodesRealProof) { + auto blob = hex_to_bytes(DOGE_REGTEST_H20_CAUXPOW); + ASSERT_EQ(blob.size(), 217u); + + PackStream ps(std::span( + reinterpret_cast(blob.data()), blob.size())); + doge::coin::CAuxPow out; + ::Unserialize(ps, out); // c2pool CAuxPow parser, on dogecoind's own bytes + + // Parent coinbase: single null-prevout input carrying the merged-mining + // (fabe6d6d) commitment in its scriptSig; no outputs; locktime 0. + ASSERT_EQ(out.m_merkle_tx.m_tx.vin.size(), 1u); + EXPECT_TRUE(out.m_merkle_tx.m_tx.vin[0].prevout.hash.IsNull()); + EXPECT_EQ(out.m_merkle_tx.m_tx.vin[0].prevout.index, 0xffffffffu); + EXPECT_EQ(out.m_merkle_tx.m_tx.vin[0].sequence, 0xffffffffu); + EXPECT_EQ(out.m_merkle_tx.m_tx.vin[0].scriptSig.m_data, + hex_to_bytes("fabe6d6df091be03e588d8f33a0bc14145cb034dbf68e0a8ca774f0f842027a1366275900100000000000000")); + EXPECT_EQ(out.m_merkle_tx.m_tx.vout.size(), 0u); + EXPECT_EQ(out.m_merkle_tx.m_tx.locktime, 0u); + + // CMerkleTx tail: parent block hash; empty coinbase merkle branch (leaf 0). + uint256 pblk; pblk.SetHex("f035f61828947a6ff8119e3e01de6c6c12f00bde51b14b98a2914f8e77036310"); + EXPECT_EQ(out.m_merkle_tx.m_block_hash, pblk); + EXPECT_EQ(out.m_merkle_tx.m_merkle_link.m_branch.size(), 0u); + EXPECT_EQ(out.m_merkle_tx.m_merkle_link.m_index, 0u); + + // Chain (aux) merkle link: single aux chain => empty branch, slot 0. + EXPECT_EQ(out.m_chain_merkle_link.m_branch.size(), 0u); + EXPECT_EQ(out.m_chain_merkle_link.m_index, 0u); + + // Parent (regtest) 80-byte header. + EXPECT_EQ(out.m_parent_block_header.m_version, 1u); + EXPECT_TRUE(out.m_parent_block_header.m_previous_block.IsNull()); + uint256 proot; proot.SetHex("9566e9772ca2b2fb1ebaaaf0da522b3b28670efbf13dabe7a5d6073f535e1713"); + EXPECT_EQ(out.m_parent_block_header.m_merkle_root, proot); + EXPECT_EQ(out.m_parent_block_header.m_timestamp, 0u); + EXPECT_EQ(out.m_parent_block_header.m_bits, 0u); + EXPECT_EQ(out.m_parent_block_header.m_nonce, 0x01000000u); // wire 00 00 00 01 LE + + // The 217-byte CAuxPow is consumed exactly — no trailing bytes, no overrun. + EXPECT_EQ(ps.cursor_size(), 0u) << "CAuxPow parser must consume exactly 217 bytes"; +} + // ─── Mersenne Twister Uniform Int Tests ───────────────────────────────────── TEST(MersenneTwisterTest, BoostCompatibleOutput) {