diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-02 15:23:02 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-02 15:23:02 +0000 |
| commit | 07051b2378734d62fb94a4a62819d8150452b661 (patch) | |
| tree | b20b912b18d5da1f524209bedda395d155fe11ef /src/imgui/imgui.cpp | |
| parent | f5def7625e6369d117cc8f02b7c0244b70d6f779 (diff) | |
pool: compute a miner's faded hashrate instead of decaying it in place
2bca62a decayed m->hashRate from the event loop's miner sweep, which
turned a figure that is only ever displayed into an accumulator, and got
the curve wrong at both ends.
Each pass recomputed `expected` -- and so `grace` -- from the estimate it
had just decayed. Every decay raised `expected`, which raised the grace
that gates the next decay, so `grace` chased `silent` upward and the
reported rate settled into a 1/t crawl instead of the half-life the
commit's own comment documents: 291x too high at ten half-lives past the
grace, and still announcing 0.4% of baseline four hours into a silence.
#7 is about the pool over-reporting its hashrate, and this under-corrected
it for hours.
The far end was worse. Once `expected` reached HASHRATE_MAX_INTERVAL it
could not rise any further, the feedback that had been gating each pass
was gone, and the decay did begin to compound -- once a second, against
the total elapsed silence, because lastShareTime does not advance while a
miner is quiet and nothing recorded when the decay had last run. A miner
four hours silent fell from 0.4% of its baseline to 1e-11 of it over the
next hundred seconds, with nothing in the code or the announced number to
explain the cliff.
Neither behaviour is worth tuning, so drop the sweep. hashRate is read in
exactly one place -- RefreshStats, building the per-worker rows and the
total that the pool status and the nostr announcement publish -- and
vardiff retargets from share timing alone and never reads it. So leave
the stored value as the sample it always was and have RefreshStats ask
MinerReportedHashRate() what to publish: a pure function of that sample
and the absolute silence since the last share. With no stored decay state
there is nothing for a tick rate, a missed pass or a repeated pass to
perturb, and the answer is bit-identical from 1/60 Hz to 1000 Hz because
nothing accumulates, not because the arithmetic was written carefully.
It also keeps the fade out of the estimator: the EWMA on the share path
still blends against the value it last computed rather than a faded copy
of it, so a miner that resumes recovers exactly as it did before any of
this, and there is no decay timestamp for a newly accepted or newly
authorised connection to be caught holding unset.
RefreshStats takes one GetTime() for the whole sweep, so the rows and the
total they are summed into describe a single instant rather than a smear
across it. The clamp is written !(expected > MIN) so a NaN lands on the
floor instead of sailing through; a negative `silent` from a clock
stepping backwards reports the sample unchanged rather than scaling it
up; and past HASHRATE_DECAY_CUTOFF half-lives the answer is a flat zero
rather than pow() walking down into the denormals. This also drops the
std::max(m->hashRate, 1e-9) the old pass carried, which the
`hashRate <= 0.0` guard immediately above it had already made dead.
pool-stratum gains ten checks over the policy: the full estimate inside
the grace, a thousand samples at one instant returning one value and
leaving the estimate untouched, a few percent lost one second past the
grace rather than the whole estimate, exactly half at one expected
interval past it, zero after an hour of silence, no inflation from a
backwards clock, zero for a miner that has no rate sample yet, and
nothing non-finite anywhere across a long silence.
Diffstat (limited to 'src/imgui/imgui.cpp')
0 files changed, 0 insertions, 0 deletions