Skip to content

Let a miner ask for its own difficulty, as a floor it cannot go under - #74

Open
Wired4ncer wants to merge 1 commit into
LayerTwo-Labs:mainfrom
Wired4ncer:up/miner-difficulty-request
Open

Let a miner ask for its own difficulty, as a floor it cannot go under#74
Wired4ncer wants to merge 1 commit into
LayerTwo-Labs:mainfrom
Wired4ncer:up/miner-difficulty-request

Conversation

@Wired4ncer

Copy link
Copy Markdown
Contributor

A miner has no way to influence its difficulty today. mining.suggest_difficulty is a standard stratum method and isn't handled, and the password field is ignored — so an ESP-Miner, which sends suggest_difficulty unprompted on connect, is silently disregarded on every pool running this.

Two channels, one meaning:

  • d=<n> in the stratum password, matched at a token boundary so id=7 isn't a request. cgminer, bosminer, Vnish and LuxOS all expose this field, which makes it the request channel with the widest reach.
  • mining.suggest_difficulty, the formal method. Either can arrive before or after authorize; both end up in the same place.

It's a floor, never a pin

Vardiff may raise the connection above it and the network-difficulty clamp still wins over it, but nothing lowers the connection below it.

A pin would be a denial-of-service hole: d=1 from a 400 TH/s miner is ~93,000 shares/sec aimed at the share pipeline. As a floor that same request is inert — max(1, whatever vardiff chose) is just vardiff's answer — while a miner asking to go higher, which is the real request, gets exactly what it asked for.

The floor is re-applied inside the vardiff retarget, not only at authorize. Without that the request lasts exactly one window: vardiff sees a rate under target — which is the point of a higher difficulty — and drags it straight back down.

Why a miner needs this

Vardiff tunes each connection toward vardiff_target_spm, but a proxied fleet spreads one rig across many connections, so the rig sees target_spm × N. At a uniform difficulty the rig's total share rate is H/(D·2³²) — the connection count cancels — so letting the miner name D is the one lever that works regardless of how its hashrate is split.

max_suggested_diff, default 5e7

<= 0 disables requests rather than uncapping them. "No ceiling" isn't a sane reading of a limit set to zero, and an operator who wants the feature off needs a way to say so. It still logs what was asked for, so you can measure how many of your miners already send d= out of habit from other pools before letting it change anything.

⚠️ Size that cap against idle_timeout_authorized_sec rather than taste. A request lengthens the connection's expected share interval and the reaper measures inbound silence: at 50M a 25 TH/s connection expects a share roughly every 8600s, past the 7200s default — it'd be reaped while mining perfectly well.

Interaction with listener policy

A request is clamped by the network ceiling, max_suggested_diff, and the listener's pol_vardiff_max. It's also raised to pol_min_diff by clamp_assigned_difficulty, so on a listener that promises a floor, a lowball request degrades to that floor rather than being refused — deliberate, and worth knowing.

At retarget the miner's floor is applied before the listener clamps, so listener policy outranks the miner's wish.

Tests

Six. The load-bearing one is that d=1 does not lower a connection — mutation-verified: turning the floor into an assignment fails it. The zero-cap test is the negative control for the cap test; without it, "capped" and "feature switched off" are indistinguishable from one passing assertion.

Note on threading

No lock is taken. c->difficulty has a single writer — the connection's own thread — and these writes are on that thread. The broadcast thread already reads it unsynchronized in conn_record_job_difficulty, racing the existing vardiff write exactly as it races these, so locking here alone would guard nothing. Flagged in a comment because anyone adding a broadcast-side write would break the property this relies on.

A miner has no way to influence its difficulty today. `mining.suggest_difficulty`
is a standard stratum method and is not handled, and the password field is
ignored — so an ESP-Miner, which sends suggest_difficulty unprompted on connect,
is silently disregarded on every pool running this.

Two channels, one meaning:

  * `d=<n>` in the stratum password, matched at a token boundary so `id=7` is
    not a request. cgminer, bosminer, Vnish and LuxOS all expose this field,
    which makes it the request channel with the widest reach.
  * `mining.suggest_difficulty`, the formal method, for clients that can send
    it. Either may arrive before or after authorize; both end up in the same
    place.

⛔ It is a FLOOR, never a pin. Vardiff may raise the connection above it and the
network-difficulty clamp still wins over it, but nothing lowers the connection
below it. A pin would be a denial-of-service hole: `d=1` from a 400 TH/s miner
is ~93,000 shares/sec at the share pipeline. As a floor that same request is
inert — max(1, whatever vardiff chose) is just vardiff's answer — while a miner
asking to go HIGHER, which is the real request, gets exactly what it asked for.

The floor is re-applied inside the vardiff retarget, not only at authorize.
Without that the request lasts exactly one window: vardiff sees a rate under
target, which is the POINT of a higher difficulty, and drags it straight back
down.

Why a miner needs this at all: vardiff tunes each CONNECTION toward
vardiff_target_spm, but a proxied fleet spreads one rig across many connections,
so the rig sees target_spm x N. At a uniform difficulty the rig's total share
rate is H/(D*2^32) — the connection count cancels — so letting the miner name D
is the one lever that works regardless of how its hashrate is split.

max_suggested_diff caps a request, default 5e7. <= 0 DISABLES requests rather
than uncapping them: "no ceiling" is not a sane reading of a limit set to zero,
and an operator who wants the feature off needs a way to say so. It still logs
what was asked for, so you can measure how many miners already send `d=` out of
habit before letting it change anything.

⚠️ Size that cap against your idle timeout rather than taste. A request lengthens
the connection's expected share interval, and an idle reaper measures inbound
silence: at 50M a 25 TH/s connection expects a share roughly every 8600s and
would be reaped from a 3600s budget while mining perfectly well.

Six tests. The load-bearing one is that `d=1` does NOT lower a connection —
mutation-verified: turning the floor into an assignment fails it. The
zero-cap test is the negative control for the cap test; without it "capped" and
"switched off" are indistinguishable from one passing assertion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant