Pool vardiff: the idle easing no longer consumes the sized first correction; docs/plans/pool.md 9.3: the re-run's close and the member rows

pm-1, 7 October 2026: the member was idle for four intervals while its worker compiled, the first-phase idle easing
set first_done, and the measured 190 shares a second then walked down one step per 30 s for 5.5 minutes (the share
check at 10 to 11 ms on pool-1's cores). The sized correction now stays owed through idle easings (test
an_idle_easing_does_not_consume_the_sized_first_correction). Section 9.3: 207 found / 144 confirmed / 56 own orphans
under 2f6c0358, the residual as the node's 1 to 1.6 s template latency (a node-lane row), the member findings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 06:41:12 +00:00
parent 6b05d53162
commit 918eef942f
2 changed files with 62 additions and 3 deletions

View file

@ -289,3 +289,40 @@ Consequences per tier: a pool operator needs a box whose memory serves the 1 GiB
verifies about 100 shares a second per core at 9.8 ms; at one share per 10 s per member that is 1,000 members per
core, so the verifier is not the limit once vardiff holds); a member on any card is unaffected by any of this; the
chain walk now costs the node at most 600 `get_block` calls per 5 s on its own connection.
### 9.3 The re-run's close (06:33:33Z) and the member rows from its logs
After 2f6c0358 (06:20:34Z to 06:33:33Z, 13 minutes, four members): 207 blocks found, 144 confirmed, 69 orphaned (13
inherited pending at the swap, 56 on its own jobs: a 27 percent residual at 1 bps), 20,404 shares accepted, 0 rejected,
0 mismatches, 0 refused, every STATUS `node ok`, the gRPC connection churn gone (ids #3 to #5 once each in a minute; the
193 connections in 150 s were under f808b3f3's pruning-point walk), re-subscribe lines 2 (one per daemon start: the
re-subscribe loop was never the churn). Before it, f808b3f3 had worked through the walk by about 06:14Z: 245 blocks,
69 confirmed, 163 orphaned. First CONFIRMED line 06:24:32Z, 4.80 IGN to the finder. Logs: `~/Desktop/fleet/pool-run-0607/`.
The residual orphans are template latency: the daemon's STATUS reads `last template 1 s ago` and the solo miner on the
same node reads `template_ms=1632`; pool-1's node answers a template request in 1 to 1.6 s although its mining manager
answers a per-member request from its cache by a coinbase rewrite (`modify_block_template`) and its prewarm builds in
0.6 ms. At 1 bps a template that arrives 1.6 s old loses about a quarter of the blocks found on it, pool or solo. That
latency is the node's RPC path, a row for the node lane (measure `get_block_template` round trips on a devnet node under
a miner and a pool; the suspect is the RPC service's `pow_epoch` derivation per request). 82 "RPC request timeout" lines
after the 2f6c0358 swap are the gRPC client's own 5 s request timeout on that path; `--template-timeout-s` cannot
lengthen it.
From pm-1's log (1.09 GB): 4,342,515 lines are `worker: error N epoch seed mismatch: this worker holds epoch eea66ce7...`,
one per re-queued job, for the 40 s the worker compiled the new epoch's pack (NVRTC 40,327 ms) and again after every
reconnect, because a session respawned the worker (a second process beside the first, the same compile again) and
the member fed jobs for a pair the worker did not hold yet. Vardiff from the same log: shift 10 to 11 (idle easing
while the worker compiled, which consumed the sized first correction), then 11 down to 0 one step per 30 s over 5.5
minutes at about 190 shares a second, the load that put the share check at 10 to 11 ms on pool-1's cores.
Fixed in the member (fork commit after b8070476): one worker process per run, jobs held for a pair whose prepare is
pending and never re-prepared once held (`WorkerMemory`, test `jobs_are_held_while_a_pair_is_being_prepared`), worker
error lines summarised (one per class, then a count per 1,000), `set_target` applied to the current work at once.
Fixed in the pool (`vardiff.rs`): the idle easing no longer consumes the sized first correction (test
`an_idle_easing_does_not_consume_the_sized_first_correction`).
Consequences per tier: a member on a 3070-class card loses 40 s of hash at every epoch roll to the NVRTC compile unless
its pack is prepared ahead (the pool's `seeds` line carries `next_epoch_seed` for that, the member's prepare-ahead is
the next member row); a member on a reconnect now keeps its worker and its compiled pair; a pool operator's verifier
sees the sized correction within 10 s of a member's first shares (one share per 10 s per member from then on) instead of
5 minutes of a 190-share-a-second flood; the 10-member load figure is still owed and now has a clean form to run in.

View file

@ -79,8 +79,11 @@ impl Vardiff {
return None;
}
if n == 0 {
// nothing yet after an interval: wait up to three intervals, then one easier step
if (now_s - self.started_s) < 3.0 * self.interval_s {
// nothing yet after an interval: wait up to three intervals, then one easier step. The sized
// correction stays owed (first_done stays false): 7 October 2026, pm-1's worker compiled its pack for
// 40 s, the idle easing consumed the first correction, and the measured 190 shares a second then
// walked down one step per 30 s for 5.5 minutes, saturating the verifier
if now_s - self.last_change_s.max(self.started_s) < 3.0 * self.interval_s {
return None;
}
self.shift = (self.shift + 1).min(cap);
@ -92,8 +95,8 @@ impl Vardiff {
} else if per_interval < 0.66 {
self.shift = (self.shift + steps).min(cap);
}
self.first_done = true;
}
self.first_done = true;
} else {
if since_change < self.min_change_s {
return None;
@ -226,4 +229,23 @@ mod tests {
}
assert_eq!(v.retarget(t, 1 << 20), Some(2), "floor at min_shift");
}
/// 7 October 2026, pm-1: a member idle for four intervals (its worker compiling) gets eased one step, and the
/// first measured rate must still size the jump (190 shares a second at shift 11 is 11 steps away from one per
/// 10 s; the sized step takes 8 at once), not walk down one step per 30 s.
#[test]
fn an_idle_easing_does_not_consume_the_sized_first_correction() {
let t64 = 1u64 << 36;
let mut v = Vardiff::new(10, 0, 20, 10.0, 0.0);
assert_eq!(v.retarget(31.0, t64), Some(11), "idle three intervals: one easier step");
assert_eq!(v.retarget(40.0, t64), None, "still idle, nothing within the next three intervals");
// the worker is ready: 190 shares a second for 10 s
let mut t = 40.0;
for _ in 0..1900 {
t += 10.0 / 1900.0;
v.on_share(t);
}
let s = v.retarget(t + 0.1, t64).expect("the first measured rate sizes the jump");
assert!(s <= 3, "sized first correction from 11 took at most 8 steps at once, got shift {s}");
}
}