@@ -1481,6 +1481,65 @@ int run_node(bool testnet, const std::string& rpc_endpoint,
14811481 }
14821482 }
14831483
1484+ // ── Fallback-arm event-driven tip refresh ────────────────────────────
1485+ // On the dashd-fallback arm (no embedded coin-P2P tip source) the template
1486+ // cache only re-sources on the 30 s staleness TTL (work_source.cpp
1487+ // kStaleAfter) — there is NO tip-change signal like the embedded arm's
1488+ // header_chain->set_on_tip_changed callback. DASH blocks arrive ~every
1489+ // 150 s, so up to ~30 s of stale-tip mining can occur per block (accepted
1490+ // pseudoshares that can no longer win the current block). Poll dashd for
1491+ // its best-block hash every 3 s; on a change, drop the cached template +
1492+ // bump work-generation + notify every session (clean_jobs) — the SAME
1493+ // refresh pair the embedded arm fires. Gated on the fallback arm (coin_p2p
1494+ // null) AND an armed rpc AND a live stratum acceptor. getbestblockhash is a
1495+ // trivial RPC; failures are swallowed so a daemon hiccup never crashes the
1496+ // run-loop (retry next tick). Reuses the existing NodeRPC client — no new
1497+ // dependency, no dashd config change, no new notify mechanism.
1498+ if (!coin_p2p && rpc && stratum_server) {
1499+ auto tip_timer = std::make_shared<io::steady_timer>(ioc);
1500+ auto last_tip = std::make_shared<std::string>();
1501+ auto tip_tick = std::make_shared<
1502+ std::function<void (const boost::system::error_code&)>>();
1503+ *tip_tick = [rpc = rpc.get (), ws = work_source.get (),
1504+ ss = stratum_server.get (), tip_timer, last_tip, tip_tick](
1505+ const boost::system::error_code& ec) {
1506+ if (ec) return ; // cancelled at shutdown
1507+ try {
1508+ const std::string tip = rpc->getbestblockhash ();
1509+ if (!tip.empty () && *last_tip != tip) {
1510+ const bool first_seen = last_tip->empty ();
1511+ *last_tip = tip;
1512+ // Skip the notify on the very first observation (baseline);
1513+ // only a genuine tip CHANGE forces a refresh.
1514+ if (!first_seen) {
1515+ ws->invalidate_template_cache (
1516+ " tip-poll: dashd best-block changed" );
1517+ ws->bump_work_generation ();
1518+ ss->notify_all ();
1519+ LOG_INFO << " [Stratum] tip-poll: new tip "
1520+ << tip.substr (0 , 16 )
1521+ << " , forcing template refresh + notify" ;
1522+ std::cout << " [Stratum] tip-poll: new tip "
1523+ << tip.substr (0 , 16 )
1524+ << " , forcing template refresh + notify\n " ;
1525+ }
1526+ }
1527+ } catch (const std::exception& e) {
1528+ LOG_WARNING << " [Stratum] tip-poll getbestblockhash failed "
1529+ " (non-fatal, retry next tick): " << e.what ();
1530+ } catch (...) {
1531+ // swallow — never crash the run-loop on a tip probe
1532+ }
1533+ tip_timer->expires_after (std::chrono::seconds (3 ));
1534+ tip_timer->async_wait (*tip_tick);
1535+ };
1536+ tip_timer->expires_after (std::chrono::seconds (3 ));
1537+ tip_timer->async_wait (*tip_tick);
1538+ std::cout << " [run] fallback-arm tip-poll ARMED (dashd getbestblockhash "
1539+ " every 3 s -> event-driven template refresh + clean_jobs "
1540+ " notify on tip change)\n " ;
1541+ }
1542+
14841543 std::cout << " [run] run-loop up (Ctrl-C to stop); won blocks relay DUAL-PATH:\n "
14851544 " [run] ARM A embedded coin-P2P relay (primary, daemonless) = "
14861545 << (p2p_relay ? " ARMED" : (no_p2p_relay ? " SUPPRESSED (--no-p2p-relay)"
0 commit comments