From 9d0b13111addf2ac673c162627ec29d6b279fd9f Mon Sep 17 00:00:00 2001 From: frstrtr Date: Sun, 21 Jun 2026 22:40:40 +0000 Subject: [PATCH] =?UTF-8?q?dgb:=20re-land=20worker->mint=20adapter=20(run?= =?UTF-8?q?=5Floop=5Fmint.hpp)=20=E2=80=94=20Phase=20B=20consumer=20seam?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-lands the #294 mint_local_share_with_ratchet helper, which was lost from master in the #292/#293/#294 stacked squash-merge (no #294 merge commit reaches origin/master; the primitives dgb_select_mint_versions + create_local_share survive). This is the consumer adapter DGBWorkSource::MintShareFn binds to (work_source.hpp:197): on a ShareAccept submission it parses the 80-byte reconstructed header, asks the live AutoRatchet for the {mint, vote} version pair, and inserts the share via create_local_share, returning the minted hash (NULL on a malformed header / fail-closed). Authored against master createlocal_share signature (authoritative). Duck-typed on the inputs so the header does not pull the stratum work_source TU in. Pure adapter: tracker-insertion thread-safety is the caller bind point concern. KAT (dgb_share_test, no new exe): 80-byte header parse round-trip, short-buffer fail-closed, and adapter fail-closed + template instantiation drift-guard against create_local_share. 25/25 green. --- src/impl/dgb/run_loop_mint.hpp | 113 +++++++++++++++++++++++++++++++ src/impl/dgb/test/share_test.cpp | 72 ++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 src/impl/dgb/run_loop_mint.hpp diff --git a/src/impl/dgb/run_loop_mint.hpp b/src/impl/dgb/run_loop_mint.hpp new file mode 100644 index 000000000..d102be8d2 --- /dev/null +++ b/src/impl/dgb/run_loop_mint.hpp @@ -0,0 +1,113 @@ +#pragma once +// run_loop_mint.hpp — DGB Phase B: worker->mint adapter SSOT. +// +// This is the consumer adapter that DGBWorkSource::MintShareFn binds to in +// main_dgb.cpp (work_source.hpp:197 "binds this to mint_local_share_with_ratchet +// (run_loop_mint.hpp) -> create_local_share"). When mining_submit classifies a +// submission as ShareAccept (Scrypt PoW meets the SHARE target but NOT the full +// block target), try_mint_share() invokes this with the assembled MintShareInputs. +// It: +// 1. parses the 80-byte reconstructed block header into a SmallBlockHeaderType, +// 2. asks the live AutoRatchet for the {mint, vote} version pair via +// dgb_select_mint_versions (anchored at the current sharechain tip = the +// new share's parent), and +// 3. inserts the share into the tracker via create_local_share, returning the +// minted share hash (NULL uint256 on a malformed header / fail-closed). +// +// History: this helper was authored under #294 and lost from master in the +// #292/#293/#294 stacked squash-merge (no #294 merge commit reaches master; the +// underlying primitives dgb_select_mint_versions + create_local_share survive). +// Re-landed here to its documented contract against master's create_local_share +// signature, which is authoritative. +// +// PURE adapter: the helper takes references and performs ONE tracker insertion. +// Thread-safety of that insertion is the CALLER's concern — main_dgb binds this +// on the Stratum-submission thread and is responsible for the tracker lock. +// +// Duck-typed on the inputs (InputsT) so this header does NOT pull the stratum +// work_source TU in: any struct exposing { header_bytes, coinbase_bytes, subsidy, +// prev_share, merkle_branches, payout_script, segwit_active } binds — in +// production that is DGBWorkSource::MintShareInputs. + +#include +#include + +#include // PackStream, operator>> +#include // BaseScript +#include + +#include // SmallBlockHeaderType, BlockHeaderType +#include // create_local_share, MergedAddressEntry, StaleInfo, uint128 +#include // dgb_select_mint_versions, AutoRatchet + +namespace dgb { + +// Parse a canonical 80-byte block header (fixed 4-byte version + merkle_root) +// into the SmallBlockHeaderType (version|prev|time|bits|nonce) create_local_share +// consumes. Returns nullopt on a short/malformed buffer (fail-closed). Pure. +inline std::optional +parse_min_header_80(const std::vector& header_bytes) +{ + if (header_bytes.size() < 80) + return std::nullopt; + try { + PackStream ps(header_bytes); + coin::BlockHeaderType full; + ps >> full; + // SmallBlockHeaderType is the base subobject of BlockHeaderType; slicing + // off m_merkle_root yields exactly the small-header fields. + return static_cast(full); + } catch (const std::exception&) { + return std::nullopt; // truncated / unserialize failure -> fail-closed + } +} + +// The worker->mint adapter. Returns the minted share hash, or NULL uint256 if +// the header is malformed (fail-closed; the caller logs a no-record outcome). +template +inline uint256 mint_local_share_with_ratchet( + const InputsT& in, + TrackerT& tracker, + const core::CoinParams& params, + AutoRatchet& ratchet, + uint16_t donation = 50) +{ + auto min_header = parse_min_header_80(in.header_bytes); + if (!min_header) + return uint256(); // fail-closed: malformed 80-byte header + + BaseScript coinbase(in.coinbase_bytes); + + // The new share's parent is the current sharechain tip carried by the + // submission; the ratchet decides which {mint, vote} version pair to stamp. + const uint256 best_share = in.prev_share; + auto [mint_version, vote_version] = + dgb_select_mint_versions(ratchet, tracker, best_share); + + return create_local_share( + tracker, params, *min_header, coinbase, in.subsidy, in.prev_share, + in.merkle_branches, in.payout_script, + donation, + std::vector{}, // merged_addrs (DOGE aux: none in Scrypt-only) + StaleInfo::none, // stale_info + in.segwit_active, // segwit_active + std::string{}, // witness_commitment_hex + std::vector{}, // message_data + std::vector{}, // actual_coinbase_bytes + uint256(), // witness_root + 0u, // override_max_bits + 0u, // override_bits + 0u, // frozen_absheight + uint128(), // frozen_abswork + uint256(), // frozen_far_share_hash + 0u, // frozen_timestamp + uint256(), // frozen_merged_payout_hash + false, // has_frozen + std::vector{}, // frozen_merkle_branches + uint256(), // frozen_witness_root + std::vector{}, // frozen_merged_coinbase_info + static_cast(mint_version), // share_version (ratchet-selected) + static_cast(vote_version)); // desired_version (always target) +} + +} // namespace dgb diff --git a/src/impl/dgb/test/share_test.cpp b/src/impl/dgb/test/share_test.cpp index 4073bffbf..3077f8360 100644 --- a/src/impl/dgb/test/share_test.cpp +++ b/src/impl/dgb/test/share_test.cpp @@ -22,6 +22,7 @@ #include // #82 external-daemon RPC creds (digibyte.conf) #include // Phase B: mint-side share-version ratchet #include // Phase B: production wire-in (base_version=35) +#include // Phase B: worker->mint adapter (re-landed #294) #include // getpid (AutoRatchet KAT temp state file) #include @@ -465,3 +466,74 @@ TEST(DGB_share_test, AutoRatchetWireBootstrapMints35Votes36) EXPECT_EQ(vote, 36); // always vote target EXPECT_EQ(ar.state(), dgb::RatchetState::VOTING); } + +// ---------------------------------------------------------------------------- +// run_loop_mint.hpp — Phase B worker->mint adapter (re-landed #294 helper). +// The adapter maps a ShareAccept submission's raw header/coinbase bytes into +// create_local_share()'s arguments, stamping the ratchet-selected {mint, vote} +// version pair. These KATs pin the novel logic: the 80-byte header parse and the +// fail-closed guard. The version-selection delegation is pinned by the +// AutoRatchetWire* tests above; create_local_share's own insertion path is +// covered by its existing fixtures. +// ---------------------------------------------------------------------------- + +// Local stand-in for DGBWorkSource::MintShareInputs — the adapter is duck-typed +// on the inputs so this TU need not pull the stratum work_source in. +struct MintInputsKAT { + std::vector header_bytes; + std::vector coinbase_bytes; + uint64_t subsidy = 0; + uint256 prev_share; + std::vector merkle_branches; + std::vector payout_script; + bool segwit_active = false; +}; + +TEST(DGB_run_loop_mint, ParseMinHeader80RoundTrip) +{ + dgb::coin::BlockHeaderType hdr; + hdr.m_version = 0x20000000u; // version bits | SCRYPT marker shape + hdr.m_previous_block = uint256S( + "00000000000000000000000000000000000000000000000000000000deadbeef"); + hdr.m_merkle_root = uint256S( + "00000000000000000000000000000000000000000000000000000000cafef00d"); + hdr.m_timestamp = 0x5f5e1000u; + hdr.m_bits = 0x1e0ffff0u; + hdr.m_nonce = 0x12345678u; + + PackStream packed = pack(hdr); + std::vector bytes( + reinterpret_cast(packed.data()), + reinterpret_cast(packed.data()) + packed.size()); + ASSERT_EQ(bytes.size(), 80u); // 4+32+32+4+4+4 + + auto small = dgb::parse_min_header_80(bytes); + ASSERT_TRUE(small.has_value()); + EXPECT_EQ(small->m_version, 0x20000000u); + EXPECT_EQ(small->m_previous_block, hdr.m_previous_block); + EXPECT_EQ(small->m_timestamp, 0x5f5e1000u); + EXPECT_EQ(small->m_bits, 0x1e0ffff0u); + EXPECT_EQ(small->m_nonce, 0x12345678u); +} + +TEST(DGB_run_loop_mint, ParseMinHeaderShortFailsClosed) +{ + EXPECT_FALSE(dgb::parse_min_header_80({}).has_value()); + EXPECT_FALSE(dgb::parse_min_header_80(std::vector(79, 0)).has_value()); +} + +// A malformed (short) header returns NULL uint256 WITHOUT touching the tracker — +// and exercises the template instantiation of mint_local_share_with_ratchet +// against master's create_local_share signature (compile+link drift guard). +TEST(DGB_run_loop_mint, AdapterFailsClosedOnShortHeader) +{ + dgb::ShareTracker tracker; + auto params = dgb::make_coin_params(/*testnet=*/false); + auto ratchet = dgb::make_dgb_ratchet(); + + MintInputsKAT in; + in.header_bytes = std::vector(40, 0); // too short -> fail-closed + + uint256 h = dgb::mint_local_share_with_ratchet(in, tracker, params, ratchet); + EXPECT_TRUE(h.IsNull()); +}