Skip to content

Commit e95fc7e

Browse files
authored
web: auto-detected per-node topology endpoint + per-coin stats seam (D0.2/D0.3) (#324)
Adds GET /api/node_topology, the truthful per-node topology surface for the 6-coin fleet (LTC/DOGE/BTC/DGB/DASH/BCH). Each node runs a different coin set + embedded daemons; the dashboard must reflect THAT node's REAL shape, not a hardcoded LTC+DOGE assumption. D0.2 (auto-detect): rest_node_topology() derives the coin set from live state -- the keys of the coin-peer map (an LTC node also runs embedded DOGE, etc.) plus this node's own chain (m_blockchain) -- and labels the primary coin with the real is_embedded()/has_rpc() flags from the connected coin node. No baked -in coin shape. D0.3 (seam): optional node_topology_fn provider hook. When the wiring layer registers one it supplies richer per-coin stats (synced flag, per-coin hashrate) and overrides the auto-detect; when unset the auto-detect keeps the endpoint truthful. Clean override-or-detect seam for each coin to feed its own real stats. Purely additive. Web/diagnostic layer only -- no consensus, sharechain, or wire change. web_server.cpp.o compiles clean. Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
1 parent c756ff8 commit e95fc7e

2 files changed

Lines changed: 85 additions & 0 deletions

File tree

src/core/web_server.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,8 @@ void HttpSession::process_request()
380380
rest_result = mining_interface_->rest_stale_rates();
381381
else if (target == "/node_info")
382382
rest_result = mining_interface_->rest_node_info();
383+
else if (target == "/api/node_topology")
384+
rest_result = mining_interface_->rest_node_topology();
383385
else if (target == "/luck_stats")
384386
rest_result = mining_interface_->rest_luck_stats();
385387
else if (target == "/ban_stats")
@@ -5107,6 +5109,79 @@ void MiningInterface::auto_detect_external_info()
51075109
// with raw TCP sockets).
51085110
}
51095111

5112+
// D0.2/D0.3 -- truthful per-node topology. Each node in the 6-coin fleet
5113+
// (LTC/DOGE/BTC/DGB/DASH/BCH) runs a different coin set + embedded daemons;
5114+
// the dashboard must reflect THAT node's REAL shape, auto-detected from live
5115+
// state -- never a hardcoded LTC+DOGE shape. Web/diagnostic layer only.
5116+
nlohmann::json MiningInterface::rest_node_topology()
5117+
{
5118+
// D0.3 seam: prefer the per-coin StatsProvider hook when the wiring layer
5119+
// feeds one (richer -- per-coin synced flag + hashrate). Falls back to the
5120+
// D0.2 auto-detect below when unset, so the endpoint is always truthful.
5121+
if (m_node_topology_fn) {
5122+
auto t = m_node_topology_fn();
5123+
if (!t.is_null())
5124+
return t;
5125+
}
5126+
5127+
auto upper = [](std::string str) {
5128+
for (auto& ch : str)
5129+
if (ch >= 'a' && ch <= 'z') ch = static_cast<char>(ch - 32);
5130+
return str;
5131+
};
5132+
5133+
// This node's own chain.
5134+
std::string primary;
5135+
switch (m_blockchain) {
5136+
case Blockchain::LITECOIN: primary = "LTC"; break;
5137+
case Blockchain::BITCOIN: primary = "BTC"; break;
5138+
case Blockchain::DOGECOIN: primary = "DOGE"; break;
5139+
case Blockchain::DASH: primary = "DASH"; break;
5140+
}
5141+
5142+
nlohmann::json coins = nlohmann::json::array();
5143+
auto has_coin = [&](const std::string& sym) {
5144+
for (const auto& c : coins)
5145+
if (c.value("coin", std::string{}) == sym) return true;
5146+
return false;
5147+
};
5148+
auto add_coin = [&](const std::string& sym, bool is_primary, int peers) {
5149+
if (sym.empty() || has_coin(sym)) return;
5150+
nlohmann::json entry = nlohmann::json::object();
5151+
entry["coin"] = sym;
5152+
entry["primary"] = is_primary;
5153+
entry["peers"] = peers;
5154+
if (is_primary) {
5155+
// Real embedded/RPC flags for this node's own daemon.
5156+
entry["embedded"] = (m_coin_node && m_coin_node->is_embedded());
5157+
entry["has_rpc"] = (m_coin_node && m_coin_node->has_rpc());
5158+
}
5159+
coins.push_back(std::move(entry));
5160+
};
5161+
5162+
// Auto-detect the embedded/aux coin set from the live coin-peer map keys
5163+
// (e.g. an LTC node also runs embedded DOGE) -- the node's REAL shape, not
5164+
// a baked-in assumption.
5165+
nlohmann::json peer_map = m_coin_peers_fn ? m_coin_peers_fn()
5166+
: nlohmann::json::object();
5167+
if (peer_map.is_object()) {
5168+
for (auto it = peer_map.begin(); it != peer_map.end(); ++it) {
5169+
std::string sym = upper(it.key());
5170+
int peers = it.value().is_array()
5171+
? static_cast<int>(it.value().size()) : 0;
5172+
add_coin(sym, sym == primary, peers);
5173+
}
5174+
}
5175+
// Guarantee the primary chain is always present even if absent from the map.
5176+
add_coin(primary, true, 0);
5177+
5178+
nlohmann::json result = nlohmann::json::object();
5179+
result["node_symbol"] = primary;
5180+
result["auto_detected"] = true;
5181+
result["coins"] = coins;
5182+
return result;
5183+
}
5184+
51105185
nlohmann::json MiningInterface::rest_node_info()
51115186
{
51125187
nlohmann::json result = nlohmann::json::object();

src/core/web_server.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,15 @@ class MiningInterface : public jsonrpccxx::JsonRpc2Server,
506506
void set_coin_peers_fn(coin_peers_fn_t fn) { m_coin_peers_fn = std::move(fn); }
507507
nlohmann::json rest_coin_peers(const std::string& remote_ip);
508508

509+
// D0.3 per-coin stats seam + D0.2 auto-detect. node_topology_fn, when set
510+
// by the wiring layer, feeds the real per-coin embedded-daemon topology
511+
// (coin label, embedded/has_rpc/synced, peers, hashrate). When unset,
512+
// rest_node_topology() auto-detects the node's real coin set from the live
513+
// coin-peer map + m_blockchain -- never a hardcoded {ltc,doge} shape.
514+
using node_topology_fn_t = std::function<nlohmann::json()>;
515+
void set_node_topology_fn(node_topology_fn_t fn) { m_node_topology_fn = std::move(fn); }
516+
nlohmann::json rest_node_topology(); // /api/node_topology
517+
509518
// Sharechain stats callback — returns live tracker data for the /sharechain/stats endpoint
510519
using sharechain_stats_fn_t = std::function<nlohmann::json()>;
511520
void set_sharechain_stats_fn(sharechain_stats_fn_t fn) { m_sharechain_stats_fn = thread_safe_wrap(std::move(fn)); }
@@ -1016,6 +1025,7 @@ class MiningInterface : public jsonrpccxx::JsonRpc2Server,
10161025
sharechain_stats_fn_t m_sharechain_stats_fn;
10171026
spv_progress_fn_t m_spv_progress_fn;
10181027
coin_peers_fn_t m_coin_peers_fn;
1028+
node_topology_fn_t m_node_topology_fn; // D0.3 per-coin stats provider (optional)
10191029
// Rate limiter for /api/coin_peers: IP → last request time
10201030
std::map<std::string, std::chrono::steady_clock::time_point> m_coin_peers_rate_limit;
10211031
sharechain_window_fn_t m_sharechain_window_fn;

0 commit comments

Comments
 (0)