Skip to content

Commit e4bc98a

Browse files
author
WillItMod
committed
AxeDGB 0.8.21: hashrate from accepted shares
1 parent 9f1910f commit e4bc98a

5 files changed

Lines changed: 114 additions & 37 deletions

File tree

willitmod-dev-dgb/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ services:
358358
- ${APP_DATA_DIR}/data/pool/config/coins.json:/app/coins.json:ro
359359

360360
app:
361-
image: ghcr.io/willitmod/axedgb-app-umbrel-dev:0.8.20
361+
image: ghcr.io/willitmod/axedgb-app-umbrel-dev:0.8.21
362362
user: "1000:1000"
363363
restart: on-failure
364364
stop_grace_period: 30s

willitmod-dev-dgb/images/axedgb-app/app.py

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def _env_or_default(name: str, default: str) -> str:
6363
SUPPORT_TICKET_URL = _env_or_default("SUPPORT_TICKET_URL", f"{DEFAULT_SUPPORT_BASE_URL}/api/support/upload")
6464

6565
APP_ID = "willitmod-dev-dgb"
66-
APP_VERSION = "0.8.20"
66+
APP_VERSION = "0.8.21"
6767

6868
DGB_RPC_HOST = os.getenv("DGB_RPC_HOST", "dgbd")
6969
DGB_RPC_PORT = int(os.getenv("DGB_RPC_PORT", "14022"))
@@ -205,11 +205,16 @@ def _pool_workers_from_db(pool_id: str):
205205
)
206206
rows = cur.fetchall() or []
207207

208-
# Pull "last share" timestamps from shares (minerstats.created is a snapshot timestamp, not the last share time).
209-
# Limit the scan window to keep it cheap even on busy pools.
208+
# Pull "last share" timestamps and an estimated hashrate from shares
209+
# (minerstats.created is a snapshot timestamp, not the last share time).
210+
# H/s over 10m ≈ sum(difficulty)*2^32 / 600.
210211
cur.execute(
211212
"""
212-
SELECT miner, worker, MAX(created) AS last_share
213+
SELECT
214+
miner,
215+
worker,
216+
MAX(created) AS last_share,
217+
COALESCE(SUM(CASE WHEN created >= (NOW() AT TIME ZONE 'utc') - INTERVAL '10 minutes' THEN difficulty ELSE 0 END), 0) AS sumdiff_10m
213218
FROM shares
214219
WHERE poolid = %s AND created >= (NOW() AT TIME ZONE 'utc') - INTERVAL '2 days'
215220
GROUP BY miner, worker
@@ -226,16 +231,22 @@ def _pool_workers_from_db(pool_id: str):
226231
pass
227232

228233
last_share_by_key: dict[tuple[str, str | None], datetime] = {}
234+
hashrate_hs_10m_by_key: dict[tuple[str, str | None], float] = {}
229235
for r in share_rows:
230236
try:
231-
miner, worker, last_share = r
237+
miner, worker, last_share, sumdiff_10m = r
232238
except Exception:
233239
continue
234-
if not isinstance(last_share, datetime):
235-
continue
236240
miner_s = str(miner or "")
237241
worker_s = str(worker or "").strip() or None
238-
last_share_by_key[(miner_s, worker_s)] = last_share
242+
if isinstance(last_share, datetime):
243+
last_share_by_key[(miner_s, worker_s)] = last_share
244+
try:
245+
sumdiff_f = float(sumdiff_10m) if sumdiff_10m is not None else 0.0
246+
if math.isfinite(sumdiff_f) and sumdiff_f > 0:
247+
hashrate_hs_10m_by_key[(miner_s, worker_s)] = (sumdiff_f * (2**32)) / (10 * 60)
248+
except Exception:
249+
pass
239250

240251
out = []
241252
for r in rows:
@@ -263,8 +274,12 @@ def _pool_workers_from_db(pool_id: str):
263274
except Exception:
264275
pass
265276

266-
hashrate_ths = None
267-
if hashrate_hs_f is not None:
277+
hashrate_hs_live = hashrate_hs_10m_by_key.get((miner_s, worker_s))
278+
hashrate_ths_live = (hashrate_hs_live / 1e12) if hashrate_hs_live is not None else None
279+
280+
# Prefer live estimate from accepted shares, fallback to Miningcore minerstats estimate.
281+
hashrate_ths = hashrate_ths_live
282+
if hashrate_ths is None and hashrate_hs_f is not None:
268283
hashrate_ths = hashrate_hs_f / 1e12
269284

270285
last_share = None
@@ -282,6 +297,8 @@ def _pool_workers_from_db(pool_id: str):
282297
"worker": worker_s,
283298
"hashrate_hs": hashrate_hs_f,
284299
"hashrate_ths": hashrate_ths,
300+
"hashrate_hs_live_10m": hashrate_hs_live,
301+
"hashrate_ths_live_10m": hashrate_ths_live,
285302
"lastShare": last_share,
286303
"sharesPerSecond": shares_per_s,
287304
}
@@ -1403,6 +1420,19 @@ def _pool_status(pool_id: str, *, algo: str | None = None):
14031420
except Exception:
14041421
share_health = {}
14051422

1423+
hashrate_ths_best_effort = hashrate_ths
1424+
hashrate_ths_live = None
1425+
try:
1426+
hs10m = share_health.get("hashrate_hs_10m")
1427+
hs10m_f = float(hs10m) if hs10m is not None else None
1428+
if hs10m_f is not None and math.isfinite(hs10m_f) and hs10m_f > 0:
1429+
hashrate_ths_live = hs10m_f / 1e12
1430+
except Exception:
1431+
hashrate_ths_live = None
1432+
1433+
if hashrate_ths_live is not None:
1434+
hashrate_ths = hashrate_ths_live
1435+
14061436
eta_seconds = None
14071437
try:
14081438
if hashrate_ths and network_difficulty:
@@ -1419,6 +1449,8 @@ def _pool_status(pool_id: str, *, algo: str | None = None):
14191449
"algo": algo,
14201450
"workers": workers_i,
14211451
"hashrate_ths": hashrate_ths,
1452+
"hashrate_ths_best_effort": hashrate_ths_best_effort,
1453+
"hashrate_ths_live_10m": hashrate_ths_live,
14221454
"total_blocks": total_blocks,
14231455
"network_difficulty": network_difficulty,
14241456
"network_height": network_height,
@@ -1476,6 +1508,8 @@ def _pool_status(pool_id: str, *, algo: str | None = None):
14761508
status.setdefault("shares_1h", None)
14771509
status.setdefault("last_share_at", None)
14781510
status.setdefault("eta_seconds", None)
1511+
status.setdefault("hashrate_ths_best_effort", None)
1512+
status.setdefault("hashrate_ths_live_10m", None)
14791513
status.setdefault("hashrates_ths", {})
14801514
return status
14811515

@@ -1495,6 +1529,8 @@ def _pool_status(pool_id: str, *, algo: str | None = None):
14951529
"shares_1h": None,
14961530
"last_share_at": None,
14971531
"eta_seconds": None,
1532+
"hashrate_ths_best_effort": None,
1533+
"hashrate_ths_live_10m": None,
14981534
"hashrates_ths": {},
14991535
"cached": False,
15001536
"lastSeen": int(time.time()),
@@ -2137,11 +2173,12 @@ def _pool_share_health(pool_id: str) -> dict:
21372173
try:
21382174
cur = conn.cursor()
21392175
cur.execute(
2140-
"SELECT COUNT(*) FROM shares WHERE poolid=%s AND created >= %s",
2176+
"SELECT COUNT(*), COALESCE(SUM(difficulty), 0) FROM shares WHERE poolid=%s AND created >= %s",
21412177
(pool_id, cutoff_10m),
21422178
)
21432179
row = cur.fetchone()
21442180
shares_10m = int(row[0]) if row and row[0] is not None else 0
2181+
sumdiff_10m = float(row[1]) if row and row[1] is not None else 0.0
21452182

21462183
cur.execute(
21472184
"SELECT COUNT(*) FROM shares WHERE poolid=%s AND created >= %s",
@@ -2165,7 +2202,21 @@ def _pool_share_health(pool_id: str) -> dict:
21652202
return {}
21662203

21672204
last_share_iso = _iso_z(last_share_created) if isinstance(last_share_created, datetime) else None
2168-
return {"shares_10m": shares_10m, "shares_1h": shares_1h, "last_share_at": last_share_iso}
2205+
# Estimate pool hashrate from accepted shares over the last 10 minutes:
2206+
# H/s ≈ sum(share_difficulty) * 2^32 / window_seconds
2207+
hashrate_hs_10m = None
2208+
try:
2209+
if sumdiff_10m and sumdiff_10m > 0:
2210+
hashrate_hs_10m = (sumdiff_10m * (2**32)) / (10 * 60)
2211+
except Exception:
2212+
hashrate_hs_10m = None
2213+
2214+
return {
2215+
"shares_10m": shares_10m,
2216+
"shares_1h": shares_1h,
2217+
"last_share_at": last_share_iso,
2218+
"hashrate_hs_10m": hashrate_hs_10m,
2219+
}
21692220

21702221

21712222
def _series_sampler(stop_event: threading.Event):

willitmod-dev-dgb/images/axedgb-app/static/app.js

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ function formatCompactNumber(v) {
6969
return `${n.toFixed(3)}`;
7070
}
7171

72+
function formatEta(seconds) {
73+
const s = Number(seconds);
74+
if (!Number.isFinite(s) || s <= 0) return '-';
75+
const m = Math.round(s / 60);
76+
if (m < 60) return `${m} min`;
77+
const h = Math.round(m / 60);
78+
if (h < 48) return `${h} hr`;
79+
const d = Math.round(h / 24);
80+
return `${d} days`;
81+
}
82+
7283
function clamp(n, min, max) {
7384
return Math.max(min, Math.min(max, n));
7485
}
@@ -349,7 +360,7 @@ function renderWorkerDetails(miners) {
349360
for (const m of list.slice(0, 50)) {
350361
const name = m.worker ? String(m.worker) : shortenMiner(m.miner);
351362
const sub = m.worker ? shortenMiner(m.miner) : '';
352-
const hr = formatHashrateFromTHS(m.hashrate_ths);
363+
const hr = formatHashrateFromTHS(m.hashrate_ths_live_10m != null ? m.hashrate_ths_live_10m : m.hashrate_ths);
353364
const last = formatAge(m.lastShare);
354365

355366
const left = `
@@ -479,14 +490,14 @@ async function refresh() {
479490
const pool = await fetchJson(`/api/pool?algo=${encodeURIComponent(algo)}`);
480491
document.getElementById('workers').textContent = pool.workers ?? '-';
481492
document.getElementById('hashrate').textContent = formatTHS(pool.hashrate_ths);
482-
const bestSince = pool && pool.best_difficulty_since_block;
483-
const bestAll = pool && pool.best_difficulty_all;
484-
const bestSinceEl = document.getElementById('bestdiff-since');
485-
const bestAllEl = document.getElementById('bestdiff-all');
486-
const bestSummaryEl = document.getElementById('bestdiff-summary');
487-
if (bestSinceEl) bestSinceEl.textContent = formatCompactNumber(bestSince);
488-
if (bestAllEl) bestAllEl.textContent = formatCompactNumber(bestAll);
489-
if (bestSummaryEl) bestSummaryEl.textContent = formatCompactNumber(bestSince);
493+
const etaEl = document.getElementById('eta');
494+
if (etaEl) etaEl.textContent = formatEta(pool && pool.eta_seconds);
495+
const etaSummary = document.getElementById('eta-summary');
496+
if (etaSummary) etaSummary.textContent = formatEta(pool && pool.eta_seconds);
497+
const sh10 = document.getElementById('shares-10m');
498+
const sh1h = document.getElementById('shares-1h');
499+
if (sh10) sh10.textContent = String(pool && pool.shares_10m != null ? pool.shares_10m : '-');
500+
if (sh1h) sh1h.textContent = String(pool && pool.shares_1h != null ? pool.shares_1h : '-');
490501
document.getElementById('workers-summary').textContent = pool.workers ?? '-';
491502
document.getElementById('hashrate-summary').textContent = formatTHS(pool.hashrate_ths);
492503

@@ -542,13 +553,16 @@ async function refresh() {
542553
document.getElementById('hashrate').textContent = '-';
543554
const lastShareEl = document.getElementById('last-share');
544555
if (lastShareEl) lastShareEl.textContent = '-';
556+
const etaEl = document.getElementById('eta');
557+
if (etaEl) etaEl.textContent = '-';
558+
const etaSummary = document.getElementById('eta-summary');
559+
if (etaSummary) etaSummary.textContent = '-';
560+
const sh10 = document.getElementById('shares-10m');
561+
const sh1h = document.getElementById('shares-1h');
562+
if (sh10) sh10.textContent = '-';
563+
if (sh1h) sh1h.textContent = '-';
545564
document.getElementById('workers-summary').textContent = '-';
546565
document.getElementById('hashrate-summary').textContent = '-';
547-
const bestIds = ['bestdiff-since', 'bestdiff-all', 'bestdiff-summary'];
548-
for (const id of bestIds) {
549-
const el = document.getElementById(id);
550-
if (el) el.textContent = '-';
551-
}
552566

553567
const diffEl = document.getElementById('difficulty');
554568
const diffSub = document.getElementById('difficulty-sub');

willitmod-dev-dgb/images/axedgb-app/static/index.html

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,12 @@
111111
<div class="axe-stat">
112112
<div class="axe-stat__k">Hashrate</div>
113113
<div class="axe-stat__v" id="hashrate-summary">-</div>
114-
<div class="axe-stat__k mt-1">TH/s (best-effort)</div>
114+
<div class="axe-stat__k mt-1">TH/s (accepted shares, 10m)</div>
115115
</div>
116116
<div class="axe-stat">
117-
<div class="axe-stat__k">Best difficulty</div>
118-
<div class="axe-stat__v" id="bestdiff-summary">-</div>
119-
<div class="axe-stat__k mt-1">Since last block</div>
117+
<div class="axe-stat__k">ETA to find block</div>
118+
<div class="axe-stat__v" id="eta-summary">-</div>
119+
<div class="axe-stat__k mt-1 text-slate-400">Based on pool hashrate</div>
120120
</div>
121121
</div>
122122
</section>
@@ -170,7 +170,7 @@
170170
<div class="axe-stat">
171171
<div class="axe-stat__k">Hashrate</div>
172172
<div class="axe-stat__v" id="hashrate">-</div>
173-
<div class="axe-stat__k mt-1">TH/s (best-effort)</div>
173+
<div class="axe-stat__k mt-1">TH/s (accepted shares, 10m)</div>
174174
<div class="mt-4 grid grid-cols-2 gap-2 md:grid-cols-4">
175175
<div class="rounded-xl border border-white/10 bg-black/35 px-3 py-2">
176176
<div class="axe-shadow-heavy text-[10px] font-extrabold uppercase tracking-wider text-slate-200">1m</div>
@@ -208,10 +208,22 @@
208208
<div class="axe-stat__k mt-1" id="difficulty-sub">-</div>
209209
</div>
210210
<div class="axe-stat">
211-
<div class="axe-stat__k">Best difficulty (since block)</div>
212-
<div class="axe-stat__v" id="bestdiff-since">-</div>
213-
<div class="axe-stat__k mt-1">Best difficulty (all-time)</div>
214-
<div class="axe-stat__v mt-1" id="bestdiff-all">-</div>
211+
<div class="axe-stat__k">ETA to find block</div>
212+
<div class="axe-stat__v" id="eta">-</div>
213+
<div class="axe-stat__k mt-1 text-slate-400">Based on pool hashrate</div>
214+
</div>
215+
<div class="axe-stat">
216+
<div class="axe-stat__k">Accepted shares</div>
217+
<div class="mt-2 grid grid-cols-2 gap-2">
218+
<div class="rounded-xl border border-white/10 bg-black/35 px-3 py-2">
219+
<div class="axe-shadow-heavy text-[10px] font-extrabold uppercase tracking-wider text-slate-200">10m</div>
220+
<div class="axe-shadow-heavy mt-1 font-mono text-sm text-white" id="shares-10m">-</div>
221+
</div>
222+
<div class="rounded-xl border border-white/10 bg-black/35 px-3 py-2">
223+
<div class="axe-shadow-heavy text-[10px] font-extrabold uppercase tracking-wider text-slate-200">1h</div>
224+
<div class="axe-shadow-heavy mt-1 font-mono text-sm text-white" id="shares-1h">-</div>
225+
</div>
226+
</div>
215227
</div>
216228
</div>
217229
</div>

willitmod-dev-dgb/umbrel-app.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ manifestVersion: 1
22
id: willitmod-dev-dgb
33
category: altcoin
44
name: AxeDGB
5-
version: "0.8.20"
5+
version: "0.8.21"
66
tagline: DGB node + solo pool
77
description: >-
88
Run a DigiByte full node (DigiByte Core) and a solo Stratum v1 pool (Miningcore) as a single Umbrel app installation.

0 commit comments

Comments
 (0)