Skip to content

Commit d8e0f7f

Browse files
committed
Merge branch 'dashboard-refinements'
2 parents fc1cff7 + 3d03668 commit d8e0f7f

6 files changed

Lines changed: 1735 additions & 163 deletions

File tree

src/c2pool/c2pool_refactored.cpp

Lines changed: 207 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2754,16 +2754,21 @@ int main(int argc, char* argv[]) {
27542754
d.version_counts[std::to_string(s->version)] = 1;
27552755
d.desired_version_counts[std::to_string(s->m_desired_version)] = 1;
27562756

2757+
auto target = chain::bits_to_target(s->m_bits);
2758+
d.difficulty_sum = chain::target_to_difficulty(target);
2759+
2760+
// Work-weighted desired version (matches p2pool's target_to_average_attempts weighting)
2761+
auto att = chain::target_to_average_attempts(target);
2762+
d.desired_version_weights[std::to_string(s->m_desired_version)] =
2763+
static_cast<double>(att.GetLow64());
2764+
27572765
std::string miner;
27582766
if constexpr (requires { s->m_address; })
27592767
miner = HexStr(s->m_address.m_data);
27602768
else if constexpr (requires { s->m_pubkey_hash; })
27612769
miner = s->m_pubkey_hash.GetHex();
27622770
if (!miner.empty())
27632771
d.miner_counts[miner] = 1;
2764-
2765-
auto target = chain::bits_to_target(s->m_bits);
2766-
d.difficulty_sum = chain::target_to_difficulty(target);
27672772
});
27682773
} catch (...) {}
27692774
return d;
@@ -2805,6 +2810,48 @@ int main(int argc, char* argv[]) {
28052810
static_cast<int>(ltc::PoolConfig::chain_length()));
28062811
auto sr = stats_skiplist->query(best, walk);
28072812

2813+
// Sampling window: oldest CHAIN_LENGTH/10 shares in the active chain
2814+
// Matches p2pool consensus: get_nth_parent(tip, CHAIN_LENGTH*9//10) then count CHAIN_LENGTH//10
2815+
int chain_len = static_cast<int>(ltc::PoolConfig::chain_length());
2816+
int chain_ht = best.IsNull() ? 0 : static_cast<int>(chain.get_height(best));
2817+
int skip_count = std::min(chain_ht, chain_len * 9 / 10);
2818+
int sample_count = std::min(chain_ht - skip_count, chain_len / 10);
2819+
2820+
// Walk active chain: find sampling start hash AND track V36 propagation
2821+
uint256 sampling_start;
2822+
int deepest_v36_pos = 0;
2823+
int v36_contiguous_from_tip = 0;
2824+
bool contiguous = true;
2825+
int full_walk = std::min(chain_ht, chain_len);
2826+
{
2827+
uint256 pos = best;
2828+
for (int i = 0; i < full_walk && !pos.IsNull(); ++i) {
2829+
if (!chain.contains(pos)) break;
2830+
if (i == skip_count) sampling_start = pos;
2831+
try {
2832+
auto& cd = chain.get(pos);
2833+
cd.share.invoke([&](auto* s) {
2834+
if (static_cast<int>(s->m_desired_version) >= 36) {
2835+
deepest_v36_pos = i + 1;
2836+
if (contiguous) v36_contiguous_from_tip = i + 1;
2837+
} else if (contiguous) {
2838+
contiguous = false;
2839+
}
2840+
pos = s->m_prev_hash;
2841+
});
2842+
} catch (...) { break; }
2843+
}
2844+
}
2845+
result["deepest_v36_position"] = deepest_v36_pos;
2846+
result["v36_contiguous_from_tip"] = v36_contiguous_from_tip;
2847+
2848+
// Query the oldest CHAIN_LENGTH/10 shares from that position
2849+
auto sampling_sr = (sample_count > 0 && !sampling_start.IsNull())
2850+
? stats_skiplist->query(sampling_start, sample_count)
2851+
: chain::StatsResult{};
2852+
result["sampling_desired_version"] = sampling_sr.desired_version_weights;
2853+
result["sampling_total"] = sampling_sr.share_count;
2854+
28082855
result["total_shares"] = sr.share_count;
28092856
result["orphan_shares"] = sr.orphan_count;
28102857
result["dead_shares"] = sr.dead_count;
@@ -3024,9 +3071,166 @@ int main(int argc, char* argv[]) {
30243071
result["blocks"] = std::move(blocks_arr);
30253072
result["doge_blocks"] = std::move(doge_arr);
30263073
result["total"] = static_cast<int>(chain.size());
3074+
// Include per-share PPLNS + current as fallback
3075+
if (mi) {
3076+
result["pplns_current"] = mi->rest_current_payouts();
3077+
// Per-share PPLNS from cache (available for shares since server start)
3078+
nlohmann::json pplns_map = nlohmann::json::object();
3079+
for (const auto& s : result["shares"]) {
3080+
std::string sh = s["h"].get<std::string>();
3081+
auto p = mi->get_pplns_for_tip(sh);
3082+
if (!p.empty()) pplns_map[sh] = std::move(p);
3083+
}
3084+
if (!pplns_map.empty()) result["pplns"] = std::move(pplns_map);
3085+
}
3086+
return result;
3087+
});
3088+
3089+
// Lightweight tip endpoint for RealTime polling
3090+
web_server.get_mining_interface()->set_sharechain_tip_fn([&p2p_node]() {
3091+
auto& chain = p2p_node->tracker().chain;
3092+
uint256 best;
3093+
int32_t best_height = -1;
3094+
for (const auto& [head_hash, tail_hash] : chain.get_heads()) {
3095+
auto h = chain.get_height(head_hash);
3096+
if (h > best_height) { best = head_hash; best_height = h; }
3097+
}
3098+
return nlohmann::json::object({
3099+
{"hash", best.IsNull() ? "" : best.GetHex().substr(0, 16)},
3100+
{"height", best_height},
3101+
{"total", static_cast<int>(chain.size())}
3102+
});
3103+
});
3104+
3105+
// Delta endpoint: return only shares newer than `since` hash
3106+
web_server.get_mining_interface()->set_sharechain_delta_fn(
3107+
[&p2p_node, &web_server](const std::string& since_hash) {
3108+
nlohmann::json result;
3109+
auto& chain = p2p_node->tracker().chain;
3110+
auto& verified = p2p_node->tracker().verified;
3111+
bool testnet = ltc::PoolConfig::is_testnet;
3112+
3113+
uint256 best;
3114+
int32_t best_height = -1;
3115+
for (const auto& [head_hash, tail_hash] : chain.get_heads()) {
3116+
auto h = chain.get_height(head_hash);
3117+
if (h > best_height) { best = head_hash; best_height = h; }
3118+
}
3119+
3120+
auto mi = web_server.get_mining_interface();
3121+
std::string fee_addr;
3122+
if (mi) fee_addr = mi->get_node_fee_hash160();
3123+
3124+
nlohmann::json shares_arr = nlohmann::json::array();
3125+
int count = 0;
3126+
3127+
if (!best.IsNull()) {
3128+
int walk = std::min(static_cast<int>(chain.get_height(best)),
3129+
static_cast<int>(ltc::PoolConfig::chain_length()));
3130+
try {
3131+
auto view = chain.get_chain(best, walk);
3132+
for (auto [hash, data] : view) {
3133+
std::string short_h = hash.GetHex().substr(0, 16);
3134+
// Stop when we reach the share the client already has
3135+
if (short_h == since_hash || hash.GetHex() == since_hash)
3136+
break;
3137+
3138+
nlohmann::json s;
3139+
s["h"] = short_h;
3140+
s["H"] = hash.GetHex();
3141+
s["p"] = count;
3142+
s["v"] = verified.contains(hash) ? 1 : 0;
3143+
3144+
auto* idx = chain.get_index(hash);
3145+
if (idx && idx->is_block_solution)
3146+
s["blk"] = 1;
3147+
3148+
data.share.invoke([&](auto* obj) {
3149+
s["t"] = obj->m_timestamp;
3150+
s["V"] = obj->version;
3151+
s["s"] = static_cast<int>(obj->m_stale_info);
3152+
s["b"] = obj->m_bits;
3153+
s["a"] = obj->m_absheight;
3154+
s["dv"] = obj->m_desired_version;
3155+
3156+
auto script = get_share_script(obj);
3157+
std::string addr = core::script_to_address(script, true, testnet);
3158+
s["m"] = addr.empty() ? HexStr(script) : addr;
3159+
3160+
if (!obj->m_coinbase.m_data.empty()) {
3161+
std::string best_run, cur_run;
3162+
for (auto c : obj->m_coinbase.m_data) {
3163+
if (c >= 32 && c <= 126) cur_run += static_cast<char>(c);
3164+
else { if (cur_run.size() > best_run.size()) best_run = cur_run; cur_run.clear(); }
3165+
}
3166+
if (cur_run.size() > best_run.size()) best_run = cur_run;
3167+
bool has_letter = false;
3168+
for (auto c : best_run) if ((c>='A'&&c<='Z')||(c>='a'&&c<='z')) { has_letter=true; break; }
3169+
if (best_run.size() >= 10 && has_letter) {
3170+
if (best_run.size() > 48) best_run.resize(48);
3171+
s["cb"] = best_run;
3172+
}
3173+
}
3174+
3175+
if (!fee_addr.empty() && script.size() >= 22) {
3176+
int off = -1;
3177+
if (script.size()==25 && script[0]==0x76) off=3;
3178+
else if (script.size()==22 && script[0]==0x00) off=2;
3179+
else if (script.size()==23 && script[0]==0xa9) off=2;
3180+
if (off >= 0) {
3181+
std::string h160 = HexStr(std::vector<unsigned char>(
3182+
script.begin()+off, script.begin()+off+20));
3183+
if (h160 == fee_addr) s["fee"] = 1;
3184+
}
3185+
}
3186+
});
3187+
shares_arr.push_back(std::move(s));
3188+
if (++count >= 200) break; // safety cap
3189+
}
3190+
} catch (...) {}
3191+
}
3192+
3193+
// Updated heads/blocks for the client to merge
3194+
nlohmann::json heads_arr = nlohmann::json::array();
3195+
for (auto& [hh, _] : chain.get_heads())
3196+
heads_arr.push_back(hh.GetHex().substr(0, 16));
3197+
3198+
nlohmann::json blocks_arr = nlohmann::json::array();
3199+
if (mi) {
3200+
for (const auto& fb : mi->get_found_blocks())
3201+
if (!fb.share_hash.empty()) blocks_arr.push_back(fb.share_hash.substr(0, 16));
3202+
}
3203+
nlohmann::json doge_arr = nlohmann::json::array();
3204+
if (mi) {
3205+
auto* mm = mi->get_mm_manager();
3206+
if (mm) for (const auto& db : mm->get_discovered_blocks())
3207+
if (!db.parent_hash.empty()) doge_arr.push_back(db.parent_hash.substr(0, 16));
3208+
}
3209+
3210+
result["shares"] = std::move(shares_arr);
3211+
result["count"] = count;
3212+
result["tip"] = best.IsNull() ? "" : best.GetHex().substr(0, 16);
3213+
result["heads"] = std::move(heads_arr);
3214+
result["blocks"] = std::move(blocks_arr);
3215+
result["doge_blocks"] = std::move(doge_arr);
3216+
3217+
// Include per-share PPLNS snapshots from server cache
3218+
// Each share's tip had a unique PPLNS computed at arrival time
3219+
if (count > 0 && mi) {
3220+
nlohmann::json pplns_map = nlohmann::json::object();
3221+
for (const auto& s : result["shares"]) {
3222+
std::string sh = s["h"].get<std::string>();
3223+
auto p = mi->get_pplns_for_tip(sh);
3224+
if (!p.empty()) pplns_map[sh] = std::move(p);
3225+
}
3226+
result["pplns"] = std::move(pplns_map);
3227+
}
30273228
return result;
30283229
});
30293230

3231+
// Start background PPLNS pre-computation (separate thread, waits for sync)
3232+
web_server.get_mining_interface()->start_pplns_precompute();
3233+
30303234
// Wire individual share lookup for /web/share/<hash> detail page
30313235
web_server.get_mining_interface()->set_share_lookup_fn(
30323236
[&p2p_node, &web_server](const std::string& hash_hex) -> nlohmann::json {

0 commit comments

Comments
 (0)