Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 62 additions & 5 deletions src/impl/dgb/stratum/work_source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@
#include <impl/dgb/coin/header_chain.hpp>
#include <impl/dgb/coin/mempool.hpp>
#include <impl/dgb/coin/embedded_coinbase_value.hpp>
#include <impl/dgb/coin/dgb_block_algo.hpp>

#include <core/log.hpp>

#include <ctime>
#include <limits>

namespace dgb::stratum {

DGBWorkSource::DGBWorkSource(c2pool::dgb::HeaderChain& chain,
Expand Down Expand Up @@ -148,11 +152,64 @@ 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);

// 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<uint32_t>(dgb::coin::DGB_BLOCK_VERSION_SCRYPT);

const int64_t mtp = chain_.median_time_past();
const int64_t mintime = (mtp == std::numeric_limits<int64_t>::min())
? 0 : (mtp + 1);
const int64_t curtime = static_cast<int64_t>(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;
}

std::vector<std::string> DGBWorkSource::get_stratum_merkle_branches() const
Expand Down
59 changes: 58 additions & 1 deletion src/impl/dgb/test/work_source_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -174,6 +176,61 @@ 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<uint32_t>(), 400000u);
// Zero embedded fees, no external GBT -> oracle subsidy at the boundary.
EXPECT_EQ(tmpl["coinbasevalue"].get<uint64_t>(), 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<uint32_t>(), 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<int64_t>(), 0);
ASSERT_TRUE(tmpl.contains("curtime"));
EXPECT_GE(tmpl["curtime"].get<int64_t>(), 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).
Expand Down
Loading