From c8ebdfe64f22f7db6ac99e89301c438d23b3030b Mon Sep 17 00:00:00 2001 From: frstrtr Date: Tue, 21 Jul 2026 05:37:19 +0400 Subject: [PATCH] dash(dashboard): wire Best Share + peers + found-blocks to the real work source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Priority — Best Share card. On the DASH solo path shares seldom mint onto the sharechain, so the tracker's verified-share hook never fired and the "Best Share" card sat empty. Record every ACCEPTED stratum pseudoshare's real X11 difficulty + miner + pow-hash into the dashboard best-share tracker via a new IWorkSource::record_best_pseudoshare() hook that the core stratum session fires on its vardiff-accept path (the one place every pseudoshare is seen). DASHWorkSource overrides it to recompute the exact pow-hash and forward to record_share_difficulty; LTC/BTC/DGB inherit a no-op (unchanged). record_share_difficulty + BestDifficulty + /best_share now carry the share hash; a compact best_share block is added to /local_stats. The dashboard card renders the record hash line. The card now shows how close the best share got to net difficulty (pct_of_block). Peers / node-status card. Add dash::Node::get_peer_info_json (ltc node.hpp parity) and wire MiningInterface::set_peer_info_fn so /local_stats peers and the node-status panel report the real pool p2p peers instead of 0. Recent-blocks card. DASHWorkSource fires a found-block hook after a won block is dispatched to the network; main_dash records it via record_found_block so DASH block wins appear in the found-block history. All three are display-only feeds: no share/target/payout/consensus logic is touched (the record_best_pseudoshare and found-block calls run after the existing accept/dispatch decisions and only read callbacks). Verified live against a testnet dashd: best_share populated difficulty=7.05e-4, pct_of_block=8.5%, with the exact pow-hash + miner; miner_hash_rates, block_value and payout split also populate through the reused LTC wiring. --- src/c2pool/main_dash.cpp | 52 +++++++++++++++ src/core/stratum_server.cpp | 14 ++++ src/core/stratum_work_source.hpp | 19 ++++++ src/core/web_server.cpp | 42 ++++++++++-- src/core/web_server.hpp | 9 ++- src/impl/dash/node.hpp | 24 +++++++ src/impl/dash/stratum/work_source.cpp | 94 ++++++++++++++++++++++++--- src/impl/dash/stratum/work_source.hpp | 48 ++++++++++++++ web-static/dashboard.html | 15 +++++ 9 files changed, 301 insertions(+), 16 deletions(-) diff --git a/src/c2pool/main_dash.cpp b/src/c2pool/main_dash.cpp index b9390a67b..9d1f9f992 100644 --- a/src/c2pool/main_dash.cpp +++ b/src/c2pool/main_dash.cpp @@ -674,6 +674,16 @@ int run_node(bool testnet, const std::string& rpc_endpoint, // ── REAL best-share hash ────────────────────────────────────── web_server->set_best_share_hash_fn( [node_ptr]() { return node_ptr->best_share_hash(); }); + + // ── REAL pool-peer info (node-status card) ──────────────────── + // /local_stats {peers:{incoming,outgoing}} + the peer table read + // this. Without it the node-status card reported 0 peers on DASH + // (the m_node fallback in rest_local_stats never sees the pool + // p2p peers). Same shape main_ltc.cpp:2830 uses. Display only. + mi->set_peer_info_fn( + [&p2p_node]() -> nlohmann::json { + return p2p_node.get_peer_info_json(); + }); } // ── REAL per-share difficulty feed (main_ltc.cpp:4213) ──────────── @@ -940,6 +950,48 @@ int run_node(bool testnet, const std::string& rpc_endpoint, work_source->set_best_share_hash_fn( [node_ptr]() -> uint256 { return node_ptr->best_share_hash(); }); + // ── REAL best-share feed for the dashboard "Best Share" card ────── + // Every ACCEPTED stratum submit reports the actual PoW difficulty of + // the found hash (target_to_difficulty(pow_hash)) + miner + pow-hash. + // record_share_difficulty tracks the pool-wide/session/round max WITH + // the hash + timestamp; /best_share + /local_stats render it. This is + // the PRIMARY best-share feed on the DASH solo path — the tracker's + // verified-share m_on_share_difficulty hook (wired above) almost never + // fires here because solo shares seldom mint onto the sharechain, so + // without this the 🎯 Best Share card sat empty. Display only; the + // callback touches no share/target/payout logic (consensus-neutral). + if (web_server) { + core::WebServer* ws = web_server.get(); + work_source->set_on_share_difficulty_fn( + [ws](double diff, const std::string& miner, const uint256& pow_hash) { + ws->get_mining_interface()->record_share_difficulty( + diff, miner, pow_hash.GetHex()); + }); + + // ── Recent-blocks feed: DASH block wins into the history card ── + // Every dispatched block solution records to /recent_blocks with + // the height, X11 block hash (== pow_hash for DASH), miner, and the + // net difficulty at find time. Without this DASH block wins never + // appeared on the recent-blocks card (main_ltc.cpp:2970 parity). + // Display only; the callback runs after dispatch, never gates it. + work_source->set_on_found_block_fn( + [ws](uint32_t height, const uint256& block_hash, + const std::string& miner, bool reached_network) { + auto* mi = ws->get_mining_interface(); + mi->record_found_block( + height, block_hash, + static_cast(std::time(nullptr)), + "DASH", miner, block_hash.GetHex(), + mi->get_network_difficulty(), + /*share_difficulty=*/0.0, + /*pool_hashrate=*/0.0, + /*subsidy=*/0); + if (!reached_network) + LOG_WARNING << "[DASH] recorded found block height=" + << height << " that reached NO network sink"; + }); + } + // Producer job: the stratum coinbase IS the producer share gentx // (byte-parity with the mint-time rebuild by construction). The // frozen per-job context is registered under its ref_hash. diff --git a/src/core/stratum_server.cpp b/src/core/stratum_server.cpp index 2cdea2042..9d289a7fe 100644 --- a/src/core/stratum_server.cpp +++ b/src/core/stratum_server.cpp @@ -1286,6 +1286,20 @@ nlohmann::json StratumSession::handle_submit(const nlohmann::json& params, const // Record in per-connection tracker (for VARDIFF adjustment + per-session stats) hashrate_tracker_.record_mining_share_submission(credited_difficulty, true); + // Best-share dashboard feed: every ACCEPTED pseudoshare's real (X11/scrypt) + // difficulty is a candidate for the "Best Share" card (highest-difficulty + // share seen — how close a miner got to a block). Default no-op; a work + // source whose dashboard is a separate object (c2pool-dash) overrides this + // to forward into the dashboard's best-share tracker. share_difficulty is + // the true per-hash difficulty computed above; pass the raw submit fields so + // the implementor can recover the exact pow-hash. Display only. + mining_interface_->record_best_pseudoshare( + share_difficulty, username_, + job.payload->coinb1, job.payload->coinb2, + extranonce1_, extranonce2, ntime, nonce, + effective_version, job.gbt_prevhash, job.nbits, + job.payload->merkle_branches); + // Check if VARDIFF changed → send new difficulty AND new work to miner. // p2pool (stratum.py:594-595): after adjusting target, calls _send_work() // which sends both set_difficulty and mining.notify. Without new work, diff --git a/src/core/stratum_work_source.hpp b/src/core/stratum_work_source.hpp index 451ef67c2..545487cda 100644 --- a/src/core/stratum_work_source.hpp +++ b/src/core/stratum_work_source.hpp @@ -183,6 +183,25 @@ class IWorkSource { uint32_t version, const std::string& prevhash_hex, const std::string& nbits_hex, const std::vector& merkle_branches) const = 0; + + /// Record an ACCEPTED pseudoshare for the dashboard "Best Share" card + /// (the highest-difficulty share seen — how close a miner got to a block). + /// The stratum session invokes this for EVERY accepted pseudoshare (the + /// vardiff-gate acceptance path), passing the already-computed X11/scrypt + /// share difficulty plus the raw submit fields so an implementor that wants + /// the exact pow-hash can recompute it. Default no-op — only work sources + /// whose dashboard is a separate object (c2pool-dash: the DASHWorkSource + /// drives its own stratum acceptor, distinct from the WebServer's + /// MiningInterface) override this to forward to the dashboard's best-share + /// tracker. Display only — never affects acceptance, target, or payout. + virtual void record_best_pseudoshare( + double /*share_difficulty*/, const std::string& /*miner*/, + 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*/) {} }; } // namespace core::stratum \ No newline at end of file diff --git a/src/core/web_server.cpp b/src/core/web_server.cpp index 2eb2796f0..5c94a398d 100644 --- a/src/core/web_server.cpp +++ b/src/core/web_server.cpp @@ -4667,6 +4667,29 @@ nlohmann::json MiningInterface::rest_local_stats() } result["miner_last_difficulties"] = miner_diffs; + // Best-share summary (highest-difficulty pseudoshare seen). Mirrors the + // dedicated /best_share endpoint so /local_stats consumers get the "how + // close did the best share get to a block" metric inline. Display only. + { + std::lock_guard lock(m_best_diff_mutex); + auto pct = [&](double d) { return net_diff > 0 ? d / net_diff * 100.0 : 0.0; }; + auto bs_entry = [&](double diff, const std::string& miner, + const std::string& hash, uint64_t ts) { + return nlohmann::json{ + {"difficulty", diff}, {"pct_of_block", pct(diff)}, + {"miner", miner}, {"hash", hash}, {"timestamp", ts}}; + }; + result["best_share"] = { + {"network_difficulty", net_diff}, + {"round", bs_entry(m_best_difficulty.round, m_best_difficulty.miner, + m_best_difficulty.hash, m_best_difficulty.timestamp)}, + {"session", bs_entry(m_best_difficulty.session, m_best_difficulty.session_miner, + m_best_difficulty.session_hash, m_best_difficulty.session_ts)}, + {"all_time", bs_entry(m_best_difficulty.all_time, m_best_difficulty.all_time_miner, + m_best_difficulty.all_time_hash, m_best_difficulty.all_time_ts)} + }; + } + // p2pool-compat: version and protocol_version result["version"] = m_pool_version; result["protocol_version"] = m_protocol_version.load(std::memory_order_relaxed); @@ -5610,23 +5633,28 @@ nlohmann::json MiningInterface::rest_best_share() best_round = avg; } - auto make_entry = [&](double diff, const std::string& miner, uint64_t ts) { + auto make_entry = [&](double diff, const std::string& miner, uint64_t ts, + const std::string& hash) { nlohmann::json e; e["difficulty"] = diff; e["pct_of_block"] = (net_diff > 0) ? (diff / net_diff * 100.0) : 0.0; e["miner"] = miner; e["timestamp"] = ts; + e["hash"] = hash; // pow-hash of the record share (empty until wired) return e; }; result["all_time"] = make_entry(best_all, - m_best_difficulty.all_time_miner, m_best_difficulty.all_time_ts); + m_best_difficulty.all_time_miner, m_best_difficulty.all_time_ts, + m_best_difficulty.all_time_hash); auto session = make_entry(best_sess, - m_best_difficulty.session_miner, m_best_difficulty.session_ts); + m_best_difficulty.session_miner, m_best_difficulty.session_ts, + m_best_difficulty.session_hash); session["started"] = m_start_time.time_since_epoch().count() / 1000000000ULL; result["session"] = session; auto round = make_entry(best_round, - m_best_difficulty.miner, m_best_difficulty.timestamp); + m_best_difficulty.miner, m_best_difficulty.timestamp, + m_best_difficulty.hash); round["started"] = m_best_difficulty.round_start; result["round"] = round; } @@ -7266,7 +7294,8 @@ nlohmann::json MiningInterface::rest_web_graph_data(const std::string& source, c // ──────────── Difficulty tracking and stat log helpers ──────────────────── -void MiningInterface::record_share_difficulty(double difficulty, const std::string& miner) +void MiningInterface::record_share_difficulty(double difficulty, const std::string& miner, + const std::string& share_hash) { // Feed the rolling-hashrate ring. Independent of the best-diff // tracking below; the ring has its own internal lock. @@ -7278,16 +7307,19 @@ void MiningInterface::record_share_difficulty(double difficulty, const std::stri if (difficulty > m_best_difficulty.all_time) { m_best_difficulty.all_time = difficulty; m_best_difficulty.all_time_miner = miner; + m_best_difficulty.all_time_hash = share_hash; m_best_difficulty.all_time_ts = now_ts; } if (difficulty > m_best_difficulty.session) { m_best_difficulty.session = difficulty; m_best_difficulty.session_miner = miner; + m_best_difficulty.session_hash = share_hash; m_best_difficulty.session_ts = now_ts; } if (difficulty > m_best_difficulty.round) { m_best_difficulty.round = difficulty; m_best_difficulty.miner = miner; + m_best_difficulty.hash = share_hash; m_best_difficulty.timestamp = now_ts; } } diff --git a/src/core/web_server.hpp b/src/core/web_server.hpp index a296f3fa5..155b1e9a0 100644 --- a/src/core/web_server.hpp +++ b/src/core/web_server.hpp @@ -1364,7 +1364,11 @@ class MiningInterface : public jsonrpccxx::JsonRpc2Server, void auto_detect_external_info(); // Best share difficulty tracking (for /best_share, /miner_stats) - void record_share_difficulty(double difficulty, const std::string& miner); + // share_hash: optional pow-hash of the record-setting share (display only; + // DASH stratum submit path supplies it so the Best Share card can show the + // exact hash that got closest to net difficulty). + void record_share_difficulty(double difficulty, const std::string& miner, + const std::string& share_hash = ""); void record_merged_share_difficulty(double difficulty, const std::string& miner); // Stat log entry (appended every 5 minutes, rolling 24h window for /web/log JSON) @@ -1426,12 +1430,15 @@ class MiningInterface : public jsonrpccxx::JsonRpc2Server, struct BestDifficulty { double all_time{0.0}; std::string all_time_miner; + std::string all_time_hash; // pow-hash of the record share (display only) uint64_t all_time_ts{0}; double session{0.0}; std::string session_miner; + std::string session_hash; uint64_t session_ts{0}; double round{0.0}; std::string miner; // round-level miner (backward compat) + std::string hash; // round-level pow-hash (display only) uint64_t timestamp{0}; // round-level timestamp (backward compat) uint64_t round_start{0}; // Merged chain (DOGE) best share tracking diff --git a/src/impl/dash/node.hpp b/src/impl/dash/node.hpp index 8df31da77..0198dbe2d 100644 --- a/src/impl/dash/node.hpp +++ b/src/impl/dash/node.hpp @@ -620,6 +620,30 @@ class NodeImpl : public pool::BaseNodeaddr(); + bool incoming = (m_outbound_addrs.find(addr) == m_outbound_addrs.end()); + auto uptime_sec = std::chrono::duration_cast( + std::chrono::steady_clock::now() - peer->m_connected_at).count(); + arr.push_back({ + {"address", addr.to_string()}, + {"version", peer->m_other_subversion}, + {"incoming", incoming}, + {"uptime", uptime_sec}, + {"downtime", 0}, + {"web_port", 0} + }); + } + return arr; + } + /// Relay a share (and up to 4 un-broadcast ancestors) to all peers via /// the message_shares wire codec (+ remember_tx/forget_tx for txs the /// peer lacks). try_to_lock; deferred if the compute thread is busy. diff --git a/src/impl/dash/stratum/work_source.cpp b/src/impl/dash/stratum/work_source.cpp index ff951fa56..05e807a83 100644 --- a/src/impl/dash/stratum/work_source.cpp +++ b/src/impl/dash/stratum/work_source.cpp @@ -710,6 +710,12 @@ nlohmann::json DASHWorkSource::mining_submit( } }; + // NOTE: the dashboard "Best Share" feed is NOT recorded here — mining_submit + // only runs for pool-quality shares / block solutions (the stratum session + // gates it), so it would miss ordinary pseudoshares. Every accepted + // pseudoshare is instead recorded via record_best_pseudoshare(), which the + // core stratum session calls on the vardiff-accept path. + // 6. Classify (tighten-first: block target before share target). if (pow_hash <= block_target) { // A full network block. NEVER drop it. @@ -803,6 +809,19 @@ nlohmann::json DASHWorkSource::mining_submit( "BLOCK height=" << height << " not broadcast -- lost subsidy!"; } + // Recent-blocks dashboard record: a genuine block solution we + // dispatched to the network. A local payee-guard reject was never sent + // (not a won block) so it is skipped. Display only; fired after the + // dispatch decision, never gates it. + if (!payee_guard_reject) { + FoundBlockFn fb_fn; + { + std::lock_guard lk(found_block_mutex_); + fb_fn = on_found_block_fn_; + } + if (fb_fn) fb_fn(height, pow_hash, username, reached_network); + } + bump(true); return nlohmann::json(true); } @@ -874,23 +893,23 @@ nlohmann::json DASHWorkSource::mining_submit( return reject(23, "Low difficulty share"); } -double DASHWorkSource::compute_share_difficulty( +// Shared header reconstruction → X11 pow-hash. Used by BOTH the vardiff-gate +// difficulty (compute_share_difficulty) and the best-share dashboard feed +// (record_best_pseudoshare) so the two never diverge. Returns a null uint256 +// on any malformed input. +static uint256 dash_reconstruct_pow_hash( 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 + const std::vector& merkle_branches) { - // Per-coin PoW difficulty for the vardiff gate: the SAME header - // reconstruction as mining_submit, ending in diff1 / x11(header). 0.0 on - // any malformed input (the documented parse-error sentinel the vardiff - // gate treats as a hard reject). auto coinb1_bytes = ParseHex(coinb1); auto en1_bytes = ParseHex(extranonce1); auto en2_bytes = ParseHex(extranonce2); auto coinb2_bytes = ParseHex(coinb2); - if (coinb1_bytes.empty() || coinb2_bytes.empty()) return 0.0; + if (coinb1_bytes.empty() || coinb2_bytes.empty()) return uint256(); std::vector coinbase; coinbase.reserve(coinb1_bytes.size() + en1_bytes.size() @@ -908,9 +927,8 @@ double DASHWorkSource::compute_share_difficulty( merkle_root = merkle_pair(merkle_root, b); } - // prevhash arrives BE-display; 64 hex chars or it's malformed. auto prevhash_be = ParseHex(prevhash_hex); - if (prevhash_be.size() != 32) return 0.0; + if (prevhash_be.size() != 32) return uint256(); uint256 prev_block; prev_block.SetHex(prevhash_hex.c_str()); @@ -920,11 +938,55 @@ double DASHWorkSource::compute_share_difficulty( parse_be_hex_u32(ntime), parse_be_hex_u32(nbits_hex), parse_be_hex_u32(nonce)); - const uint256 pow_hash = dash::crypto::hash_x11(header, sizeof(header)); + return dash::crypto::hash_x11(header, sizeof(header)); +} + +double DASHWorkSource::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 +{ + // Per-coin PoW difficulty for the vardiff gate: the SAME header + // reconstruction as mining_submit, ending in diff1 / x11(header). 0.0 on + // any malformed input (the documented parse-error sentinel the vardiff + // gate treats as a hard reject). + const uint256 pow_hash = dash_reconstruct_pow_hash( + coinb1, coinb2, extranonce1, extranonce2, ntime, nonce, + version, prevhash_hex, nbits_hex, merkle_branches); if (pow_hash.IsNull()) return 0.0; return chain::target_to_difficulty(pow_hash); } +void DASHWorkSource::record_best_pseudoshare( + double share_difficulty, const std::string& miner, + 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) +{ + ShareDifficultyFn fn; + { + std::lock_guard lk(share_difficulty_mutex_); + fn = on_share_difficulty_fn_; + } + if (!fn) return; + const uint256 pow_hash = dash_reconstruct_pow_hash( + coinb1, coinb2, extranonce1, extranonce2, ntime, nonce, + version, prevhash_hex, nbits_hex, merkle_branches); + // Prefer the difficulty the stratum session already computed; fall back to + // the reconstructed hash if the caller passed 0. + double diff = share_difficulty > 0.0 + ? share_difficulty + : (pow_hash.IsNull() ? 0.0 : chain::target_to_difficulty(pow_hash)); + if (diff > 0.0) + fn(diff, miner, pow_hash); +} + // ───────────────────────────────────────────────────────────────────────────── // DASH-specific control surface. // ───────────────────────────────────────────────────────────────────────────── @@ -935,6 +997,18 @@ void DASHWorkSource::set_best_share_hash_fn(std::function fn) best_share_hash_fn_ = std::move(fn); } +void DASHWorkSource::set_on_share_difficulty_fn(ShareDifficultyFn fn) +{ + std::lock_guard lk(share_difficulty_mutex_); + on_share_difficulty_fn_ = std::move(fn); +} + +void DASHWorkSource::set_on_found_block_fn(FoundBlockFn fn) +{ + std::lock_guard lk(found_block_mutex_); + on_found_block_fn_ = std::move(fn); +} + void DASHWorkSource::set_mint_share_fn(MintShareFn fn) { std::lock_guard lk(mint_share_mutex_); diff --git a/src/impl/dash/stratum/work_source.hpp b/src/impl/dash/stratum/work_source.hpp index 62b7a80c2..22cc05311 100644 --- a/src/impl/dash/stratum/work_source.hpp +++ b/src/impl/dash/stratum/work_source.hpp @@ -241,6 +241,20 @@ class DASHWorkSource : public core::stratum::IWorkSource const std::string& nbits_hex, const std::vector& merkle_branches) const override; + /// IWorkSource best-share feed: recompute the exact X11 pow-hash for this + /// accepted pseudoshare and forward (difficulty, miner, pow_hash) to the + /// dashboard best-share tracker via on_share_difficulty_fn_. Every accepted + /// pseudoshare flows here (the stratum vardiff-accept path), so the "Best + /// Share" card populates on the DASH solo path where shares seldom mint. + void record_best_pseudoshare( + double share_difficulty, const std::string& miner, + 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) 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(); } @@ -266,6 +280,32 @@ class DASHWorkSource : public core::stratum::IWorkSource /// ShareTracker is constructed. void set_best_share_hash_fn(std::function fn); + /// Best-share (highest-difficulty pseudoshare) reporter. Fired from + /// mining_submit for EVERY accepted submission with the actual PoW + /// difficulty of the found hash (target_to_difficulty(pow_hash)), the + /// submitting miner (stratum username), and the pow-hash hex. main_dash.cpp + /// binds this to MiningInterface::record_share_difficulty so the dashboard + /// "Best Share" card shows how close DASH solo shares got to net difficulty + /// even when no share ever mints onto the sharechain. Display only — never + /// touches share/target/payout logic. While unbound this is a no-op. + using ShareDifficultyFn = + std::function; + void set_on_share_difficulty_fn(ShareDifficultyFn fn); + + /// Found-block reporter for the dashboard "Recent Blocks" card. Fired from + /// mining_submit the moment a submission meets the full network block + /// target AND is dispatched to the network (NOT on a local payee-guard + /// reject). Carries the block height, the X11 block hash (== pow_hash for + /// DASH), the submitting miner, and whether at least one network sink was + /// reached. main_dash.cpp binds this to MiningInterface::record_found_block + /// so DASH block wins appear in the found-block history. Display only — + /// never gates the won-block dispatch. While unbound this is a no-op. + using FoundBlockFn = + std::function; + void set_on_found_block_fn(FoundBlockFn fn); + /// Wire the sharechain mint dispatch (stage 4d follow-up). While unbound, /// share-target submissions are accepted for vardiff + loudly logged. void set_mint_share_fn(MintShareFn fn); @@ -349,6 +389,14 @@ class DASHWorkSource : public core::stratum::IWorkSource mutable std::mutex best_share_mutex_; std::function best_share_hash_fn_; + // Best-share (highest-difficulty pseudoshare) reporter. Empty until wired. + mutable std::mutex share_difficulty_mutex_; + ShareDifficultyFn on_share_difficulty_fn_; + + // Found-block reporter (dashboard recent-blocks card). Empty until wired. + mutable std::mutex found_block_mutex_; + FoundBlockFn on_found_block_fn_; + // Sharechain mint dispatch (stage 4d). Empty until wired. mutable std::mutex mint_share_mutex_; MintShareFn mint_share_fn_; diff --git a/web-static/dashboard.html b/web-static/dashboard.html index a4f7e32a3..5cec34a5a 100644 --- a/web-static/dashboard.html +++ b/web-static/dashboard.html @@ -1841,6 +1841,7 @@

🎯 Best Share

-
- / -
🏆 -
+ @@ -2992,6 +2993,20 @@

Legend

d3.select('#best_share_round_diff').text(format_difficulty(bs.round.difficulty)); d3.select('#best_share_net_diff').text(format_difficulty(bs.network_difficulty)); d3.select('#best_share_alltime_pct_fancy').html(format_pct_fancy(bs.all_time.pct_of_block)); + // Record-share hash (the exact hash that got closest to a block). + var recHash = (bs.round && bs.round.hash) ? bs.round.hash + : ((bs.all_time && bs.all_time.hash) ? bs.all_time.hash : ''); + var recMiner = (bs.round && bs.round.miner) ? bs.round.miner + : ((bs.all_time && bs.all_time.miner) ? bs.all_time.miner : ''); + var hl = d3.select('#best_share_hash_line'); + if (recHash) { + hl.style('display', 'block') + .attr('title', recHash + (recMiner ? ' — ' + recMiner : '')) + .text('⛏ ' + recHash.substr(0, 12) + '…' + recHash.substr(-8) + + (recMiner ? ' · ' + recMiner.substr(0, 10) : '')); + } else { + hl.style('display', 'none'); + } if (bs.merged) { d3.select('#best_share_merged_round_part').style('display', 'inline'); d3.select('#best_share_merged_pct_fancy').html(format_pct_fancy(bs.merged.round.pct_of_block));