diff --git a/docs/spec/09-pool-protocol.md b/docs/spec/09-pool-protocol.md index e9c8abd1f..521a92ab3 100644 --- a/docs/spec/09-pool-protocol.md +++ b/docs/spec/09-pool-protocol.md @@ -62,7 +62,7 @@ Three modes. The pool announces which it supports in `welcome`; every conforming A conforming member MUST verify, against its verifier, every item below, and MUST refuse the job (`job_refused` with the code) when one fails: -1. `vote_key_hash` equals the hash of the member's own key. +1. `vote_key_hash` equals the hash of the member's own key, in the template's header and in the `job` line that carries it (Igneum 2.0, 8 October 2026, `docs/design/pool-vote-key-commitment.md` section 2.2: the job names the key so a member checks it before it holds the template, and a job that names another key is refused with code `vote_key` before any other check). 2. The coinbase `miner` address is the pool's address announced in `welcome` (so the pool, not a third party, is paid) and, when the member's key has not yet been revealed on chain, the coinbase extra data carries the member's `KeyReveal`. 3. The finality section carries every vote the member sent in this session that is not yet carried by a block in the template's past (the pool reports carriage in `votes_carried`, and the member's verifier can confirm it). 4. `daa_score`, `bits`, `pruning_point`, the parents and `seed_source` agree with what the member's verifier reports for a block built now: parents are known tips or within the merge-depth bound, `bits` is the target the verifier computes for those parents (section 2.3), the epoch seed in `seeds` is the one the verifier holds for that DAA score (section 1.12). @@ -96,10 +96,10 @@ Direction P = pool, M = member. Every message is one object with `"t"` naming th | `declare_template` | M to P | `header`, `coinbase`, `transactions` | Mode C | | `template_ack` | P to M | `template_id` | | | `template_refused` | P to M | `code` in {`invalid`, `payout`, `tag`, `oversize`, `rate`, `capacity`, `other`}, `detail` | | -| `job` | P to M | `job_id`, `template_id`, `prehash` (64 hex), `target64` (16 hex), `share_target64` (16 hex), `nonce_start` (hex, a multiple of 32), `nonce_count` (hex, a multiple of 32), `clean` (bool) | Work. `clean` true means abandon earlier jobs. The member forwards it to its workers as a worker-protocol `job` line with `share_target64` in the target field | +| `job` | P to M | `job_id`, `template_id`, `prehash` (64 hex), `vote_key_hash` (64 hex, the key the template's header names; Igneum 2.0), `target64` (16 hex), `share_target64` (16 hex), `nonce_start` (hex, a multiple of 32), `nonce_count` (hex, a multiple of 32), `clean` (bool) | Work. `clean` true means abandon earlier jobs. The member forwards it to its workers as a worker-protocol `job` line with `share_target64` in the target field | | `job_refused` | M to P | `job_id`, `code` in {`vote_key`, `payout`, `reveal`, `votes`, `header`, `merkle`, `invalid`, `seeds`}, `detail` | The member will not hash this job and says why | -| `share` | M to P | `job_id`, `nonce` (16 hex), `hash` (16 hex) | A lane hash at or below `share_target64` | -| `share_result` | P to M | `job_id`, `nonce`, `accepted` (bool), `code` in {`ok`, `stale`, `duplicate`, `above_target`, `wrong_hash`, `unknown_job`}, `weight` (the share's weight, 9.8) | | +| `share` | M to P | `job_id`, `nonce` (16 hex), `hash` (16 hex), `vote_key_hash` (64 hex, the member's key; Igneum 2.0) | A lane hash at or below `share_target64`. The pool MUST refuse a share whose `vote_key_hash` is not the session's authorised key or not the key of the job's template (`share_result` code `vote_key`), so a share is evidence on its own of whose key the work was done under | +| `share_result` | P to M | `job_id`, `nonce`, `accepted` (bool), `code` in {`ok`, `stale`, `duplicate`, `above_target`, `wrong_hash`, `unknown_job`, `vote_key`}, `weight` (the share's weight, 9.8) | | | `solution` | M to P | `job_id`, `nonce`, `hash`, `block_hash` | A lane hash at or below `target64`. The member has already submitted the block to its own node, or does so at once if it has none | | `checkpoint` | P to M | `index`, `hash`, `blue_score`, `daa_score`, `state` | The pool's view, for information. A member MUST NOT sign from this message alone (9.7) | | `vote` | M to P | `index`, `hash`, `vote` (280 bytes hex, section 3.10 C2) | The member's signed vote, for relay and carriage | @@ -139,7 +139,7 @@ The hash is 64 bits and is defined over an aligned group of 32 nonces (section 1 2. `share_target64 = min(2^64 - 1, target64 << s)` for the shift `s` the pool last sent in `set_target`, `min_shift <= s <= max_shift`. The pool MUST choose `s` so that `target64 << s` does not saturate; a saturated target makes every hash a share and the pool's verification cost (item 5) unbounded. `target64` is the block target of the template's `bits` under the mapping of section 1.10 (`target64 = target256 >> 192`, candidate, Open O-2.4). 3. Weight. A share at shift `s` counts `2^-s` of a block: its expected cost is `2^64 / share_target64` hashes against `2^64 / target64` for a block, and the ratio is `2^-s` exactly, so share weights are exact binary fractions and a pool's ledger needs no floating point. A `solution` (a share at or below `target64`) counts as a share at its job's shift plus whatever the scheme pays for the block. 4. Vardiff. The pool SHOULD set `s` per member so that the member sends about one share per `share_interval_s` (Designed 10 s): at the worker protocol's 64-bit hash and a card at 229 Mhash/s (the RTX 5090 bench figure quoted in `docs/review/round-3-2026-10-03.md`, R3.15), ten seconds is 2.3 x 10^9 hashes, which fixes `s` once `target64` is known. The pool adjusts `s` by one per `set_target` and never more than once per 30 s (Designed), so the share rate is a smooth estimate of a member's hashrate for the dashboards. -5. Verification. The pool MUST verify every share by evaluating the 32-lane group on a CPU exactly as a node verifies a block (section 1.11): 0.441 ms per group steady on one M5 Max performance core, 0.87 ms worst cold (Measured, `docs/bench-log.md`, igneum-pow entry). One such core verifies about 2,270 shares a second, so at one share per 10 s per member one core covers about 22,000 members (derived from the measurement; a server core is the measurement of O-9.5). A pool MAY sample shares at high shifts; it MUST NOT credit a share it did not verify at a shift below `sample_shift` (Designed 8, so every share worth more than 1/256 of a block is checked). +5. Verification. The pool MUST verify every share by evaluating the 32-lane group on a CPU exactly as a node verifies a block (section 1.11), against the prehash of the job it issued to that session, whose header names the session's key (9.4.1 item 1; the share's `vote_key_hash` compared first, one 32-byte comparison, Igneum 2.0): 0.441 ms per group steady on one M5 Max performance core, 0.87 ms worst cold (Measured, `docs/bench-log.md`, igneum-pow entry). One such core verifies about 2,270 shares a second, so at one share per 10 s per member one core covers about 22,000 members (derived from the measurement; a server core is the measurement of O-9.5). A pool MAY sample shares at high shifts; it MUST NOT credit a share it did not verify at a shift below `sample_shift` (Designed 8, so every share worth more than 1/256 of a block is checked). 6. The member re-checks every `found` nonce on its own CPU before sending it as a share, as the devnet miner already does for blocks (`mine_worker`, "cpu re-check ok"); a worker whose `found` lines fail the re-check is reported as `WORKER MISMATCH` and the share is not sent. 7. Rejections. `stale`: the job's template is superseded and the share is older than `stale_grace_ms` (Designed 2,000 ms, two block times, so a share found a moment after a new template still pays); `duplicate`: the nonce was already credited for the job; `above_target`: the pool's own evaluation exceeds `share_target64`; `wrong_hash`: the member's `hash` field disagrees with the pool's evaluation (a worker fault, reported to the operator). 8. Payment schemes are the pool's business. The protocol fixes share weight and the `share_scheme` disclosure; it does not fix PPLNS window lengths or PPS fees. A pool that pays the proving share: a member's blocks pay 80% of emission to the pool's address and 20% to the provers of the block (section 2.5), so a pool receives only the 80%; whether a pool also proves (its node holds the members' keys' sortition eligibility, section 7.2, because eligibility is by key and the key is the member's) is answered by 7.2 item 2: the assignee is the member's key, so a pool cannot prove in its members' name without their shard proofs carrying their keys (R3.13, `provers` naming in the proof statement). Pools that offer proving to members do so under the external job market's terms, outside this section.