@@ -776,7 +776,7 @@ void HttpSession::process_request()
776776 std::string ext = resolved.extension ().string ();
777777 std::string mime = " application/octet-stream" ;
778778 if (ext == " .html" || ext == " .htm" ) mime = " text/html; charset=utf-8" ;
779- else if (ext == " .js" ) mime = " application/javascript; charset=utf-8" ;
779+ else if (ext == " .js" || ext == " .mjs " ) mime = " application/javascript; charset=utf-8" ;
780780 else if (ext == " .css" ) mime = " text/css; charset=utf-8" ;
781781 else if (ext == " .json" ) mime = " application/json; charset=utf-8" ;
782782 else if (ext == " .ico" ) mime = " image/x-icon" ;
@@ -3071,6 +3071,17 @@ nlohmann::json MiningInterface::rest_current_payouts()
30713071 nlohmann::json result = nlohmann::json::object ();
30723072 bool is_ltc = (m_blockchain == Blockchain::LITECOIN );
30733073
3074+ // Script-to-address resolver — picks chain-specific version bytes so
3075+ // Dash payouts render as 'X...' not Bitcoin '1...'.
3076+ auto resolve_addr = [this , is_ltc](const std::vector<unsigned char >& s) -> std::string {
3077+ if (m_blockchain == Blockchain::DASH ) {
3078+ uint8_t p2pkh = m_testnet ? 140 : 76 ; // Dash: 'y' / 'X'
3079+ uint8_t p2sh = m_testnet ? 19 : 16 ; // Dash: '7'
3080+ return core::script_to_address (s, " " , p2pkh, p2sh);
3081+ }
3082+ return core::script_to_address (s, is_ltc, m_testnet);
3083+ };
3084+
30743085 // Primary source: cached PPLNS outputs from the coinbase builder.
30753086 // These are always up-to-date with the latest share template and subsidy.
30763087 auto cached = get_cached_pplns_outputs ();
@@ -3085,7 +3096,7 @@ nlohmann::json MiningInterface::rest_current_payouts()
30853096
30863097 // script_hex is a hex-encoded scriptPubKey — decode to bytes, then to address
30873098 auto script_bytes = ParseHex (script_hex);
3088- std::string addr = core::script_to_address (script_bytes, is_ltc, m_testnet );
3099+ std::string addr = resolve_addr (script_bytes);
30893100 if (addr.empty () && script_bytes.size () > 33 && script_bytes.back () == 0xac ) {
30903101 // P2PK: PUSH<len> <pubkey> OP_CHECKSIG → hash pubkey → P2PKH address
30913102 size_t pk_len = script_bytes[0 ];
@@ -3098,7 +3109,7 @@ nlohmann::json MiningInterface::rest_current_payouts()
30983109 p2pkh.insert (p2pkh.end (), rip, rip + 20 );
30993110 p2pkh.push_back (0x88 );
31003111 p2pkh.push_back (0xac );
3101- addr = core::script_to_address (p2pkh, is_ltc, m_testnet );
3112+ addr = resolve_addr (p2pkh);
31023113 }
31033114 }
31043115 if (addr.empty ())
@@ -3754,12 +3765,13 @@ void MiningInterface::cache_pplns_at_tip()
37543765 std::lock_guard<std::mutex> lock (m_pplns_cache_mutex);
37553766 m_pplns_per_tip[tip] = std::move (pplns);
37563767
3757- // Evict old entries to prevent unbounded growth.
3758- // Keep at most 100 entries (~50 minutes of tips at 30s intervals).
3759- constexpr size_t MAX_PPLNS_CACHE = 100 ;
3768+ // Evict old entries to prevent unbounded growth. Cap at 5000 so Dash's
3769+ // chain_length=4320 precompute + live tip churn both fit. LTC's older
3770+ // 100-entry comment was sized for live tips only; precompute now stores
3771+ // one entry per share in the window, so the cap has to be larger than
3772+ // the biggest window we serve.
3773+ constexpr size_t MAX_PPLNS_CACHE = 5000 ;
37603774 if (m_pplns_per_tip.size () > MAX_PPLNS_CACHE * 2 ) {
3761- // Bulk clear — cheaper than selective eviction on unordered_map
3762- // Keep only the current tip entry
37633775 auto current = std::move (m_pplns_per_tip[tip]);
37643776 m_pplns_per_tip.clear ();
37653777 m_pplns_per_tip[tip] = std::move (current);
@@ -4350,25 +4362,42 @@ nlohmann::json MiningInterface::rest_local_stats()
43504362
43514363 // block_value in coins (not satoshis)
43524364 double block_value = 0.0 ;
4365+ double payment_amount = 0.0 ;
43534366 {
43544367 std::lock_guard<std::mutex> lock (m_work_mutex);
4355- if (!m_cached_template.is_null ())
4368+ if (!m_cached_template.is_null ()) {
43564369 block_value = m_cached_template.value (" coinbasevalue" , uint64_t (0 )) / 1e8 ;
4370+ // Dash: payment_amount = masternode + superblock + platform payments
4371+ // These are deducted from miner payout before PPLNS distribution
4372+ payment_amount = m_cached_template.value (" payment_amount" , uint64_t (0 )) / 1e8 ;
4373+ }
43574374 }
43584375 result[" block_value" ] = block_value;
4359- // Deduct total fee (node fee + dev donation) from miner portion
4360- // donation_proportion already represents the combined fee ratio
4376+ // Miner portion: subsidy minus protocol payments (masternode/superblock) minus node fee
4377+ // For LTC/DOGE: payment_amount=0, so block_value_miner = block_value * (1 - fee)
4378+ // For Dash: block_value_miner = (subsidy - payment_amount) * (1 - fee)
43614379 double fee_ratio = m_pool_fee_percent / 100.0 ;
4362- result[" block_value_miner" ] = block_value * (1.0 - fee_ratio);
4363- result[" block_value_payments" ] = block_value; // total including fees
4380+ double miner_subsidy = std::max (0.0 , block_value - payment_amount);
4381+ result[" block_value_miner" ] = miner_subsidy * (1.0 - fee_ratio);
4382+ result[" block_value_payments" ] = (m_blockchain == Blockchain::DASH ) ? payment_amount : block_value;
43644383
43654384 // Node fee amounts per block: fee% × (local_hashrate / pool_hashrate) × block_value
43664385 // Matches p2pool: operator gets fee% of THIS node's contribution, not the whole block.
43674386 {
43684387 double local_hr = m_stratum_hashrate_fn ? m_stratum_hashrate_fn () : 0.0 ;
43694388 double pool_hr = m_pool_hashrate_fn ? m_pool_hashrate_fn () : 0.0 ;
43704389 double node_share = (pool_hr > 0 && local_hr > 0 ) ? local_hr / pool_hr : 0.0 ;
4371- result[" node_fee_ltc" ] = block_value * fee_ratio * node_share;
4390+ // Use blockchain-appropriate key for backward compatibility
4391+ double primary_fee = miner_subsidy * fee_ratio * node_share;
4392+ switch (m_blockchain) {
4393+ case Blockchain::DASH :
4394+ result[" node_fee_dash" ] = primary_fee; break ;
4395+ case Blockchain::DOGECOIN :
4396+ result[" node_fee_doge" ] = primary_fee;
4397+ result[" node_fee_ltc" ] = primary_fee; break ; // compat
4398+ default :
4399+ result[" node_fee_ltc" ] = primary_fee; break ;
4400+ }
43724401 if (m_mm_manager) {
43734402 auto chain_infos = m_mm_manager->get_chain_infos ();
43744403 for (const auto & ci : chain_infos) {
@@ -4396,7 +4425,10 @@ nlohmann::json MiningInterface::rest_local_stats()
43964425 }
43974426
43984427 // 2. No work template yet
4399- {
4428+ // Coin targets that drive their own work pipeline (c2pool-dash)
4429+ // don't populate m_cached_template; suppress this warning when
4430+ // the dashboard-always-ready flag is set.
4431+ if (!m_dashboard_always_ready.load (std::memory_order_relaxed)) {
44004432 std::lock_guard<std::mutex> lock (m_work_mutex);
44014433 if (!m_work_valid)
44024434 warnings.push_back (" No block template received yet — waiting for daemon connection" );
@@ -4528,7 +4560,7 @@ nlohmann::json MiningInterface::rest_local_stats()
45284560
45294561 // p2pool-compat: version and protocol_version
45304562 result[" version" ] = m_pool_version;
4531- result[" protocol_version" ] = 3600 ; // V36 share format
4563+ result[" protocol_version" ] = m_protocol_version. load (std::memory_order_relaxed);
45324564
45334565 return result;
45344566}
@@ -4636,6 +4668,20 @@ nlohmann::json MiningInterface::rest_web_currency_info()
46364668 result[" tx_explorer_url_prefix" ] = " https://blockchair.com/dogecoin/transaction/" ;
46374669 }
46384670 break ;
4671+ case Blockchain::DASH :
4672+ result[" symbol" ] = " DASH" ;
4673+ result[" name" ] = " Dash" ;
4674+ result[" block_period" ] = 150 ; // 2.5 min average
4675+ if (!m_custom_address_explorer.empty ()) {
4676+ result[" address_explorer_url_prefix" ] = m_custom_address_explorer;
4677+ result[" block_explorer_url_prefix" ] = m_custom_block_explorer;
4678+ result[" tx_explorer_url_prefix" ] = m_custom_tx_explorer;
4679+ } else {
4680+ result[" address_explorer_url_prefix" ] = " https://blockchair.com/dash/address/" ;
4681+ result[" block_explorer_url_prefix" ] = " https://blockchair.com/dash/block/" ;
4682+ result[" tx_explorer_url_prefix" ] = " https://blockchair.com/dash/transaction/" ;
4683+ }
4684+ break ;
46394685 }
46404686
46414687 // Expose explorer state so dashboard JS can link to internal explorer
@@ -4647,6 +4693,17 @@ nlohmann::json MiningInterface::rest_web_currency_info()
46474693 result[" embedded" ] = (m_embedded_node != nullptr );
46484694 result[" has_rpc" ] = (m_coin_rpc != nullptr );
46494695
4696+ // p2pool share version for the current coin — consumed by the
4697+ // bundled @c2pool/sharechain-explorer to classify share cells.
4698+ // Dash = v16, LTC/DOGE/BTC = v36.
4699+ switch (m_blockchain) {
4700+ case Blockchain::DASH : result[" share_version" ] = 16 ; break ;
4701+ case Blockchain::LITECOIN :
4702+ case Blockchain::BITCOIN :
4703+ case Blockchain::DOGECOIN :
4704+ default : result[" share_version" ] = 36 ; break ;
4705+ }
4706+
46504707 return result;
46514708}
46524709
@@ -4702,7 +4759,11 @@ nlohmann::json MiningInterface::rest_sync_status()
47024759 // The placeholder template has height=1 and coinbasevalue=5000000000 —
47034760 // reject it by requiring height > 100.
47044761 bool has_work = false ;
4705- {
4762+ if (m_dashboard_always_ready.load (std::memory_order_relaxed)) {
4763+ // Coin-driven work pipelines (c2pool-dash) manage their own work;
4764+ // report has_work=true so the loading gate doesn't stall.
4765+ has_work = true ;
4766+ } else {
47064767 std::lock_guard<std::mutex> lock (m_work_mutex);
47074768 has_work = !m_cached_template.is_null ()
47084769 && m_cached_template.value (" coinbasevalue" , uint64_t (0 )) > 0
@@ -4734,6 +4795,12 @@ nlohmann::json MiningInterface::rest_sync_status()
47344795
47354796bool MiningInterface::is_node_ready ()
47364797{
4798+ // Coin targets that drive their own work pipeline (c2pool-dash) can
4799+ // bypass the internal readiness gate — their dashboard is ready as
4800+ // soon as the HTTP server is up.
4801+ if (m_dashboard_always_ready.load (std::memory_order_relaxed))
4802+ return true ;
4803+
47374804 // Check sharechain has verified shares
47384805 if (m_sharechain_stats_fn) {
47394806 auto sc = m_sharechain_stats_fn ();
@@ -5025,6 +5092,10 @@ nlohmann::json MiningInterface::rest_node_info()
50255092 result[" network" ] = m_testnet ? " dogecoin_testnet" : " dogecoin" ;
50265093 result[" symbol" ] = " DOGE" ;
50275094 break ;
5095+ case Blockchain::DASH :
5096+ result[" network" ] = m_testnet ? " dash_testnet" : " dash" ;
5097+ result[" symbol" ] = " DASH" ;
5098+ break ;
50285099 }
50295100 return result;
50305101}
@@ -5374,6 +5445,12 @@ nlohmann::json MiningInterface::rest_miner_payouts(const std::string& address)
53745445
53755446nlohmann::json MiningInterface::rest_version_signaling (const nlohmann::json* cached_sc)
53765447{
5448+ // V35→V36 transition tracking applies to the v36 coins (LTC and DOGE).
5449+ // Other blockchains (e.g. Dash v16) don't have a pending transition, so
5450+ // return an empty object to keep the dashboard's transition banners hidden.
5451+ if (m_blockchain != Blockchain::LITECOIN && m_blockchain != Blockchain::DOGECOIN )
5452+ return nlohmann::json::object ();
5453+
53775454 // Matches p2pool's get_version_signaling() — all fields the dashboard JS expects.
53785455 constexpr int TARGET_VERSION = 36 ;
53795456 const std::map<int , std::string> share_type_names = {
@@ -6421,18 +6498,38 @@ nlohmann::json MiningInterface::rest_discovered_merged_blocks()
64216498nlohmann::json MiningInterface::rest_broadcaster_status ()
64226499{
64236500 nlohmann::json result = nlohmann::json::object ();
6424- if (!m_mm_manager || !m_mm_manager->has_chains ()) {
6425- result[" running" ] = false ;
6426- result[" last_broadcast" ] = nullptr ;
6501+
6502+ // LTC / merged-mining path: broadcaster is driven by MM manager.
6503+ if (m_mm_manager && m_mm_manager->has_chains ()) {
6504+ result[" running" ] = true ;
6505+ result[" enabled" ] = true ;
6506+ result[" chains" ] = m_mm_manager->chain_count ();
6507+ result[" total_blocks_found" ] = m_mm_manager->get_total_blocks ();
6508+ if (m_ltc_peer_info_fn)
6509+ result[" peers" ] = m_ltc_peer_info_fn ();
64276510 return result;
64286511 }
6429- result[" running" ] = true ;
6430- result[" enabled" ] = true ;
6431- result[" chains" ] = m_mm_manager->chain_count ();
6432- result[" total_blocks_found" ] = m_mm_manager->get_total_blocks ();
6433- // LTC P2P peer info
6434- if (m_ltc_peer_info_fn)
6435- result[" peers" ] = m_ltc_peer_info_fn ();
6512+
6513+ // Non-merged path (e.g. Dash SPV): coin_peer_info_fn provides the
6514+ // daemon P2P peer list so the dashboard's "Parent Chain Peers" panel
6515+ // can render dashd connections. Runs true iff the callback is wired
6516+ // AND at least one peer is connected.
6517+ if (m_coin_peer_info_fn) {
6518+ auto peers = m_coin_peer_info_fn ();
6519+ bool any_connected = false ;
6520+ if (peers.is_array ()) {
6521+ for (const auto & p : peers)
6522+ if (p.value (" connected" , false )) { any_connected = true ; break ; }
6523+ }
6524+ result[" running" ] = any_connected;
6525+ result[" enabled" ] = true ;
6526+ result[" peers" ] = std::move (peers);
6527+ result[" total_blocks_found" ] = 0 ; // no coin-side found-block counter yet
6528+ return result;
6529+ }
6530+
6531+ result[" running" ] = false ;
6532+ result[" last_broadcast" ] = nullptr ;
64366533 return result;
64376534}
64386535
@@ -6888,10 +6985,30 @@ void MiningInterface::update_stat_log()
68886985 entry.current_payouts = nlohmann::json::object ();
68896986 {
68906987 auto cached = get_cached_pplns_outputs ();
6891- bool is_ltc = (m_blockchain == Blockchain::LITECOIN );
6988+ // Chain-aware address encoding. The legacy is_ltc bool overload
6989+ // only covers LTC/BTC version bytes; Dash needs 76/16 mainnet,
6990+ // 140/19 testnet. Pick the right pair per blockchain so graph_db
6991+ // samples carry human-readable addresses (not Bitcoin fallback
6992+ // encodings). Parity-audit D-tier followup shipped alongside C2.
6993+ auto [p2pkh_ver, p2sh_ver, hrp] = [&]()
6994+ -> std::tuple<uint8_t , uint8_t , std::string>
6995+ {
6996+ switch (m_blockchain) {
6997+ case Blockchain::LITECOIN :
6998+ return m_testnet ? std::make_tuple<uint8_t , uint8_t , std::string>(0x6f , 0xc4 , " tltc" )
6999+ : std::make_tuple<uint8_t , uint8_t , std::string>(0x30 , 0x32 , " ltc" );
7000+ case Blockchain::DASH :
7001+ return m_testnet ? std::make_tuple<uint8_t , uint8_t , std::string>(140 , 19 , " " )
7002+ : std::make_tuple<uint8_t , uint8_t , std::string>(76 , 16 , " " );
7003+ case Blockchain::BITCOIN :
7004+ default :
7005+ return m_testnet ? std::make_tuple<uint8_t , uint8_t , std::string>(0x6f , 0xc4 , " tb" )
7006+ : std::make_tuple<uint8_t , uint8_t , std::string>(0x00 , 0x05 , " bc" );
7007+ }
7008+ }();
68927009 for (const auto & [script_hex, amount] : cached) {
68937010 auto script_bytes = ParseHex (script_hex);
6894- std::string addr = core::script_to_address (script_bytes, is_ltc, m_testnet );
7011+ std::string addr = core::script_to_address (script_bytes, hrp, p2pkh_ver, p2sh_ver );
68957012 if (addr.empty ()) addr = script_hex;
68967013 double coin_amt = static_cast <double >(amount) / 1e8 ;
68977014 if (entry.current_payouts .contains (addr))
0 commit comments