From 41ab6666694a853b7ed998e4e99538b30983e74e Mon Sep 17 00:00:00 2001 From: frstrtr Date: Sun, 21 Jun 2026 11:23:40 +0000 Subject: [PATCH] dgb: stand up worker->mint share-accept seam on DGBWorkSource (Phase B) mining_submit (work_source.cpp:292) classifies a Scrypt submission three ways; the won-block outcome already dispatches via SubmitBlockFn, but the share-difficulty outcome -- "pow_hash <= share target -> record share in tracker" -- had no seam. This adds the producer half of the worker->mint run-loop standup, parallel to SubmitBlockFn: - MintShareInputs: raw-bytes found-share fields (header, coinbase, subsidy, prev_share/tip, merkle_branches, payout_script, segwit), decoupled from share/BlockType serialization like SubmitBlockFn. - MintShareFn + set_mint_share_fn(): the callback main_dgb.cpp binds to mint_local_share_with_ratchet (run_loop_mint.hpp) -> create_local_share, where the AutoRatchet chooses the {mint,vote} version pair. Mirrors the set_best_share_hash_fn idiom (empty until wired). - try_mint_share(): thread-safe dispatch (copies fn under lock); when unbound, logs loudly and returns a NULL hash -- no silent drop. No behavior change: the 4a mining_submit stub still rejects every submission and never reaches the seam (the classify branch lands in 4d). +3 KATs (forward+pass-through, empty-until-wired no-silent-drop, stub does-not-reach), dgb_work_source_test 20/20. --- src/impl/dgb/stratum/work_source.cpp | 22 +++++++ src/impl/dgb/stratum/work_source.hpp | 51 ++++++++++++++++ src/impl/dgb/test/work_source_test.cpp | 81 ++++++++++++++++++++++++++ 3 files changed, 154 insertions(+) diff --git a/src/impl/dgb/stratum/work_source.cpp b/src/impl/dgb/stratum/work_source.cpp index 52b0e1dfb..cba2fef49 100644 --- a/src/impl/dgb/stratum/work_source.cpp +++ b/src/impl/dgb/stratum/work_source.cpp @@ -333,4 +333,26 @@ void DGBWorkSource::set_best_share_hash_fn(std::function fn) best_share_hash_fn_ = std::move(fn); } +void DGBWorkSource::set_mint_share_fn(MintShareFn fn) +{ + std::lock_guard lk(mint_share_mutex_); + mint_share_fn_ = std::move(fn); +} + +uint256 DGBWorkSource::try_mint_share(const MintShareInputs& in) const +{ + MintShareFn fn; + { + std::lock_guard lk(mint_share_mutex_); + fn = mint_share_fn_; // copy so a concurrent set cannot tear it out mid-call + } + if (!fn) { + LOG_WARNING << "[DGB-STRATUM] share met share-target but no mint callback " + "wired -- share NOT recorded (set_mint_share_fn unbound). " + "No silent drop: logged."; + return uint256{}; + } + return fn(in); +} + } // namespace dgb::stratum diff --git a/src/impl/dgb/stratum/work_source.hpp b/src/impl/dgb/stratum/work_source.hpp index a4eff3588..32976d81a 100644 --- a/src/impl/dgb/stratum/work_source.hpp +++ b/src/impl/dgb/stratum/work_source.hpp @@ -81,6 +81,35 @@ class DGBWorkSource : public core::stratum::IWorkSource using SubmitBlockFn = std::function& block_bytes, uint32_t height)>; + /// Inputs the worker->mint sharechain-accept path hands to the run-loop: + /// the assembled fields of a submission whose **Scrypt** PoW met the SHARE + /// target (but NOT the full block target). Raw-bytes form (header + + /// coinbase) keeps DGBWorkSource decoupled from the share/BlockType + /// serialization details, exactly like SubmitBlockFn -- main_dgb.cpp parses + /// these into create_local_share()'s arguments. + struct MintShareInputs { + std::vector header_bytes; ///< 80-byte reconstructed block header + std::vector coinbase_bytes; ///< p2pool coinbase scriptSig (BIP34 height + marker) + uint64_t subsidy = 0; ///< coinbasevalue (block reward) + uint256 prev_share; ///< current sharechain tip = the new share's parent + std::vector merkle_branches; ///< coinbase txid -> merkle root branches + std::vector payout_script; ///< finder's scriptPubKey + bool segwit_active = false; + }; + + /// Callback invoked when `mining_submit` validates a submission whose + /// **Scrypt** PoW meets the SHARE target but NOT the full block target -- + /// the worker->mint sharechain-accept branch (the mining_submit classify + /// step "pow_hash <= share target -> record share in tracker"). main_dgb.cpp + /// binds this to mint_local_share_with_ratchet (run_loop_mint.hpp, #294) + /// -> create_local_share: the WorkSource hands out the found-share fields, + /// the run-loop asks the AutoRatchet for the {mint, vote} version pair and + /// inserts the share. Returns the minted share hash (NULL uint256 on + /// failure). Parallel to SubmitBlockFn but for the share-difficulty (not + /// block-difficulty) outcome -- the two non-reject classes of mining_submit. + using MintShareFn = + std::function; + DGBWorkSource(c2pool::dgb::HeaderChain& chain, dgb::coin::Mempool& mempool, bool is_testnet, @@ -163,6 +192,22 @@ class DGBWorkSource : public core::stratum::IWorkSource /// is constructed. void set_best_share_hash_fn(std::function fn); + /// Wire the worker->mint sharechain-accept dispatch. Called once at startup + /// from main_dgb.cpp, bound to mint_local_share_with_ratchet (#294) -> + /// create_local_share. Empty until wired (mirrors set_best_share_hash_fn); + /// while empty, try_mint_share() no-ops with a loud log rather than + /// silently dropping an accepted share. + void set_mint_share_fn(MintShareFn fn); + + /// Dispatch one share-difficulty submission to the bound mint callback. + /// The stage-4d mining_submit classify branch calls this on the + /// "pow_hash <= share target" outcome. Returns the minted share hash, or a + /// NULL uint256 when no mint callback is wired (logged, never crashes) or + /// when the callback itself returns null. Thread-safe: copies the callback + /// under lock before invoking so a concurrent set_mint_share_fn() cannot + /// tear it out mid-call. + uint256 try_mint_share(const MintShareInputs& in) const; + /// Embedded-path coinbasevalue for the template builder (Stage 4c). /// Routes through dgb::coin::resolve_coinbase_value: when the external /// digibyted GBT supplied a coinbasevalue it is returned verbatim (the @@ -202,6 +247,12 @@ class DGBWorkSource : public core::stratum::IWorkSource mutable std::mutex best_share_mutex_; std::function best_share_hash_fn_; + // Worker->mint sharechain-accept callback (to mint_local_share_with_ratchet + // -> create_local_share, wired in main_dgb.cpp). Empty until set; the + // mining_submit classify branch no-ops the mint when unbound. + mutable std::mutex mint_share_mutex_; + MintShareFn mint_share_fn_; + // Template cache (filled lazily; invalidated when work_generation_ bumps) // Stage 4c populates these. mutable std::mutex template_mutex_; diff --git a/src/impl/dgb/test/work_source_test.cpp b/src/impl/dgb/test/work_source_test.cpp index 03605f6dc..102b5a637 100644 --- a/src/impl/dgb/test/work_source_test.cpp +++ b/src/impl/dgb/test/work_source_test.cpp @@ -112,6 +112,87 @@ TEST(DgbWorkSource, BestShareHashFnEmptyUntilWired) EXPECT_EQ(fn(), uint256::ZERO); } +// ── Worker->mint sharechain-accept seam (set_mint_share_fn / try_mint_share) ── +// The producer half of the worker->mint run-loop standup: DGBWorkSource hands a +// share-difficulty submission's found-share fields to a callback main_dgb.cpp +// binds to mint_local_share_with_ratchet (#294) -> create_local_share. These +// pin the seam contract before the stage-4d classify branch reaches it. + +TEST(DgbWorkSource, MintShareFnEmptyUntilWiredReturnsNullNoSilentDrop) +{ + Fixture fx; + auto ws = fx.make(); + // Unbound: try_mint_share must NOT crash and must return a NULL hash + // (the accepted share is logged, never silently dispatched into a null fn). + dgb::stratum::DGBWorkSource::MintShareInputs in; + in.subsidy = 500000000; + EXPECT_EQ(ws->try_mint_share(in), uint256::ZERO); +} + +TEST(DgbWorkSource, MintShareFnForwardsInputsAndReturnsHash) +{ + Fixture fx; + auto ws = fx.make(); + + // Spy mint callback: capture the inputs (forward) and return a sentinel + // hash (pass-through back to the classify branch). + dgb::stratum::DGBWorkSource::MintShareInputs seen; + bool called = false; + uint256 sentinel; sentinel.SetHex( + "00000000000000000000000000000000000000000000000000000000cafe5a7e"); + + ws->set_mint_share_fn( + [&](const dgb::stratum::DGBWorkSource::MintShareInputs& got) -> uint256 { + called = true; + seen = got; + return sentinel; + }); + + dgb::stratum::DGBWorkSource::MintShareInputs in; + in.header_bytes = std::vector(80, 0xab); + in.coinbase_bytes = {0x03, 0x01, 0x02, 0x03}; + in.subsidy = 0x1234567890ULL; + in.prev_share.SetHex( + "00000000000000000000000000000000000000000000000000000000000000aa"); + in.merkle_branches.push_back(in.prev_share); + in.payout_script = {0x76, 0xa9}; + in.segwit_active = true; + + uint256 minted = ws->try_mint_share(in); + + EXPECT_TRUE(called); + EXPECT_EQ(minted, sentinel); // minted hash flows back verbatim + EXPECT_EQ(seen.header_bytes.size(), 80u); // inputs forwarded, not dropped + EXPECT_EQ(seen.coinbase_bytes.size(), 4u); + EXPECT_EQ(seen.subsidy, 0x1234567890ULL); + EXPECT_EQ(seen.prev_share, in.prev_share); + ASSERT_EQ(seen.merkle_branches.size(), 1u); + EXPECT_EQ(seen.merkle_branches[0], in.prev_share); + EXPECT_EQ(seen.payout_script.size(), 2u); + EXPECT_TRUE(seen.segwit_active); +} + +// No behavior change this slice: the seam is stood up but the 4a mining_submit +// stub still rejects every submission and must NOT reach the mint callback +// (the classify branch that calls try_mint_share lands in stage 4d). +TEST(DgbWorkSource, MiningSubmitStubDoesNotInvokeMintFnYet) +{ + Fixture fx; + auto ws = fx.make(); + bool mint_called = false; + ws->set_mint_share_fn( + [&](const dgb::stratum::DGBWorkSource::MintShareInputs&) -> uint256 { + mint_called = true; + return uint256{}; + }); + auto result = ws->mining_submit( + "DGBaddr.worker1", "job-0", "en1", "en2", "ntime", "nonce", "rid-0", + /*merged_addresses=*/{}, /*job=*/nullptr); + ASSERT_TRUE(result.is_array()); + EXPECT_FALSE(result[0].get()); // 4a stub still rejects + EXPECT_FALSE(mint_called); // seam wired but NOT yet reached +} + TEST(DgbWorkSource, WorkerRegistryRoundTrip) { Fixture fx;