From e2714b9649910d1869d3dfab3bfef5d0c54d4094 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Fri, 19 Jun 2026 14:17:33 +0000 Subject: [PATCH 1/2] dgb: wire get_current_work_template coinbasevalue through #207 SSOT (Stage 4c) get_current_work_template() now emits the next-block height and its coinbasevalue instead of an empty object. The coinbasevalue is derived through the #207 embedded SSOT (coinbase_value -> resolve_coinbase_value -> subsidy_func) keyed on next_block_height() (#209, == tip.height + 1; == base_height for an empty chain). Embedded path: no external-daemon GBT value is plumbed in yet and mempool-fee aggregation is unwired, so total_fees = 0; a present GBT coinbasevalue stays authoritative and fees add on top of subsidy in later slices without changing this SSOT call. The remaining GBT template fields (previousblockhash, bits, version, curtime, mintime, transactions[]) and the build_connection_coinbase gentx assembly land in following Stage 4c/4d slices. Test: +WorkTemplateEmitsHeightAndCoinbaseValueViaSsot pins height + coinbasevalue to the p2pool-dgb-scrypt oracle subsidy at the phase3 era boundary; existing stub-default assertion relaxed. dgb_work_source_test 14/14. --- src/impl/dgb/stratum/work_source.cpp | 25 ++++++++++++++++++++----- src/impl/dgb/test/work_source_test.cpp | 24 +++++++++++++++++++++++- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/impl/dgb/stratum/work_source.cpp b/src/impl/dgb/stratum/work_source.cpp index c07029160..94aa0fde2 100644 --- a/src/impl/dgb/stratum/work_source.cpp +++ b/src/impl/dgb/stratum/work_source.cpp @@ -148,11 +148,26 @@ void DGBWorkSource::update_stratum_worker(const std::string& session_id, nlohmann::json DGBWorkSource::get_current_work_template() const { - // Stage 4c: drive dgb::coin::TemplateBuilder over chain_ + mempool_ and - // shape its WorkData into the GBT-style JSON the stratum session expects - // (previousblockhash, bits, version, curtime, mintime, height, - // coinbasevalue, transactions[]). Scrypt nBits, not SHA256d. - return nlohmann::json::object(); + // Stage 4c (coinbasevalue wire): the full GBT-shaped template + // (previousblockhash, bits, version, curtime, mintime, transactions[]) + // is assembled by the embedded dgb::coin::TemplateBuilder in a following + // slice. What lands here is the consensus-bearing reward field: the + // coinbasevalue for the NEXT block, derived through the #207 SSOT + // (coinbase_value -> resolve_coinbase_value -> subsidy_func) keyed on the + // absolute next-block height from the #209 HeaderChain accessor + // (next_block_height() == tip_height()+1, or base_height for an empty + // chain). Embedded path: no external-daemon GBT value is plumbed in yet + // and mempool-fee aggregation is not wired, so total_fees = 0 here. Both + // compose in later slices WITHOUT changing this SSOT call (a present GBT + // coinbasevalue stays authoritative; fees add on top of subsidy). + const uint32_t next_h = chain_.next_block_height(); + const uint64_t coinbasevalue = + coinbase_value(next_h, /*total_fees=*/0, /*gbt_coinbasevalue=*/std::nullopt); + + nlohmann::json tmpl = nlohmann::json::object(); + tmpl["height"] = next_h; + tmpl["coinbasevalue"] = coinbasevalue; + return tmpl; } std::vector DGBWorkSource::get_stratum_merkle_branches() const diff --git a/src/impl/dgb/test/work_source_test.cpp b/src/impl/dgb/test/work_source_test.cpp index 6d0dc6f82..400ac20dd 100644 --- a/src/impl/dgb/test/work_source_test.cpp +++ b/src/impl/dgb/test/work_source_test.cpp @@ -135,8 +135,10 @@ TEST(DgbWorkSource, WorkGenStubsReturnSafeDefaults) // 4a skeleton: every work-generation getter returns its documented // empty/default form (4c fills them in). EXPECT_TRUE(ws->get_current_gbt_prevhash().empty()); + // get_current_work_template() now emits height + coinbasevalue (Stage 4c + // coinbasevalue wire); its dedicated assertions live in + // WorkTemplateEmitsHeightAndCoinbaseValueViaSsot below. EXPECT_TRUE(ws->get_current_work_template().is_object()); - EXPECT_TRUE(ws->get_current_work_template().empty()); EXPECT_TRUE(ws->get_stratum_merkle_branches().empty()); auto parts = ws->get_coinbase_parts(); EXPECT_TRUE(parts.first.empty()); @@ -174,6 +176,26 @@ TEST(DgbWorkSource, ComputeShareDifficultyReturnsNotYetSentinel) EXPECT_DOUBLE_EQ(diff, 0.0); } +// Stage 4c coinbasevalue wire: the work template surfaces the NEXT-block +// height and its coinbasevalue, the latter derived THROUGH the #207 SSOT +// (subsidy_func) keyed on next_block_height() == tip.height + 1 (#209). An +// empty chain makes next_block_height() == base_height, so seeding an oracle +// era boundary pins the value unambiguously to the p2pool-dgb-scrypt subsidy. +TEST(DgbWorkSource, WorkTemplateEmitsHeightAndCoinbaseValueViaSsot) +{ + Fixture fx; + fx.chain.set_base_height(400000); // phase3 first block (oracle boundary) + auto ws = fx.make(); + auto tmpl = ws->get_current_work_template(); + ASSERT_TRUE(tmpl.is_object()); + ASSERT_TRUE(tmpl.contains("height")); + ASSERT_TRUE(tmpl.contains("coinbasevalue")); + // next_h = next_block_height() = base_height (empty chain) = 400000. + EXPECT_EQ(tmpl["height"].get(), 400000u); + // Zero embedded fees, no external GBT -> oracle subsidy at the boundary. + EXPECT_EQ(tmpl["coinbasevalue"].get(), 2434410000ULL); +} + // ── Embedded coinbasevalue: first production caller of subsidy_func ────────── // One pin on each side of every DGB reward-era boundary (p2pool-dgb-scrypt // oracle vectors, test_dgb_subsidy.cpp). From f7d5c7e436d816379c9aa1143bf646e16673d248 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Fri, 19 Jun 2026 14:23:53 +0000 Subject: [PATCH 2/2] dgb: emit GBT scaffold fields (version/curtime/mintime/transactions) in work template (Stage 4c) get_current_work_template() now surfaces the GBT-shaped fields the embedded path can derive TRUTHFULLY from current chain state, ahead of a full dgb::coin::TemplateBuilder port (M3 TODO), alongside the existing height + coinbasevalue: version BIP9 base | DGB Scrypt algo nibble. A DGB template MUST pin the Scrypt lane: the mining algo lives in 4 nVersion bits (coin/dgb_block_algo.hpp SSOT) and Scrypt is the all-zero codepoint (DGB_BLOCK_VERSION_SCRYPT == 0x0000). Any other nibble is a non-Scrypt algo that is accept-by-continuity / V37, never a template this V36 binary emits. curtime current wall-clock; GBT's suggested header nTime. mintime median_time_past()+1 (#209 accessor) = DGB Core ContextualCheckBlockHeader lower bound (nTime > MTP). Empty chain returns INT64_MIN (unconstrained) -> emitted as 0. transactions empty array: embedded mempool tx SELECTION is unwired, so no transactions are fabricated and fees stay 0 (consistent with the total_fees=0 coinbasevalue). Deliberately NOT emitted yet (need accessors the Scrypt-only HeaderSample does not carry; following Stage 4c/4d slices): previousblockhash tip block hash (HeaderSample stores no hash yet) bits next-block compact target off the DigiShield retarget window and the per-connection coinbase (gentx + ShareTracker ref_hash + PPLNS payout map) assembles in build_connection_coinbase() -- consensus-bearing output that surfaces for an operator tap, not in this field wire. Test: +WorkTemplateEmitsGbtScaffoldFields pins version == 0x20000000, mintime == 0 on an empty chain, curtime >= 0, transactions[] present-but-empty, and asserts previousblockhash/bits remain absent. dgb_work_source_test 15/15. Fenced to src/impl/dgb (2 files); no new gtest target, build.yml untouched. --- src/impl/dgb/stratum/work_source.cpp | 42 ++++++++++++++++++++++++++ src/impl/dgb/test/work_source_test.cpp | 35 +++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/impl/dgb/stratum/work_source.cpp b/src/impl/dgb/stratum/work_source.cpp index 94aa0fde2..d191dd13e 100644 --- a/src/impl/dgb/stratum/work_source.cpp +++ b/src/impl/dgb/stratum/work_source.cpp @@ -18,9 +18,13 @@ #include #include #include +#include #include +#include +#include + namespace dgb::stratum { DGBWorkSource::DGBWorkSource(c2pool::dgb::HeaderChain& chain, @@ -164,9 +168,47 @@ nlohmann::json DGBWorkSource::get_current_work_template() const const uint64_t coinbasevalue = coinbase_value(next_h, /*total_fees=*/0, /*gbt_coinbasevalue=*/std::nullopt); + // GBT-scaffold fields the embedded path can derive TRUTHFULLY from current + // chain state, ahead of a full dgb::coin::TemplateBuilder port (M3 TODO): + // version — BIP9 base | the DGB Scrypt algo nibble. A DGB block + // template MUST pin the Scrypt lane: the mining algo lives + // in 4 nVersion bits (coin/dgb_block_algo.hpp SSOT) and + // Scrypt is the all-zero codepoint (DGB_BLOCK_VERSION_SCRYPT + // == 0x0000); any other nibble is a non-Scrypt algo that is + // accept-by-continuity / V37 here, never a template we emit. + // curtime — current wall-clock; GBT's suggested header nTime. + // mintime — median_time_past()+1 (#209 accessor): DGB Core's + // ContextualCheckBlockHeader lower bound (nTime > MTP). An + // empty chain returns INT64_MIN (unconstrained) -> emit 0. + // transactions — empty array: embedded mempool tx SELECTION is not wired, + // so no transactions are fabricated and fees stay 0 + // (consistent with the total_fees=0 coinbasevalue above). + // + // Deliberately NOT emitted yet — they need accessors the Scrypt-only + // HeaderSample does not carry, and land in the following Stage 4c/4d slices: + // previousblockhash — the tip block hash (HeaderSample stores no hash yet) + // bits — the next-block compact target off the DigiShield + // retarget window + // and the per-connection coinbase (gentx + ShareTracker ref_hash + PPLNS + // payout map) assembles in build_connection_coinbase() — that output is + // consensus-bearing and surfaces for an operator tap, not in this field wire. + static constexpr uint32_t BIP9_BASE_VERSION = 0x20000000u; + const uint32_t version = + BIP9_BASE_VERSION | + static_cast(dgb::coin::DGB_BLOCK_VERSION_SCRYPT); + + const int64_t mtp = chain_.median_time_past(); + const int64_t mintime = (mtp == std::numeric_limits::min()) + ? 0 : (mtp + 1); + const int64_t curtime = static_cast(std::time(nullptr)); + nlohmann::json tmpl = nlohmann::json::object(); tmpl["height"] = next_h; tmpl["coinbasevalue"] = coinbasevalue; + tmpl["version"] = version; + tmpl["curtime"] = curtime; + tmpl["mintime"] = mintime; + tmpl["transactions"] = nlohmann::json::array(); return tmpl; } diff --git a/src/impl/dgb/test/work_source_test.cpp b/src/impl/dgb/test/work_source_test.cpp index 400ac20dd..024c0e617 100644 --- a/src/impl/dgb/test/work_source_test.cpp +++ b/src/impl/dgb/test/work_source_test.cpp @@ -196,6 +196,41 @@ TEST(DgbWorkSource, WorkTemplateEmitsHeightAndCoinbaseValueViaSsot) EXPECT_EQ(tmpl["coinbasevalue"].get(), 2434410000ULL); } +// Stage 4c GBT scaffold: alongside height + coinbasevalue, the work template +// now surfaces the GBT fields the embedded path can derive truthfully without +// a TemplateBuilder port -- version (Scrypt algo lane), curtime, mintime, and +// an (empty) transactions[]. previousblockhash + bits intentionally stay absent +// until HeaderSample carries the tip hash / next-target compact (later slices). +TEST(DgbWorkSource, WorkTemplateEmitsGbtScaffoldFields) +{ + Fixture fx; + fx.chain.set_base_height(400000); + auto ws = fx.make(); + auto tmpl = ws->get_current_work_template(); + ASSERT_TRUE(tmpl.is_object()); + + // version pins the DGB Scrypt lane: BIP9 base | algo nibble 0x0000. + ASSERT_TRUE(tmpl.contains("version")); + EXPECT_EQ(tmpl["version"].get(), 0x20000000u); + + // Empty chain -> median_time_past() == INT64_MIN -> mintime emitted as 0 + // (unconstrained), and curtime is a real wall-clock stamp (>= 0). + ASSERT_TRUE(tmpl.contains("mintime")); + EXPECT_EQ(tmpl["mintime"].get(), 0); + ASSERT_TRUE(tmpl.contains("curtime")); + EXPECT_GE(tmpl["curtime"].get(), 0); + + // No embedded tx selection yet -> transactions[] present but empty (no + // fabricated entries; consistent with the total_fees=0 coinbasevalue). + ASSERT_TRUE(tmpl.contains("transactions")); + EXPECT_TRUE(tmpl["transactions"].is_array()); + EXPECT_TRUE(tmpl["transactions"].empty()); + + // The two hash/difficulty fields are deliberately NOT emitted yet. + EXPECT_FALSE(tmpl.contains("previousblockhash")); + EXPECT_FALSE(tmpl.contains("bits")); +} + // ── Embedded coinbasevalue: first production caller of subsidy_func ────────── // One pin on each side of every DGB reward-era boundary (p2pool-dgb-scrypt // oracle vectors, test_dgb_subsidy.cpp).