diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e9993d14e..3ad2b4f7d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,7 +69,7 @@ jobs: test_address_resolution test_compute_share_target \ test_utxo test_dgb_subsidy dgb_share_test dgb_block_assembly_test \ dgb_gentx_coinbase_test nmc_auxpow_merkle_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \ - dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_gentx_unpack_test \ + dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_gentx_unpack_test dgb_work_source_test \ rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \ v37_test \ -j$(nproc) @@ -201,7 +201,7 @@ jobs: test_address_resolution test_compute_share_target \ test_utxo test_dgb_subsidy dgb_share_test dgb_block_assembly_test \ dgb_gentx_coinbase_test nmc_auxpow_merkle_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \ - dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_gentx_unpack_test \ + dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_gentx_unpack_test dgb_work_source_test \ rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \ test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \ v37_test \ diff --git a/src/impl/dgb/CMakeLists.txt b/src/impl/dgb/CMakeLists.txt index 2417c7f96..e284a09ba 100644 --- a/src/impl/dgb/CMakeLists.txt +++ b/src/impl/dgb/CMakeLists.txt @@ -19,6 +19,7 @@ if(COIN_DGB) message(STATUS "c2pool: DGB Scrypt-only coin module enabled") add_subdirectory(coin) + add_subdirectory(stratum) # dgb_stratum: IWorkSource (#82 miner-facing path) # add_subdirectory(daemon) add_subdirectory(test) diff --git a/src/impl/dgb/stratum/CMakeLists.txt b/src/impl/dgb/stratum/CMakeLists.txt new file mode 100644 index 000000000..e0c5e0eab --- /dev/null +++ b/src/impl/dgb/stratum/CMakeLists.txt @@ -0,0 +1,22 @@ +# dgb::stratum module — concrete IWorkSource implementation for c2pool-dgb. +# Bridges core::StratumServer (coin-agnostic protocol layer) to DGB's +# Scrypt template builder + sharechain + the #82 dual-path won-block +# broadcaster. Mirrors src/impl/btc/stratum/CMakeLists.txt. Stage 4a +# skeleton (work-gen/submit stubbed; 4b/4c/4d fill them in). + +add_library(dgb_stratum + work_source.hpp work_source.cpp +) + +target_link_libraries(dgb_stratum + core + dgb_coin + btclibs + nlohmann_json::nlohmann_json + ${Boost_LIBRARIES} +) + +# Include parent so #include resolves +target_include_directories(dgb_stratum PUBLIC + ${CMAKE_SOURCE_DIR}/src +) diff --git a/src/impl/dgb/stratum/work_source.cpp b/src/impl/dgb/stratum/work_source.cpp new file mode 100644 index 000000000..4405c4d77 --- /dev/null +++ b/src/impl/dgb/stratum/work_source.cpp @@ -0,0 +1,225 @@ +// dgb::stratum::DGBWorkSource — Stage 4a skeleton. +// +// All IWorkSource methods are stubbed to safe defaults. Subsequent +// sub-stages flesh them out (mirroring btc::stratum::BTCWorkSource's own +// 4b/4c/4d progression): +// Stage 4b: read-only getters (config, prevhash, generation, workers) +// Stage 4c: Scrypt work generation (template, merkle branches, coinbase) +// Stage 4d: mining_submit hot path (Scrypt PoW classify + won-block dispatch) +// +// The skeleton is intentionally non-functional but compiles, instantiates, +// and lets us validate the StratumServer wiring in main_dgb.cpp end-to-end +// before implementing the substantive logic. DGB validates Scrypt blocks +// only (V36 / project_v36_dgb_scrypt_only); the other four DGB algos are +// accept-by-continuity / V37 and never reach this work source. + +#include + +#include +#include + +#include + +namespace dgb::stratum { + +DGBWorkSource::DGBWorkSource(c2pool::dgb::HeaderChain& chain, + dgb::coin::Mempool& mempool, + bool is_testnet, + SubmitBlockFn submit_fn, + core::stratum::StratumConfig config) + : chain_(chain) + , mempool_(mempool) + , is_testnet_(is_testnet) + , submit_block_fn_(std::move(submit_fn)) + , config_(std::move(config)) +{ + LOG_INFO << "[DGB-STRATUM] DGBWorkSource constructed" + << " (testnet=" << is_testnet_ + << " min_diff=" << config_.min_difficulty + << " max_diff=" << config_.max_difficulty + << " target_time=" << config_.target_time + << "s vardiff=" << (config_.vardiff_enabled ? "on" : "off") << ")"; +} + +DGBWorkSource::~DGBWorkSource() = default; + +// ───────────────────────────────────────────────────────────────────────────── +// IWorkSource: config + read-only state — Stage 4b will fill these in. +// ───────────────────────────────────────────────────────────────────────────── + +const core::stratum::StratumConfig& DGBWorkSource::get_stratum_config() const +{ + return config_; +} + +std::function DGBWorkSource::get_best_share_hash_fn() const +{ + std::lock_guard lk(best_share_mutex_); + return best_share_hash_fn_; // empty function until set_best_share_hash_fn() called +} + +std::string DGBWorkSource::get_current_gbt_prevhash() const +{ + // Stage 4b: read chain_.tip() and return BE-display-hex form. + return {}; +} + +uint64_t DGBWorkSource::get_work_generation() const +{ + return work_generation_.load(std::memory_order_relaxed); +} + +bool DGBWorkSource::has_merged_chain(uint32_t /*chain_id*/) const +{ + // DGB V36 default build: standalone Scrypt parent, no merged mining. + // The DGB-as-DOGE-parent dual-parent path (-DAUX_DOGE=ON) is a V36 + // STRETCH, parked behind the shared DOGE-aux settle — not wired here. + return false; +} + +// ───────────────────────────────────────────────────────────────────────────── +// IWorkSource: per-connection bookkeeping — minimal but real now. +// ───────────────────────────────────────────────────────────────────────────── + +void DGBWorkSource::register_stratum_worker(const std::string& session_id, + const core::stratum::WorkerInfo& info) +{ + std::lock_guard lk(workers_mutex_); + workers_[session_id] = info; + LOG_INFO << "[DGB-STRATUM] worker registered: session=" << session_id + << " user=" << info.username + << " worker=" << info.worker_name + << " endpoint=" << info.remote_endpoint; +} + +void DGBWorkSource::unregister_stratum_worker(const std::string& session_id) +{ + std::lock_guard lk(workers_mutex_); + auto it = workers_.find(session_id); + if (it != workers_.end()) { + LOG_INFO << "[DGB-STRATUM] worker unregistered: session=" << session_id + << " user=" << it->second.username + << " accepted=" << it->second.accepted + << " rejected=" << it->second.rejected + << " stale=" << it->second.stale; + workers_.erase(it); + } +} + +void DGBWorkSource::update_stratum_worker(const std::string& session_id, + double hashrate, double dead_hashrate, + double difficulty, + uint64_t accepted, uint64_t rejected, uint64_t stale) +{ + std::lock_guard lk(workers_mutex_); + auto it = workers_.find(session_id); + if (it == workers_.end()) return; + it->second.hashrate = hashrate; + it->second.dead_hashrate = dead_hashrate; + it->second.difficulty = difficulty; + it->second.accepted = accepted; + it->second.rejected = rejected; + it->second.stale = stale; +} + +// ───────────────────────────────────────────────────────────────────────────── +// IWorkSource: work generation — Stage 4c will fill these in. +// ───────────────────────────────────────────────────────────────────────────── + +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(); +} + +std::vector DGBWorkSource::get_stratum_merkle_branches() const +{ + // Stage 4c: return cached branches from last template build. + return {}; +} + +std::pair DGBWorkSource::get_coinbase_parts() const +{ + // Stage 4c: return cached coinb1/coinb2 (extranonce slot between them). + return { {}, {} }; +} + +core::stratum::CoinbaseResult DGBWorkSource::build_connection_coinbase( + const uint256& /*prev_share_hash*/, + const std::string& /*extranonce1_hex*/, + const std::vector& /*payout_script*/, + const std::vector>>& /*merged_addrs*/) const +{ + // Stage 4c: build per-connection coinbase using the SSOT gentx assembler + // (coin/gentx_coinbase.hpp) + ShareTracker ref_hash + PPLNS payout map. + // For now return an empty result; sessions calling this will get an empty + // job and skip pushing work, which is safe but non-functional. + return {}; +} + +// ───────────────────────────────────────────────────────────────────────────── +// IWorkSource: share submission — Stage 4d (the hot path). +// ───────────────────────────────────────────────────────────────────────────── + +nlohmann::json DGBWorkSource::mining_submit( + const std::string& username, const std::string& job_id, + const std::string& /*extranonce1*/, const std::string& /*extranonce2*/, + const std::string& /*ntime*/, const std::string& /*nonce*/, + const std::string& /*request_id*/, + const std::map>& /*merged_addresses*/, + const core::stratum::JobSnapshot* /*job*/) +{ + // Stage 4d will: + // 1. Reconstruct the 80-byte block header from JobSnapshot + miner inputs + // 2. Scrypt(header) → pow_hash (scrypt_1024_1_1_256, the DGB-Scrypt algo) + // 3. Decode share target from job->share_bits (compact) + // 4. Decode block target from job->block_nbits (compact) + // 5. Classify: + // pow_hash <= block target → submit_block_fn_(full_block, height) + // pow_hash <= share target → record share in tracker (sharechain accept) + // otherwise → reject as low-difficulty + // 6. Update worker stats accordingly + // + // For now: log + reject everything as low-difficulty. Miners will see + // stratum errors but the binary won't crash. + LOG_WARNING << "[DGB-STRATUM] mining_submit not implemented (stage 4d): " + << "user=" << username << " job=" << job_id + << " — submission rejected as low-difficulty"; + + return nlohmann::json::array({ + false, + nlohmann::json::array({23, "Low difficulty share (stratum stub: stage 4d not implemented)", nullptr}) + }); +} + +double DGBWorkSource::compute_share_difficulty( + const std::string& /*coinb1*/, const std::string& /*coinb2*/, + const std::string& /*extranonce1*/, const std::string& /*extranonce2*/, + const std::string& /*ntime*/, const std::string& /*nonce*/, + uint32_t /*version*/, const std::string& /*prevhash_hex*/, + const std::string& /*nbits_hex*/, + const std::vector& /*merkle_branches*/) const +{ + // Stage 4b/4c: reconstruct the 80-byte header from (coinb1+en1+en2+coinb2) + // merkle-rooted with merkle_branches, then scrypt_1024_1_1_256(header) and + // return diff1 / pow_hash. Until then the coin-agnostic StratumServer must + // not credit pseudoshares it cannot score, so we return 0.0 — the documented + // parse-error / not-yet sentinel that the vardiff gate already treats as a + // hard reject (no garbage difficulty leaks into the rate monitor). + return 0.0; +} + +// ───────────────────────────────────────────────────────────────────────────── +// DGB-specific control surface +// ───────────────────────────────────────────────────────────────────────────── + +void DGBWorkSource::set_best_share_hash_fn(std::function fn) +{ + std::lock_guard lk(best_share_mutex_); + best_share_hash_fn_ = std::move(fn); +} + +} // namespace dgb::stratum diff --git a/src/impl/dgb/stratum/work_source.hpp b/src/impl/dgb/stratum/work_source.hpp new file mode 100644 index 000000000..48276ce3a --- /dev/null +++ b/src/impl/dgb/stratum/work_source.hpp @@ -0,0 +1,194 @@ +#pragma once + +// dgb::stratum::DGBWorkSource — concrete `core::stratum::IWorkSource` +// implementation for c2pool-dgb (DigiByte Scrypt-only, V36). +// +// Responsibility: bridge the coin-agnostic `core::StratumServer` (TCP, +// JSON-RPC, sessions, vardiff, rate monitor) to DGB-specific work +// generation + share validation. Produces stratum jobs from the local +// header chain + mempool via `dgb::coin::TemplateBuilder` (Scrypt +// templates), validates submitted shares with **Scrypt** PoW (NOT +// SHA256d — DGB-Scrypt is the only algo this V36 binary mines; the other +// four DGB algos are accept-by-continuity / V37), and dispatches +// mainnet-hit blocks to the dual-path won-block broadcaster wired in +// main_dgb.cpp (coin/block_broadcast.hpp: P2P relay primary + submitblock +// RPC fallback — rpc.cpp:387 submit_block_hex, already real, NOT a stub). +// +// Lifetime: holds non-owning references to `HeaderChain` and `Mempool` +// — main_dgb.cpp owns those for the process lifetime, DGBWorkSource is +// constructed after them and destroyed before. The submit-block callback +// captures whatever upstream state it needs (typically a coin_node ref + +// the won-block dispatcher from PRs #166/#167). +// +// Threading: `core::StratumServer` runs on its own io_context; methods +// here may be invoked from any thread serviced by it. Internal +// synchronisation: +// - `work_generation_`, `share_bits_`, `share_max_bits_` are atomics +// - `workers_` is guarded by `workers_mutex_` +// - the template cache is guarded by `template_mutex_` +// - `chain_` and `mempool_` have their own internal locking +// +// What's deliberately MVP-incomplete in this commit (Stage 4a skeleton — +// mirrors btc::stratum::BTCWorkSource's own 4a landing @541c735f): +// - All work-generation / submit methods return defaults or empty +// results. Subsequent sub-stages (4b/4c/4d) implement the read-only +// getters, the Scrypt work assembly, and the share-validation hot path. +// - No vardiff feedback loop yet — `update_stratum_worker` records but +// doesn't yet drive set_difficulty back to sessions. +// The skeleton is intentionally non-functional but compiles, instantiates, +// and lets us validate the StratumServer wiring in main_dgb.cpp end-to-end +// (the next stacked slice) before implementing the substantive logic. + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Forward declarations — heavy headers live in the .cpp. Note the split +// namespaces: HeaderChain lives in c2pool::dgb (header_chain.hpp), Mempool +// in dgb::coin (mempool.hpp). +namespace c2pool::dgb { +class HeaderChain; +} // namespace c2pool::dgb +namespace dgb::coin { +class Mempool; +} // namespace dgb::coin + +namespace dgb::stratum { + +class DGBWorkSource : public core::stratum::IWorkSource +{ +public: + /// Callback invoked when `mining_submit` validates a submission whose + /// **Scrypt** PoW meets DGB mainnet difficulty. main_dgb.cpp wires this + /// to the dual-path won-block broadcaster (coin/block_broadcast.hpp): + /// P2P relay primary + submitblock RPC fallback. Raw-bytes form keeps + /// DGBWorkSource decoupled from the BlockType serialization details. + /// Returns true iff the won block reached at least one network sink + /// (P2P relay or submitblock RPC fallback). A false return means it + /// reached NEITHER and the won-block path must log a loud error + /// (no silent drop — the dual-path broadcaster gate on #82). + using SubmitBlockFn = std::function& block_bytes, + uint32_t height)>; + + DGBWorkSource(c2pool::dgb::HeaderChain& chain, + dgb::coin::Mempool& mempool, + bool is_testnet, + SubmitBlockFn submit_fn, + core::stratum::StratumConfig config = {}); + ~DGBWorkSource() override; + + DGBWorkSource(const DGBWorkSource&) = delete; + DGBWorkSource& operator=(const DGBWorkSource&) = delete; + + // ── IWorkSource: config + read-only state ──────────────────────────── + const core::stratum::StratumConfig& get_stratum_config() const override; + std::function get_best_share_hash_fn() const override; + std::string get_current_gbt_prevhash() const override; + uint64_t get_work_generation() const override; + bool has_merged_chain(uint32_t chain_id) const override; + + // ── IWorkSource: per-connection bookkeeping ────────────────────────── + void register_stratum_worker(const std::string& session_id, + const core::stratum::WorkerInfo& info) override; + void unregister_stratum_worker(const std::string& session_id) override; + void update_stratum_worker(const std::string& session_id, + double hashrate, double dead_hashrate, double difficulty, + uint64_t accepted, uint64_t rejected, uint64_t stale) override; + + // ── IWorkSource: work generation ───────────────────────────────────── + nlohmann::json get_current_work_template() const override; + std::vector get_stratum_merkle_branches() const override; + std::pair get_coinbase_parts() const override; + core::stratum::CoinbaseResult build_connection_coinbase( + const uint256& prev_share_hash, + const std::string& extranonce1_hex, + const std::vector& payout_script, + const std::vector>>& merged_addrs) const override; + + // ── IWorkSource: share submission ──────────────────────────────────── + nlohmann::json mining_submit( + const std::string& username, const std::string& job_id, + const std::string& extranonce1, const std::string& extranonce2, + const std::string& ntime, const std::string& nonce, + const std::string& request_id, + const std::map>& merged_addresses, + const core::stratum::JobSnapshot* job) override; + + /// Per-coin PoW-hash difficulty for a pseudoshare. DGB-Scrypt = + /// scrypt_1024_1_1_256 over the reconstructed 80-byte header. Stage + /// 4b/4c implements the assembly + scrypt call; the 4a skeleton + /// returns 0.0 (the documented parse-error / not-yet default). + double compute_share_difficulty( + const std::string& coinb1, const std::string& coinb2, + const std::string& extranonce1, const std::string& extranonce2, + const std::string& ntime, const std::string& nonce, + uint32_t version, const std::string& prevhash_hex, + const std::string& nbits_hex, + const std::vector& merkle_branches) const override; + + // ── IWorkSource: atomic state ──────────────────────────────────────── + uint32_t get_share_bits() const override { return share_bits_.load(); } + uint32_t get_share_max_bits() const override { return share_max_bits_.load(); } + + // ── DGB-specific control surface (called from main_dgb.cpp) ────────── + + /// Increment work_generation. Called when the DGB tip moves (new + /// headers) or when the sharechain tip moves. Triggers stratum sessions + /// to re-push work on their next heartbeat. + void bump_work_generation() { work_generation_.fetch_add(1, std::memory_order_relaxed); } + + /// Set the current share-target bits (compact-target encoding). + /// `max_bits` is the easiest the share target can be. Both atomically + /// visible to stratum sessions. + void set_share_target(uint32_t bits, uint32_t max_bits) + { + share_bits_.store(bits, std::memory_order_relaxed); + share_max_bits_.store(max_bits, std::memory_order_relaxed); + } + + /// Wire the share-tracker accessor that returns the current best-share + /// hash. Called once at startup from main_dgb.cpp after ShareTracker + /// is constructed. + void set_best_share_hash_fn(std::function fn); + +private: + // External dependencies (non-owning references) + c2pool::dgb::HeaderChain& chain_; + dgb::coin::Mempool& mempool_; + const bool is_testnet_; + + // Submission dispatch + SubmitBlockFn submit_block_fn_; + + // Config (held by value; const after construction in MVP) + core::stratum::StratumConfig config_; + + // Atomic state + std::atomic work_generation_{0}; + std::atomic share_bits_{0}; + std::atomic share_max_bits_{0}; + + // Worker registry (per-connection metadata) + mutable std::mutex workers_mutex_; + std::map workers_; + + // Best-share callback (from ShareTracker) + mutable std::mutex best_share_mutex_; + std::function best_share_hash_fn_; + + // Template cache (filled lazily; invalidated when work_generation_ bumps) + // Stage 4c populates these. + mutable std::mutex template_mutex_; + // ... cache fields land here in stage 4c +}; + +} // namespace dgb::stratum diff --git a/src/impl/dgb/test/CMakeLists.txt b/src/impl/dgb/test/CMakeLists.txt index cd711f609..0cadb66dc 100644 --- a/src/impl/dgb/test/CMakeLists.txt +++ b/src/impl/dgb/test/CMakeLists.txt @@ -128,6 +128,19 @@ if (BUILD_TESTING AND GTest_FOUND) c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage dgb_coin pool sharechain) gtest_add_tests(dgb_gentx_unpack_test "" AUTO) + # --- #82 miner-facing path: DGBWorkSource (IWorkSource) Stage 4a skeleton - + # Constructs dgb::stratum::DGBWorkSource over the live coin deps and pins + # the IWorkSource contract + real-now surface (config/atomics/workers). + # Links dgb_stratum + the same proven SCC set as the sibling dgb tests. + # MUST also be in the build.yml --target allowlist (#143 NOT_BUILT trap). + add_executable(dgb_work_source_test work_source_test.cpp) + target_link_libraries(dgb_work_source_test PRIVATE + GTest::gtest_main GTest::gtest + core dgb dgb_stratum + c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage + dgb_coin pool sharechain + nlohmann_json::nlohmann_json) + gtest_add_tests(dgb_work_source_test "" AUTO) foreach(dgb_guard rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test) add_executable(${dgb_guard} ${dgb_guard}.cpp) diff --git a/src/impl/dgb/test/work_source_test.cpp b/src/impl/dgb/test/work_source_test.cpp new file mode 100644 index 000000000..5f61bb9ab --- /dev/null +++ b/src/impl/dgb/test/work_source_test.cpp @@ -0,0 +1,169 @@ +// dgb::stratum::DGBWorkSource — Stage 4a skeleton construction + contract test. +// +// Proves the work source instantiates against the live coin types +// (c2pool::dgb::HeaderChain + dgb::coin::Mempool), satisfies the full +// core::stratum::IWorkSource pure-virtual contract (so core::StratumServer +// can hold it via shared_ptr in the next slice), and that its +// real-now surface (config defaults, atomic work-generation, share-target +// atomics, worker registry, best-share callback) behaves. The stubbed +// work-generation / submit methods are asserted to return their documented +// safe defaults so a regression that accidentally "implements" them with +// garbage is caught. +// +// MUST appear in BOTH this ctest registration AND the build.yml --target +// allowlist, or it becomes a #143-style NOT_BUILT sentinel that reds master. + +#include +#include +#include + +#include + +#include + +#include + +namespace { + +// Construct a DGBWorkSource over default-constructed coin deps. The submit +// callback records whether it was invoked (it must NOT be in the 4a skeleton). +struct Fixture { + c2pool::dgb::HeaderChain chain; + dgb::coin::Mempool mempool; + bool submit_called = false; + + std::unique_ptr make() + { + auto fn = [this](const std::vector&, uint32_t) -> bool { + submit_called = true; + return false; + }; + return std::make_unique( + chain, mempool, /*is_testnet=*/false, fn); + } +}; + +TEST(DgbWorkSource, ConstructsAndSatisfiesIWorkSourceContract) +{ + Fixture fx; + auto ws = fx.make(); + // Usable through the abstract interface core::StratumServer holds. + core::stratum::IWorkSource* iface = ws.get(); + ASSERT_NE(iface, nullptr); +} + +TEST(DgbWorkSource, ConfigDefaultsMatchStratumConfig) +{ + Fixture fx; + auto ws = fx.make(); + const auto& cfg = ws->get_stratum_config(); + EXPECT_DOUBLE_EQ(cfg.min_difficulty, 0.0005); + EXPECT_DOUBLE_EQ(cfg.max_difficulty, 65536.0); + EXPECT_DOUBLE_EQ(cfg.target_time, 3.0); + EXPECT_TRUE(cfg.vardiff_enabled); +} + +TEST(DgbWorkSource, WorkGenerationStartsZeroAndBumps) +{ + Fixture fx; + auto ws = fx.make(); + EXPECT_EQ(ws->get_work_generation(), 0u); + ws->bump_work_generation(); + ws->bump_work_generation(); + EXPECT_EQ(ws->get_work_generation(), 2u); +} + +TEST(DgbWorkSource, ShareTargetAtomicsRoundTrip) +{ + Fixture fx; + auto ws = fx.make(); + EXPECT_EQ(ws->get_share_bits(), 0u); + EXPECT_EQ(ws->get_share_max_bits(), 0u); + ws->set_share_target(0x1d00ffff, 0x1e0fffff); + EXPECT_EQ(ws->get_share_bits(), 0x1d00ffffu); + EXPECT_EQ(ws->get_share_max_bits(), 0x1e0fffffu); +} + +TEST(DgbWorkSource, NoMergedChainInDefaultBuild) +{ + Fixture fx; + auto ws = fx.make(); + // DGB V36 default build is a standalone Scrypt parent (no merged mining; + // -DAUX_DOGE dual-parent is a parked STRETCH). + EXPECT_FALSE(ws->has_merged_chain(0x0001)); +} + +TEST(DgbWorkSource, BestShareHashFnEmptyUntilWired) +{ + Fixture fx; + auto ws = fx.make(); + EXPECT_FALSE(static_cast(ws->get_best_share_hash_fn())); + ws->set_best_share_hash_fn([]() { return uint256::ZERO; }); + auto fn = ws->get_best_share_hash_fn(); + ASSERT_TRUE(static_cast(fn)); + EXPECT_EQ(fn(), uint256::ZERO); +} + +TEST(DgbWorkSource, WorkerRegistryRoundTrip) +{ + Fixture fx; + auto ws = fx.make(); + core::stratum::WorkerInfo info; + info.username = "DGBaddr.worker1"; + info.worker_name = "worker1"; + ws->register_stratum_worker("sess-1", info); + ws->update_stratum_worker("sess-1", /*hashrate=*/1.0e9, /*dead=*/0.0, + /*difficulty=*/16.0, /*accepted=*/3, /*rejected=*/0, /*stale=*/0); + // No crash + idempotent unregister of a known + unknown session. + ws->unregister_stratum_worker("sess-1"); + ws->unregister_stratum_worker("sess-unknown"); + SUCCEED(); +} + +TEST(DgbWorkSource, WorkGenStubsReturnSafeDefaults) +{ + Fixture fx; + auto ws = fx.make(); + // 4a skeleton: every work-generation getter returns its documented + // empty/default form (4c fills them in). + EXPECT_TRUE(ws->get_current_gbt_prevhash().empty()); + 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()); + EXPECT_TRUE(parts.second.empty()); +} + +TEST(DgbWorkSource, MiningSubmitStubRejectsWithoutCallingBroadcaster) +{ + Fixture fx; + auto ws = fx.make(); + auto result = ws->mining_submit( + "DGBaddr.worker1", "job-0", "en1", "en2", "ntime", "nonce", "rid-0", + /*merged_addresses=*/{}, /*job=*/nullptr); + // Stratum mining.submit response = [false, [code, msg, null]] reject form. + ASSERT_TRUE(result.is_array()); + ASSERT_GE(result.size(), 1u); + EXPECT_FALSE(result[0].get()); + // The 4a stub must NOT have reached the won-block broadcaster. + EXPECT_FALSE(fx.submit_called); +} + +TEST(DgbWorkSource, ComputeShareDifficultyReturnsNotYetSentinel) +{ + Fixture fx; + auto ws = fx.make(); + // 4a skeleton: the per-coin (Scrypt) PoW-difficulty hook returns the + // documented 0.0 parse-error/not-yet sentinel. The coin-agnostic + // StratumServer's vardiff gate treats 0.0 as a hard reject, so no + // garbage difficulty leaks into the rate monitor before 4b/4c land + // the real scrypt_1024_1_1_256 assembly. + double diff = ws->compute_share_difficulty( + "coinb1", "coinb2", "en1", "en2", "ntime", "nonce", + /*version=*/0x20000000u, "prevhash", "1e0ffff0", + /*merkle_branches=*/{}); + EXPECT_DOUBLE_EQ(diff, 0.0); +} + +} // namespace