diff --git a/src/impl/dash/coin/embedded_gbt.hpp b/src/impl/dash/coin/embedded_gbt.hpp index af299866c..938e85cd3 100644 --- a/src/impl/dash/coin/embedded_gbt.hpp +++ b/src/impl/dash/coin/embedded_gbt.hpp @@ -66,7 +66,13 @@ inline DashWorkData build_embedded_workdata( // Step 8 seam: injectable block time. Defaults to std::time(nullptr) so // every existing caller is byte-for-byte unchanged (SAFE-ADDITIVE); the // G1 golden KAT pins it for a deterministic template+coinbase vector. - uint32_t curtime = static_cast(std::time(nullptr))) + uint32_t curtime = static_cast(std::time(nullptr)), + // Seam: injectable block version. Defaults to 0x20000000 (BIP9 "no + // signaling" baseline) so every existing caller is byte-for-byte + // unchanged (SAFE-ADDITIVE); the G1 golden KAT pins it, and a real + // BIP9-deployment-aware value can later be threaded in without touching + // the default header projection. + uint32_t version = 0x20000000u) { DashWorkData w; w.m_height = prev_height + 1; @@ -78,7 +84,7 @@ inline DashWorkData build_embedded_workdata( // GBT returns MTP+1 as mintime so miners don't accidentally // produce stale-time blocks. w.m_mintime = mtp_at_tip + 1; - w.m_version = 0x20000000; // TODO: real BIP9 bits + w.m_version = version; // seam: default 0x20000000 (BIP9 baseline) // Subsidy + tx selection. int64_t reward = compute_dash_block_reward_post_v20(w.m_height); diff --git a/test/test_dash_embedded_gbt.cpp b/test/test_dash_embedded_gbt.cpp index 5232788c9..87e486594 100644 --- a/test/test_dash_embedded_gbt.cpp +++ b/test/test_dash_embedded_gbt.cpp @@ -498,3 +498,60 @@ TEST(DashEmbeddedGbt, G1GoldenDeterministicTemplateViaCurtimeSeam) { EXPECT_GE(live.m_curtime, 1'700'000'000u) << "default curtime must still read std::time(nullptr)"; } + +// ======================================================================== +// G1 golden: injectable block-version seam (companion to the curtime seam). +// +// build_embedded_workdata hardcoded m_version = 0x20000000 (BIP9 "no +// signaling" baseline). With the trailing defaulted `version` param a +// fixed-input call yields a byte-for-byte reproducible version projection, +// and a real BIP9-deployment-aware value can later be threaded through +// without disturbing the default. curtime is pinned here because the +// version seam sits AFTER curtime in the signature. +// ======================================================================== +TEST(DashEmbeddedGbt, G1GoldenVersionSeamEchoesInjectedVersion) { + UTXOViewCache utxo(nullptr); + uint256 prev = mint_hash(88); + utxo.add_coin(Outpoint(prev, 0), Coin(100'000, {}, /*height=*/1, /*cb=*/false)); + Mempool mp; + mp.set_utxo(&utxo); + auto tx = make_spend(prev, 0, 90'000, /*salt=*/9); // fee = 10'000 + ASSERT_TRUE(mp.add_tx(tx)); + + auto payout = p2pkh_script(0x55); + auto mnstates = single_mn(payout); + + uint256 prev_hash = raw256(0xD4); + const uint32_t bits = 0x1b104be3u; // pinned oracle GBT nBits + const uint32_t mtp = 1'700'000'000u; + const uint32_t PINNED_CURTIME = 1'700'000'123u; // pinned: sits before version in sig + const uint32_t PINNED_VERSION = 0x20000004u; // BIP9 baseline + bit 2 set + + // Injected version is echoed byte-for-byte -- the seam adds no arithmetic. + auto injected = build_embedded_workdata( + H - 1, prev_hash, mnstates, mp, + bits, mtp, DASH_PUBKEY_VER, DASH_P2SH_VER, PINNED_CURTIME, PINNED_VERSION); + EXPECT_EQ(injected.m_version, PINNED_VERSION); + + // Deterministic: same fixed inputs -> identical version projection. + auto injected2 = build_embedded_workdata( + H - 1, prev_hash, mnstates, mp, + bits, mtp, DASH_PUBKEY_VER, DASH_P2SH_VER, PINNED_CURTIME, PINNED_VERSION); + EXPECT_EQ(injected.m_version, injected2.m_version); + + // Default arg preserves the prior hardcoded projection: omitting version + // still yields the BIP9 "no signaling" baseline, so no caller is changed. + auto baseline = build_embedded_workdata( + H - 1, prev_hash, mnstates, mp, + bits, mtp, DASH_PUBKEY_VER, DASH_P2SH_VER, PINNED_CURTIME); + EXPECT_EQ(baseline.m_version, 0x20000000u); + + // The version seam is orthogonal to every other header field: injecting a + // non-default version changes ONLY m_version, nothing else drifts. + EXPECT_EQ(injected.m_height, baseline.m_height); + EXPECT_EQ(injected.m_previous_block, baseline.m_previous_block); + EXPECT_EQ(injected.m_bits, baseline.m_bits); + EXPECT_EQ(injected.m_mintime, baseline.m_mintime); + EXPECT_EQ(injected.m_coinbase_value, baseline.m_coinbase_value); + EXPECT_EQ(injected.m_payment_amount, baseline.m_payment_amount); +}