Skip to content

Commit e803932

Browse files
committed
Wire stratum stats page: p2pool-format response with pool/workers split
The stratum.html frontend expects { pool: {...}, workers: {...} } but the endpoint returned a flat object with wrong field names — all stats showed zero. Restructure rest_stratum_stats() to match p2pool format: - pool.connections, pool.total_accepted/rejected, pool.submission_rate, pool.uptime, pool.ip_connections, pool.ip_workers - workers keyed by "addr.worker" with hash_rate, shares, accepted, rejected, first_seen, last_seen, connections - Per-IP connection tracking with worker counts for DDoS detection
1 parent cd7d917 commit e803932

1 file changed

Lines changed: 58 additions & 36 deletions

File tree

src/core/web_server.cpp

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3066,12 +3066,18 @@ nlohmann::json MiningInterface::rest_connected_miners()
30663066

30673067
nlohmann::json MiningInterface::rest_stratum_stats()
30683068
{
3069+
// p2pool format: { pool: {...}, workers: {...} }
3070+
// stratum.html reads data.pool.* and data.workers.*
30693071
nlohmann::json result = nlohmann::json::object();
30703072
auto workers = get_stratum_workers();
3073+
auto now = std::time(nullptr);
3074+
auto now_steady = std::chrono::steady_clock::now();
30713075

30723076
double total_hashrate = 0.0;
30733077
uint64_t total_accepted = 0, total_rejected = 0, total_stale = 0;
30743078
std::set<std::string> unique_addrs;
3079+
std::map<std::string, int> ip_connections; // IP → connection count
3080+
std::map<std::string, std::set<std::string>> ip_workers_set; // IP → unique workers
30753081

30763082
nlohmann::json workers_json = nlohmann::json::object();
30773083
for (const auto& [sid, w] : workers) {
@@ -3081,10 +3087,19 @@ nlohmann::json MiningInterface::rest_stratum_stats()
30813087
total_stale += w.stale;
30823088
unique_addrs.insert(w.username);
30833089

3090+
// Track connections per IP
3091+
auto colon = w.remote_endpoint.rfind(':');
3092+
std::string ip = (colon != std::string::npos) ? w.remote_endpoint.substr(0, colon) : w.remote_endpoint;
3093+
if (!ip.empty()) {
3094+
ip_connections[ip]++;
3095+
ip_workers_set[ip].insert(w.username);
3096+
}
3097+
30843098
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
3085-
std::chrono::steady_clock::now() - w.connected_at).count();
3099+
now_steady - w.connected_at).count();
3100+
auto first_seen_ts = static_cast<uint64_t>(now) - static_cast<uint64_t>(elapsed);
30863101

3087-
// Key by worker name: "ADDRESS.worker" (like p2pool) or just "ADDRESS" if no worker suffix
3102+
// Key by worker name: "ADDRESS.worker" (like p2pool)
30883103
std::string worker_key = w.worker_name.empty()
30893104
? w.username
30903105
: w.username + "." + w.worker_name;
@@ -3098,46 +3113,53 @@ nlohmann::json MiningInterface::rest_stratum_stats()
30983113
workers_json[worker_key]["accepted"] = workers_json[worker_key]["accepted"].get<uint64_t>() + w.accepted;
30993114
workers_json[worker_key]["rejected"] = workers_json[worker_key]["rejected"].get<uint64_t>() + w.rejected;
31003115
workers_json[worker_key]["stale"] = workers_json[worker_key]["stale"].get<uint64_t>() + w.stale;
3116+
workers_json[worker_key]["shares"] = workers_json[worker_key]["shares"].get<uint64_t>() + w.accepted + w.stale;
3117+
// Keep earliest first_seen
3118+
if (first_seen_ts < workers_json[worker_key]["first_seen"].get<uint64_t>())
3119+
workers_json[worker_key]["first_seen"] = first_seen_ts;
31013120
} else {
31023121
workers_json[worker_key] = {
3103-
{"hash_rate", w.hashrate},
3104-
{"dead_hash_rate", w.dead_hashrate},
3105-
{"connections", 1},
3106-
{"connection_difficulties", nlohmann::json::array({w.difficulty})},
3107-
{"last_seen", static_cast<uint64_t>(std::time(nullptr))},
3108-
{"difficulty", w.difficulty},
3109-
{"accepted", w.accepted},
3110-
{"rejected", w.rejected},
3111-
{"stale", w.stale},
3112-
{"connected_seconds", elapsed},
3113-
{"remote_endpoint", w.remote_endpoint}
3114-
};
3122+
{"hash_rate", w.hashrate},
3123+
{"dead_hash_rate", w.dead_hashrate},
3124+
{"connections", 1},
3125+
{"connection_difficulties", nlohmann::json::array({w.difficulty})},
3126+
{"last_seen", static_cast<uint64_t>(now)},
3127+
{"first_seen", first_seen_ts},
3128+
{"difficulty", w.difficulty},
3129+
{"accepted", w.accepted},
3130+
{"rejected", w.rejected},
3131+
{"stale", w.stale},
3132+
{"shares", w.accepted + w.stale},
3133+
{"connected_seconds", elapsed},
3134+
{"remote_endpoint", w.remote_endpoint}
3135+
};
31153136
}
31163137
}
31173138

3118-
result["difficulty"] = 1.0;
3119-
if (!workers.empty())
3120-
result["difficulty"] = workers.begin()->second.difficulty;
3121-
result["accepted_shares"] = total_accepted;
3122-
result["rejected_shares"] = total_rejected;
3123-
result["stale_shares"] = total_stale;
3124-
result["hashrate"] = total_hashrate;
3125-
result["active_workers"] = static_cast<int>(workers.size());
3126-
result["unique_addresses"] = static_cast<int>(unique_addrs.size());
3127-
result["workers"] = workers_json;
3128-
3129-
double total_shares = static_cast<double>(total_accepted + total_rejected + total_stale);
3139+
// Pool-level stats (stratum.html reads data.pool.*)
31303140
auto uptime = std::chrono::duration_cast<std::chrono::seconds>(
3131-
std::chrono::steady_clock::now() - m_stratum_start_time).count();
3132-
result["shares_per_minute"] = (uptime > 0) ? (total_shares * 60.0 / uptime) : 0.0;
3133-
result["last_share_time"] = static_cast<uint64_t>(std::time(nullptr));
3134-
result["uptime_seconds"] = static_cast<double>(uptime);
3135-
3136-
{
3137-
std::lock_guard<std::mutex> lock(m_control_mutex);
3138-
result["mining_enabled"] = m_mining_enabled;
3139-
result["banned_count"] = static_cast<uint64_t>(m_banned_targets.size());
3140-
}
3141+
now_steady - m_stratum_start_time).count();
3142+
double total_shares = static_cast<double>(total_accepted + total_rejected + total_stale);
3143+
double submission_rate = (uptime > 0) ? (total_shares / static_cast<double>(uptime)) : 0.0;
3144+
3145+
nlohmann::json ip_workers_json = nlohmann::json::object();
3146+
for (const auto& [ip, wset] : ip_workers_set)
3147+
ip_workers_json[ip] = static_cast<int>(wset.size());
3148+
3149+
result["pool"] = {
3150+
{"connections", static_cast<int>(workers.size())},
3151+
{"workers", static_cast<int>(unique_addrs.size())},
3152+
{"unique_addresses", static_cast<int>(unique_addrs.size())},
3153+
{"total_accepted", total_accepted},
3154+
{"total_rejected", total_rejected},
3155+
{"total_stale", total_stale},
3156+
{"submission_rate", submission_rate},
3157+
{"uptime", static_cast<double>(uptime)},
3158+
{"hashrate", total_hashrate},
3159+
{"ip_connections", ip_connections},
3160+
{"ip_workers", ip_workers_json}
3161+
};
3162+
result["workers"] = workers_json;
31413163

31423164
return result;
31433165
}

0 commit comments

Comments
 (0)