@@ -408,3 +408,93 @@ TEST(DashEmbeddedGbt, GbtXcheckMatchesIdenticalDetectsFieldDrift) {
408408 rpc_count.m_packed_payments .pop_back ();
409409 EXPECT_FALSE (gbt_xcheck (w, rpc_count));
410410}
411+
412+ // ════════════════════════════════════════════════════════════════════════
413+ // G1 golden: deterministic template+coinbase vector via the curtime seam.
414+ //
415+ // The sole non-determinism in build_embedded_workdata was the live
416+ // std::time(nullptr) read for m_curtime. With the trailing defaulted curtime
417+ // param, a fixed-input call yields a byte-for-byte reproducible header
418+ // projection — the G1 deliverable (fixed-input template+coinbase golden) in
419+ // the per-coin block-production gate.
420+ //
421+ // Oracle discipline (non-circular, mirrors the params KAT): bits is pinned to
422+ // the oracle GBT nBits and passed through; every value/split number is
423+ // re-derived here from the SAME closed-form dashcore formulas, not copied from
424+ // a build_embedded_workdata run. Only curtime is a pinned input we assert the
425+ // seam faithfully echoes.
426+ // ════════════════════════════════════════════════════════════════════════
427+
428+ TEST (DashEmbeddedGbt, G1GoldenDeterministicTemplateViaCurtimeSeam) {
429+ UTXOViewCache utxo (nullptr );
430+ uint256 prev = mint_hash (77 );
431+ utxo.add_coin (Outpoint (prev, 0 ), Coin (100'000 , {}, /* height=*/ 1 , /* cb=*/ false ));
432+ Mempool mp;
433+ mp.set_utxo (&utxo);
434+ auto tx = make_spend (prev, 0 , 90'000 , /* salt=*/ 7 ); // fee = 10'000
435+ ASSERT_TRUE (mp.add_tx (tx));
436+
437+ auto payout = p2pkh_script (0x55 );
438+ auto mnstates = single_mn (payout);
439+
440+ uint256 prev_hash = raw256 (0xC3 );
441+ const uint32_t bits = 0x1b104be3u ; // pinned oracle GBT nBits
442+ const uint32_t mtp = 1'700'000'000u ;
443+ const uint32_t PINNED_CURTIME = 1'700'000'123u ; // fixed block time
444+
445+ // Same fixed inputs → identical WorkData every run (the golden property).
446+ auto a = build_embedded_workdata (
447+ H - 1 , prev_hash, mnstates, mp,
448+ bits, mtp, DASH_PUBKEY_VER , DASH_P2SH_VER , PINNED_CURTIME );
449+ auto b = build_embedded_workdata (
450+ H - 1 , prev_hash, mnstates, mp,
451+ bits, mtp, DASH_PUBKEY_VER , DASH_P2SH_VER , PINNED_CURTIME );
452+
453+ // The seam echoes the pinned time exactly — no wall-clock leak.
454+ EXPECT_EQ (a.m_curtime , PINNED_CURTIME );
455+ EXPECT_EQ (b.m_curtime , PINNED_CURTIME );
456+
457+ // Full header projection is deterministic field-for-field.
458+ EXPECT_EQ (a.m_height , b.m_height );
459+ EXPECT_EQ (a.m_previous_block , b.m_previous_block );
460+ EXPECT_EQ (a.m_bits , b.m_bits );
461+ EXPECT_EQ (a.m_mintime , b.m_mintime );
462+ EXPECT_EQ (a.m_version , b.m_version );
463+ EXPECT_EQ (a.m_coinbase_value , b.m_coinbase_value );
464+ EXPECT_EQ (a.m_payment_amount , b.m_payment_amount );
465+
466+ // Independent re-derivation of the golden values (non-circular oracle).
467+ int64_t reward = compute_dash_block_reward_post_v20 (H);
468+ int64_t total_fees = 10'000 ;
469+ int64_t block_value = reward + total_fees;
470+ int64_t platform_reward = compute_dash_platform_reward_post_v20_mn_rr (H);
471+ int64_t mn_payment = compute_dash_mn_payment_post_v20 (block_value)
472+ - platform_reward;
473+
474+ EXPECT_EQ (a.m_height , H);
475+ EXPECT_EQ (a.m_previous_block , prev_hash);
476+ EXPECT_EQ (a.m_bits , bits); // oracle nBits passthrough
477+ EXPECT_EQ (a.m_mintime , mtp + 1u ); // GBT returns MTP+1
478+ EXPECT_EQ (a.m_version , 0x20000000u );
479+ EXPECT_EQ (a.m_coinbase_value , static_cast <uint64_t >(block_value));
480+ EXPECT_EQ (a.m_payment_amount , static_cast <uint64_t >(mn_payment));
481+
482+ // Coinbase payee side is deterministic: platform OP_RETURN burn FIRST,
483+ // then the MN base58 payee — same ordering the shadow path asserts.
484+ ASSERT_EQ (a.m_packed_payments .size (), 2u );
485+ EXPECT_EQ (a.m_packed_payments [0 ].payee , " !6a" ); // DIP-0027 burn
486+ EXPECT_EQ (a.m_packed_payments [0 ].amount ,
487+ static_cast <uint64_t >(platform_reward));
488+ EXPECT_EQ (a.m_packed_payments [1 ].amount ,
489+ static_cast <uint64_t >(mn_payment));
490+ EXPECT_FALSE (a.m_packed_payments [1 ].payee .empty ());
491+ EXPECT_NE (a.m_packed_payments [1 ].payee .front (), ' !' ); // base58, not !hex
492+
493+ // The default arg preserves the prior live-read behavior: omitting curtime
494+ // still reads the wall clock (post-2023 epoch), so no caller is changed.
495+ auto live = build_embedded_workdata (
496+ H - 1 , prev_hash, mnstates, mp,
497+ bits, mtp, DASH_PUBKEY_VER , DASH_P2SH_VER );
498+ EXPECT_GE (live.m_curtime , 1'700'000'000u )
499+ << " default curtime must still read std::time(nullptr)" ;
500+ }
0 commit comments