diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c1025dec0..e90d6dd33 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,7 +82,7 @@ jobs: test_address_resolution test_compute_share_target \ test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_redistribute_test dgb_block_assembly_test dgb_header_sample_build_test dgb_header_ingest_test dgb_mempool_ingest_test \ dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test dgb_gentx_share_path_test dgb_conn_pplns_producer_test dgb_other_tx_resolver_test \ - dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_dc_proof_test \ + dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test \ rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \ dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test v37_test \ -j$(nproc) @@ -214,7 +214,7 @@ jobs: test_address_resolution test_compute_share_target \ test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_redistribute_test dgb_block_assembly_test dgb_header_sample_build_test dgb_header_ingest_test dgb_mempool_ingest_test \ dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test dgb_gentx_share_path_test dgb_conn_pplns_producer_test dgb_other_tx_resolver_test \ - dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_dc_proof_test \ + dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test \ rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \ dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \ v37_test \ diff --git a/src/impl/dgb/coin/node.hpp b/src/impl/dgb/coin/node.hpp index 01ab209ec..3ae4ea48d 100644 --- a/src/impl/dgb/coin/node.hpp +++ b/src/impl/dgb/coin/node.hpp @@ -33,10 +33,17 @@ #ifdef AUX_DOGE // DOGE merged-mining aux module (STRETCH; -DAUX_DOGE=ON only). dgb CONSUMES the // shared aux types; ltc-doge owns and is the sole modifier of src/impl/doge/coin/aux_*. -// Forward declaration only at slice #3 -- the real -// include lands with the -// bind_aux_doge_parsers() body at M3. -namespace doge { namespace coin { class AuxChainEmbedded; } } +// M3: the REAL shared-module includes that let bind_aux_doge_parsers() instantiate +// the structured AuxPoW parser with the DGB parent coinbase type. Pulling these in +// here (under AUX_DOGE only) is what ODR-USES the DGB-parent template emissions +// (parse_aux_header / CAuxPow) inside the PRODUCTION dgb object library, not merely in fixtures. +#include + +#include // PackStream (the byte stream the parser reads) +#include // shared SSOT: CAuxPow<>, parse_aux_header<>, CPureBlockHeader +#include // dgb::coin::MutableTransaction (the DGB parent coinbase type) +#include // doge::coin::parent_coinbase_no_witness #endif namespace dgb @@ -47,14 +54,27 @@ namespace coin #ifdef AUX_DOGE // --------------------------------------------------------------------------- -// DOGE merged-mining aux seam -- header-only type alias (slice #3, option b per -// integrator UID-904). Names the shared src/impl/doge aux-module backend that -// bind_aux_doge_parsers() wires onto the (M3) coin P2P layer. No new TU; no -// effect on the default Scrypt-only standalone build (which never defines -// AUX_DOGE). The structured parsers consumed at M3 bind time are the free -// functions doge::coin::parse_doge_header / parse_doge_headers_message / -// parse_doge_block (auxpow_header.hpp). Parity: p2pool-merged-v36. -using AuxChainBackend = ::doge::coin::AuxChainEmbedded; // IAuxChainBackend impl +// DOGE merged-mining aux seam (M3 body; -DAUX_DOGE=ON only). The DGB-as-parent +// structured AuxPoW parse contract, bound onto the production dgb object lib by +// bind_aux_doge_parsers(). The shared module's parser is templated on the parent +// coinbase type; here we pin it to dgb::coin::MutableTransaction so the DGB-parent +// CAuxPow<> / parse_aux_header<> template instantiations are emitted and ODR-used +// in production (not only in test fixtures). Parity: p2pool-merged-v36. +// +// AuxDogeParse: the result of feeding a (possibly AuxPoW-extended) DOGE header +// blob through the DGB-parent parser -- the 80-byte child header, the structured +// CAuxPow proof, and whether an aux proof was present. +struct AuxDogeParse +{ + ::doge::coin::CPureBlockHeader m_header; // 80-byte child header + ::doge::coin::CAuxPow m_aux; // structured DGB-parent proof + bool m_has_aux{}; // AuxPoW version-bit present +}; + +// The bound parser callable: consumes bytes from a PackStream and yields the +// DGB-parent parse. std::function so bind_aux_doge_parsers() can ASSIGN it (real, +// callable binding -- never a no-op), forcing ODR-use of the templated parser. +using AuxDogeParserFn = std::function; #endif template @@ -88,10 +108,32 @@ class Node : public dgb::interfaces::Node NodeRPC* rpc() { return m_rpc.get(); } #ifdef AUX_DOGE - // DOGE merged-mining aux seam (STRETCH; -DAUX_DOGE=ON only). Binds the shared - // src/impl/doge aux module's parsers onto the (M3) coin P2P layer. Declaration - // only -- no body until NodeP2P is ported. Parity: p2pool-merged-v36. - void bind_aux_doge_parsers(); + // DOGE merged-mining aux seam (STRETCH; -DAUX_DOGE=ON only). The DGB-parent + // structured AuxPoW parser, bound by bind_aux_doge_parsers(). Unbound until + // bind is called. Parity: p2pool-merged-v36. + AuxDogeParserFn m_aux_doge_parser; + + // Binds the shared src/impl/doge aux module's structured parser onto the dgb + // node, pinned to the DGB parent coinbase type. The body is a genuine callable + // binding (NOT a no-op): it assigns m_aux_doge_parser to a lambda that drives + // bytes through doge::coin::parse_aux_header(). + // Inline (the class is templated; an out-of-line def would need explicit + // instantiation) -- matches the existing inline run(). This is what ODR-USES / + // emits the DGB-parent CAuxPow<> + parse_aux_header<> instantiations in the + // production dgb object library. NodeP2P (not yet ported) will route relayed + // DOGE headers through m_aux_doge_parser at M3 live-dispatch time. + void bind_aux_doge_parsers() + { + m_aux_doge_parser = [](PackStream& s) -> AuxDogeParse { + AuxDogeParse out; + out.m_header = ::doge::coin::parse_aux_header( + s, out.m_aux, out.m_has_aux); + return out; + }; + } + + // Accessor for the bound parser (test surface + future NodeP2P dispatch). + const AuxDogeParserFn& aux_doge_parser() const { return m_aux_doge_parser; } #endif }; diff --git a/src/impl/dgb/test/CMakeLists.txt b/src/impl/dgb/test/CMakeLists.txt index a43045e63..6f0096676 100644 --- a/src/impl/dgb/test/CMakeLists.txt +++ b/src/impl/dgb/test/CMakeLists.txt @@ -569,6 +569,24 @@ if (BUILD_TESTING AND GTest_FOUND) dgb_coin pool sharechain) gtest_add_tests(dgb_aux_doge_dc_proof_test "" AUTO) + # --- DGB+DOGE (M3) -- bind_aux_doge_parsers() PRODUCTION-binding KAT -------- + # Proves the production dgb node seam coin/node.hpp's bind_aux_doge_parsers() + # is NON-HOLLOW: it instantiates a dgb::coin::Node, calls the (now real) bind + # body, and drives a canonical DGB-parent AuxPoW blob THROUGH the bound member + # -- ODR-USING parse_aux_header + CAuxPow in the PRODUCTION dgb object library, not merely in + # fixtures. Links the production dgb lib + ltc_coin like the DB/DC siblings -- + # the -DAUX_DOGE consumption path legitimately couples to ltc. Test-only; + # consumes the shared module, modifies nothing in src/impl/doge. MUST appear in + # BOTH this registration AND the build.yml --target allowlist (#143 NOT_BUILT). + add_executable(dgb_aux_doge_bind_parsers_test aux_doge_bind_parsers_test.cpp) + target_link_libraries(dgb_aux_doge_bind_parsers_test PRIVATE + GTest::gtest_main GTest::gtest + core dgb ltc_coin + c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage + dgb_coin pool sharechain) + gtest_add_tests(dgb_aux_doge_bind_parsers_test "" AUTO) + # dgb_share_target_genesis_test: FENCED KAT pinning ShareTracker::compute_ # share_target genesis/unknown-prev clamp to [MAX_TARGET/30, MAX_TARGET] vs # the DGB oracle data.py generate_transaction. Links the same proven set as diff --git a/src/impl/dgb/test/aux_doge_bind_parsers_test.cpp b/src/impl/dgb/test/aux_doge_bind_parsers_test.cpp new file mode 100644 index 000000000..031cf9e20 --- /dev/null +++ b/src/impl/dgb/test/aux_doge_bind_parsers_test.cpp @@ -0,0 +1,252 @@ +// --------------------------------------------------------------------------- +// DGB+DOGE merged-mining (M3) — bind_aux_doge_parsers() PRODUCTION-binding KAT. +// Fenced / test-only — links the PRODUCTION dgb object lib, consumes the shared +// aux module, modifies nothing in src/impl/doge. +// +// PURPOSE. Phase DA/DB pinned the DGB-as-parent AuxPoW *contracts* +// (parse_aux_header + CAuxPow) but only ever exercised them from test fixtures; the production +// dgb node seam bind_aux_doge_parsers() was declaration-only. M3 gives that +// member a real body: it ASSIGNS a stored parser callable that drives bytes +// through the DGB-parent parser, ODR-USING / emitting those template +// instantiations inside the production dgb object library. +// +// This KAT proves the binding is NON-HOLLOW: it constructs a real DGB-parent +// AuxPoW header blob, instantiates a dgb::coin::Node, calls +// bind_aux_doge_parsers(), then feeds the blob THROUGH the bound member and +// asserts the parsed CAuxPow round-trips and carries the expected parent +// coinbase field (has_aux true; parent coinbase value matches). +// +// CANONICAL BLOB. The blob is captured as ONE hex literal (CANONICAL_AUX_BLOB), +// produced deterministically from the shared serializer (see BuildCanonicalBlob +// below, whose self-consistency is asserted) — never hand-split. +// +// Per-coin isolation note: legitimately links the ltc OBJECT lib because the +// -DAUX_DOGE shared module includes ltc/coin/transaction.hpp by design. We +// CONSUME the module; we do not modify it. MUST appear in BOTH +// test/CMakeLists.txt AND the build.yml --target allowlist or it becomes a +// NOT_BUILT sentinel that reds master. +// --------------------------------------------------------------------------- + +#include + +#include // PRODUCTION dgb node seam (bind_aux_doge_parsers) +#include // shared SSOT: CAuxPow<>, parse_aux_header<>, CPureBlockHeader +#include // dgb::coin::MutableTransaction +#include // DGB-parent witness-strip specialization + +#include +#include +#include + +#include // dgb::coin::Node ctx ctor param type + +#include +#include +#include + +namespace { + +std::vector unhex(const std::string& h) { + std::vector v; v.reserve(h.size() / 2); + auto nyb = [](char c) -> int { return (c <= '9') ? c - '0' : (c | 0x20) - 'a' + 10; }; + for (size_t i = 0; i + 1 < h.size(); i += 2) + v.push_back(static_cast((nyb(h[i]) << 4) | nyb(h[i + 1]))); + return v; +} +std::string tohex(const std::vector& v) { + static const char* H = "0123456789abcdef"; + std::string s; s.reserve(v.size() * 2); + for (unsigned char b : v) { s.push_back(H[b >> 4]); s.push_back(H[b & 0xf]); } + return s; +} +template +std::vector pack_bytes(const T& value) { + auto packed = pack(value); + auto sp = packed.get_span(); + return std::vector( + reinterpret_cast(sp.data()), + reinterpret_cast(sp.data()) + sp.size()); +} + +OPScript script_of(const std::vector& bytes) { + return OPScript(bytes.data(), bytes.data() + bytes.size()); +} + +// --- canonical, deterministic parent coinbase fields ----------------------- +const std::vector CB_SCRIPT = unhex("03a1b2c3041122334455667788"); +const std::vector PK_SCRIPT = + unhex(std::string("76a914") + std::string(40, '3') + "88ac"); +constexpr int32_t TX_VERSION = 1; +constexpr uint32_t TX_LOCKTIME = 0; +constexpr uint32_t CB_SEQUENCE = 0xffffffffu; +constexpr int64_t CB_VALUE = 5000000000ll; // 50.00000000 + +dgb::coin::MutableTransaction build_parent_coinbase() { + dgb::coin::MutableTransaction tx; + tx.version = TX_VERSION; + tx.locktime = TX_LOCKTIME; + + typename decltype(tx.vin)::value_type in; + in.prevout.hash = uint256(); + in.prevout.index = 0xffffffffu; + in.scriptSig = script_of(CB_SCRIPT); + in.sequence = CB_SEQUENCE; + tx.vin.push_back(in); + + typename decltype(tx.vout)::value_type out; + out.value = CB_VALUE; + out.scriptPubKey = script_of(PK_SCRIPT); + tx.vout.push_back(out); + return tx; +} + +doge::coin::CMerkleLink canonical_merkle_link() { + doge::coin::CMerkleLink link; + link.m_branch = std::vector{uint256(), uint256()}; + link.m_index = 5; + return link; +} + +doge::coin::CAuxPow build_auxpow() { + doge::coin::CAuxPow aux; + aux.SetNull(); + aux.m_merkle_tx.m_tx = build_parent_coinbase(); + aux.m_merkle_tx.m_block_hash = uint256(); + aux.m_merkle_tx.m_merkle_link = canonical_merkle_link(); + aux.m_chain_merkle_link = canonical_merkle_link(); + return aux; +} + +// Child header carrying the AuxPoW version bit (0x100) so parse_aux_header +// deserializes the trailing CAuxPow proof. +constexpr uint32_t AUXPOW_VERSION = 0x00620100u; // chain-id 0x62 in high bits | aux bit 0x100 +constexpr uint32_t HDR_TIMESTAMP = 0x5f5e1000u; +constexpr uint32_t HDR_BITS = 0x1e0ffff0u; +constexpr uint32_t HDR_NONCE = 0x12345678u; + +doge::coin::CPureBlockHeader build_child_header() { + doge::coin::CPureBlockHeader hdr; + hdr.SetNull(); + hdr.m_version = AUXPOW_VERSION; + hdr.m_previous_block = uint256(); + hdr.m_merkle_root = uint256(); + hdr.m_timestamp = HDR_TIMESTAMP; + hdr.m_bits = HDR_BITS; + hdr.m_nonce = HDR_NONCE; + return hdr; +} + +// Build the full wire image: 80-byte child header followed by the structured +// CAuxPow proof — exactly the byte stream parse_aux_header consumes. +std::vector build_canonical_blob() { + std::vector blob = pack_bytes(build_child_header()); + std::vector aux = pack_bytes(build_auxpow()); + blob.insert(blob.end(), aux.begin(), aux.end()); + return blob; +} + +// The ONE canonical hex literal — captured from build_canonical_blob() and +// pinned here so the bound-parser path is fed a fixed wire image, not a +// re-derived structure. (Self-consistency asserted in BlobMatchesBuilder.) +const char* CANONICAL_AUX_BLOB = + "00016200" // version (LE 0x00620100) + aux bit + "0000000000000000000000000000000000000000000000000000000000000000" // prev block + "0000000000000000000000000000000000000000000000000000000000000000" // merkle root + "00105e5f" // timestamp + "f0ff0f1e" // bits + "78563412" // nonce + // --- CAuxPow proof --- + // m_merkle_tx: parent coinbase + "01000000" // tx version + "01" // vin count + "0000000000000000000000000000000000000000000000000000000000000000" // prevout hash + "ffffffff" // prevout index + "0d03a1b2c3041122334455667788" // scriptSig (len 13 + bytes) + "ffffffff" // sequence + "01" // vout count + "00f2052a01000000" // value 50 BTC LE + "1976a914333333333333333333333333333333333333333388ac" // scriptPubKey (len 25 + p2pkh) + "00000000" // locktime + // m_block_hash + "0000000000000000000000000000000000000000000000000000000000000000" + // m_merkle_link + "02" // branch count + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000" + "05000000" // index 5 + // m_chain_merkle_link + "02" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000" + "05000000" + // m_parent_block_header (null 80-byte) + "00000000" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000" + "00000000" + "00000000" + "00000000"; + +} // namespace + +// Sanity: the pinned hex literal equals the deterministically built wire image. +// If the shared serializer field-order ever drifts, this trips (and so does the +// sibling parity golden) — proving the literal is a faithful capture, not magic. +TEST(DGB_AuxDogeBindParsers, BlobMatchesBuilder) { + EXPECT_EQ(tohex(build_canonical_blob()), std::string(CANONICAL_AUX_BLOB)); +} + +#ifdef AUX_DOGE // node seam (bind_aux_doge_parsers/aux_doge_parser) exists + // only in the dual-parent build; default Scrypt-only arm skips these. +// CORE: the production seam binds a NON-HOLLOW parser. Build a node, bind, feed +// the canonical blob through the bound member, assert the DGB-parent proof is +// parsed (has_aux true) and carries the expected parent coinbase value. +TEST(DGB_AuxDogeBindParsers, BoundParserConsumesDgbParentBlob) { + struct FakeConfig { bool m_testnet = false; }; + FakeConfig cfg; + boost::asio::io_context ioc; // ctor only stores the pointer; no transport bring-up / run() here. + dgb::coin::Node node(&ioc, &cfg); + + // Before bind: parser is unbound. + ASSERT_FALSE(static_cast(node.aux_doge_parser())); + + node.bind_aux_doge_parsers(); + ASSERT_TRUE(static_cast(node.aux_doge_parser())); + + PackStream s(unhex(CANONICAL_AUX_BLOB)); + auto parsed = node.aux_doge_parser()(s); + + // Non-hollow: the aux version bit was honored and a real proof deserialized. + EXPECT_TRUE(parsed.m_has_aux); + EXPECT_EQ(parsed.m_header.m_version, AUXPOW_VERSION); + EXPECT_EQ(parsed.m_header.m_nonce, HDR_NONCE); + + // Parent coinbase round-tripped THROUGH the DGB-parent CAuxPow path. + ASSERT_EQ(parsed.m_aux.m_merkle_tx.m_tx.vout.size(), 1u); + EXPECT_EQ(parsed.m_aux.m_merkle_tx.m_tx.vout[0].value, CB_VALUE); + EXPECT_EQ(parsed.m_aux.m_chain_merkle_link.m_index, 5); +} + +// The parsed CAuxPow re-serializes to the same proof bytes that followed +// the 80-byte header in the canonical blob (byte-exact round trip via the +// production-bound parser). +TEST(DGB_AuxDogeBindParsers, BoundParserAuxRoundTripsByteIdentical) { + struct FakeConfig { bool m_testnet = false; }; + FakeConfig cfg; + boost::asio::io_context ioc; + dgb::coin::Node node(&ioc, &cfg); + node.bind_aux_doge_parsers(); + + const auto whole = unhex(CANONICAL_AUX_BLOB); + PackStream s(whole); + auto parsed = node.aux_doge_parser()(s); + + // Re-emit header+aux and require byte-for-byte identity with the input blob. + std::vector reemit = pack_bytes(parsed.m_header); + std::vector aux = pack_bytes(parsed.m_aux); + reemit.insert(reemit.end(), aux.begin(), aux.end()); + EXPECT_EQ(tohex(reemit), tohex(whole)); +} + +#endif // AUX_DOGE