Pool protocol after Stratum V2 job declaration: member-checked or member-built templates, JSON over TLS, shares at target64 << s on the 32-lane unit, one vote key per operator held by the member, votes relayed and carried by the pool with a chain-only drop test. Light client: trust table, checkpoint-mode bytes per day, pinned seed list, the read-only node API, the homepage card's steps. Phone app: wallet, miner monitor, node card, store rules, build plan on journey.json. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
189 lines
31 KiB
Markdown
189 lines
31 KiB
Markdown
# Igneum protocol specification, section 9: the pool protocol
|
|
|
|
Spec version 0.1, 3 October 2026. Status of this section: Designed. Nothing here is implemented. The only miner-to-worker protocol that exists is the devnet worker protocol (`igneum-miner --worker`, `vendor/igneum-node/igneum/miner/src/main.rs`; `--serve` in `proto-cuda/host.cu`, `proto-opencl/host.c`, `proto-metal/main.swift`), which runs between two processes on one machine and is cited here where this section reuses it. The review of 3 October 2026 found that no pool protocol existed and that "shares on a 64-bit lane hash with vardiff are unspecified" (`docs/review/round-3-2026-10-03.md`, farm operator, attack 3, R3.15). This section is the answer.
|
|
|
|
Lineage, credited. The shape is Stratum V2's (Braiins and the Stratum V2 working group, stratumprotocol.org), from memory and approximate: a binary, encrypted protocol with a Mining Protocol for shares, a Job Declaration Protocol under which a miner builds its own block template from its own node and declares it to the pool, which may accept it and must then pay shares found on it, and a Template Distribution Protocol between the miner and its node. Igneum keeps the three ideas (encrypted transport, miner-built templates under the pool's payout, pool pays by shares) and changes what the header carries: on Igneum the header names a vote key (section 2.4) and the vote key decides finality (section 3), so this section also fixes whose key a pooled block names and who signs the checkpoint votes. The ledger entries F10 and G6 (pools hold the votes; Stratum V2 job declaration is optional) are the two criticisms this section is written against.
|
|
|
|
Two words that collide. In this section "pool" means a mining pool: an operator that distributes work to many hashers and pays them by shares. The "proving pool" of sections 2.5 and 5.3 is the 20% emission share paid to provers and is called "the proving share" here whenever it is meant.
|
|
|
|
## 9.1 Roles and what each one holds
|
|
|
|
| Role | Runs | Holds | Never holds |
|
|
|---|---|---|---|
|
|
| Member | The operator's miner program (the signer), on a machine the operator controls, driving one or more GPU workers over the worker protocol | The operator's vote key (section 3.1 W1, section 8.5 item 4), the operator's payout preference, a verifier: either a full node or the light client of section 10 | The pool's keys |
|
|
| Worker | A GPU process fed `job` lines by the member (the `--serve` protocol) | Nothing: no key, no template, no network connection | Any key |
|
|
| Pool | A server that builds templates, issues jobs, verifies shares, pays members, relays votes and carries them in its templates | Its own payout address, its own full node, its members' public keys and share ledger | Any member's secret key |
|
|
| Member's verifier | A full node or a section 10 light client | The chain state the member checks templates and checkpoints against | |
|
|
|
|
The signer is one process per vote key (9.6). Hashing rigs that carry no key are workers, and a member MAY drive any number of workers on any number of machines from one signer.
|
|
|
|
## 9.2 What the pool cannot do, and how a member would notice
|
|
|
|
This section exists so that each row of the following table is true for a conforming member against any pool, conforming or not.
|
|
|
|
| The pool tries to | Why it fails against a conforming member | How it shows |
|
|
|---|---|---|
|
|
| Leave out or reorder transactions the member wanted in its block | The member receives the full template (9.4, mode A) or builds its own (mode C) and checks it against its verifier before hashing it; a declared template is paid like any other | A pool that refuses declared templates answers with a code (9.5, `template_refused`) and the member counts refusals; the explorer shows which templates each pool's blocks came from by the `template_mode` tag in the coinbase extra data |
|
|
| Name its own vote key in the header | The pre-PoW hash commits to `vote_key_hash` (section 2.2, fork point a5), the member checks the field against its own key before hashing and refuses a job that names another | A pool whose blocks all name one key is a custodial pool (9.6 item 5); any observer counts keys per payout address from headers and bodies alone |
|
|
| Drop a member's checkpoint votes | Votes are signed by the member and reach the chain by three roads (9.7): the pool's template, the member's own template and any node's `submitFinalityVote`. The member checks that the template it is about to hash carries its outstanding votes | Participation per key is public (`getFinalityWeights`, section 3.10) and computed from votes in blocks (Q2); 9.7 item 6 gives the chain-only test |
|
|
| Make a member sign a wrong checkpoint | The member signs only a checkpoint hash its own verifier reports; a member without a verifier does not vote (9.7 item 2) | A member that signs two hashes at one index produces equivocation evidence against itself (section 3.6), which is why the rule is "no verifier, no vote" |
|
|
| Withhold a block the member found | The member holds the full block and submits it to its own node as well as to the pool (9.5, `solution`) | The block is on the chain whatever the pool did |
|
|
| Underpay shares | Share weight is fixed by this section (9.8) and the member keeps its own ledger of accepted shares | Not a protocol matter beyond the ledger; the member changes pool |
|
|
|
|
What the pool can still do: choose the transactions in its own templates for members who take them as offered (mode A members who do not check, or mode B members), decline declared templates with a stated code, and concentrate hashrate. Vote weight, however, follows the members' keys (9.6), so pool concentration no longer equals vote concentration as section 3.7 item 3 states; that sentence describes a custodial pool and is narrowed by this section once a conforming pool exists (O-9.1).
|
|
|
|
## 9.3 Transport and encoding
|
|
|
|
Decision: newline-delimited JSON over TLS 1.3, one JSON object per line, UTF-8, binary fields as lowercase hex. Not the node's gRPC.
|
|
|
|
Why. The pool protocol crosses operators and languages: pools, mining operating systems and rental markets each have their own code base, and the lingua franca of every one of them is a line of JSON on a socket (Stratum V1 made it so). The node's gRPC surface is the node's own interface, it is edited at every fork point (`docs/fork-divergence.md`, the `rpc/grpc` rows) and a pool written against it would break at each. Line-oriented text also matches the worker protocol the member already speaks downward, so one miner program has one shape of message in both directions. TLS is required because Stratum V1 in plaintext is hijackable on the wire (the reason Stratum V2 encrypts, approximate), and a hijacked connection could feed a member a checkpoint hash to sign.
|
|
|
|
| Rule | Value | Label |
|
|
|---|---|---|
|
|
| Transport | TLS 1.3, server-authenticated; the pool's certificate chain or a pinned public key the member obtained with the pool's address | Designed |
|
|
| Framing | One JSON object per line, `\n` terminated, no object over 4 MiB (a template with its transactions) | Designed |
|
|
| Field order | Irrelevant; unknown fields MUST be ignored | Designed |
|
|
| Integers | JSON numbers up to 2^53; anything that can exceed it (nonces, targets, hashes, weights in base units) is a hex string | Designed |
|
|
| Identifiers | `id` on every request, echoed on the reply; notifications carry no `id` | Designed |
|
|
| Port | 4461 (the mainnet chain id, section 7.4), 4462 testnet, 4463 devnet; a pool MAY use another and say so in its address | Designed |
|
|
| Member authentication | The `authorize` message carries the member's BLS public key and a proof of possession (the `KeyReveal` of section 3.10, 144 bytes), plus a signature over the session's TLS exporter value under the same key, so the key proves it is live and the connection is bound to it | Designed |
|
|
|
|
## 9.4 Templates
|
|
|
|
Three modes. The pool announces which it supports in `welcome`; every conforming pool MUST support A and C, and MAY support B.
|
|
|
|
| Mode | What the member receives | What the member checks | For whom |
|
|
|---|---|---|---|
|
|
| A, full template (default) | The complete block: header fields except nonce, the coinbase (payout, the member's key reveal when outstanding, the finality section with the member's votes), every transaction as hex | Everything in 9.4.1 | A member with a verifier and ordinary bandwidth |
|
|
| B, commitment | The header fields, the coinbase, the Merkle path from the coinbase to `hash_merkle_root`, and the transaction count; not the transactions | Header, coinbase and path; it cannot check transaction choice and the protocol marks its blocks `template_mode: "B"` | A member on a metered link that has chosen to trust the pool's transaction choice |
|
|
| C, declared | Nothing: the member builds the template from its own node with the pool's payout address and declaration constraints, and declares it; the pool replies with an id and issues jobs on it | The pool checks validity and payout; the member checks nothing further because it built it | A member that wants its own transaction choice, which is the Stratum V2 job declaration idea |
|
|
|
|
### 9.4.1 What a member checks before hashing a template (modes A and B)
|
|
|
|
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.
|
|
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).
|
|
5. Mode A only: `hash_merkle_root` recomputes from the transactions and the coinbase, no transaction is a state-free fault (design document, execution layer 1.5), and, when the member's verifier is a full node, the node accepts the block as a template (the node's own template validation; the RPC is the forward reference of O-9.4).
|
|
6. Mode B only: the Merkle path from the coinbase proves membership under `hash_merkle_root`.
|
|
|
|
Cost. Items 1 to 4 are field comparisons. Item 5 is one Merkle root over the block's transactions and one node call, both well under the 1-s template lifetime at any block size the gas budgets of the design document allow (Designed, not measured; the measurement is O-9.5). The lottery hash is not evaluated at job time at all: the member's CPU evaluates one 32-lane unit per `found` nonce only (9.8), at 0.441 ms per unit on one M5 Max core (Measured, `docs/bench-log.md`, igneum-pow entry; section 1.11).
|
|
|
|
### 9.4.2 Declared templates (mode C)
|
|
|
|
1. The member sends `declare_template` with the full block (as mode A would carry it) built from its own node, with the pool's payout address as `miner`, its own `vote_key_hash`, and the pool's `declaration` constraints from `welcome` honoured: at most `max_extra_bytes` of member extra data after the pool's tag, and the pool's `tag` (an opaque string the pool uses to attribute the block) placed first in the coinbase extra data.
|
|
2. The pool MUST reply within `declare_timeout_ms` (Designed 500 ms) with `template_ack` (an id) or `template_refused` with one of the codes of 9.5. A pool MUST NOT refuse a declared template on the content of its transactions; it MAY refuse for invalidity against its own node, a wrong payout, a missing tag, oversize, or a rate above `max_declarations_per_s`.
|
|
3. The pool issues jobs on a declared template as on its own; shares on it are paid identically (9.8).
|
|
4. A pool MAY cap the share of its hashrate on declared templates only by refusing new declarations with code `capacity`, never by paying them less.
|
|
|
|
A member that is refused on code `other` more than `refusal_tolerance` times in a session SHOULD disconnect and say why in its log; the pool's refusal rate is reported by the member in `stats` so that a mining operating system can show it.
|
|
|
|
## 9.5 Messages
|
|
|
|
Direction P = pool, M = member. Every message is one object with `"t"` naming the type.
|
|
|
|
| Type | Direction | Fields | Meaning |
|
|
|---|---|---|---|
|
|
| `hello` | M to P | `versions` (list), `chain_id`, `client`, `modes` supported | Opens the session |
|
|
| `welcome` | P to M | `version`, `chain_id`, `pool_address`, `modes`, `vote_mode` (`member` or `pool`), `declaration` {`tag`, `max_extra_bytes`, `max_declarations_per_s`, `declare_timeout_ms`}, `share_scheme` (`pplns` or `pps` with its parameters), `min_shift`, `max_shift` | The pool's terms; the member shows `vote_mode` and `share_scheme` to its operator |
|
|
| `authorize` | M to P | `pubkey` (48 bytes hex), `pop` (96 bytes hex), `binding` (signature over the TLS exporter), `label` | Names the member by its vote key |
|
|
| `authorized` | P to M | `member_id`, `revealed` (true when the key has been revealed on chain, per the pool's node) | |
|
|
| `seeds` | P to M | `epoch_seed`, `day_seed`, `seed_source`, `vdf_proof` (516 bytes hex, section 4.4, when the member asks), `next_epoch_seed`, `next_day_seed`, `next_at_daa` | Current and next seed pair so the member can `prepare` its workers ahead (the worker protocol's `prepare`) |
|
|
| `set_target` | P to M | `shift` s (9.8) | Vardiff |
|
|
| `template` | P to M | `template_id`, `mode` (A or B), `header` (every field but nonce, hex), `coinbase`, `transactions` (A) or `merkle_path` plus `tx_count` (B), `votes_carried` (indices of the member's votes in this template) | A new template; supersedes the previous one for new jobs |
|
|
| `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_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) | |
|
|
| `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 |
|
|
| `vote_ack` | P to M | `index`, `relayed` (bool) | |
|
|
| `votes_carried` | P to M | list of {`index`, `block_hash`} | Blocks the pool's node has seen that carry the member's votes |
|
|
| `stats` | both | member: `hashrate`, `workers`, `refusals`, `shares`; pool: `members`, `hashrate`, `blocks_24h`, `declared_share` | For dashboards and the phone app (`docs/design/phone-app.md`), read-only |
|
|
| `ping`, `pong` | both | `id` | Liveness |
|
|
| `bye` | both | `reason` | Close |
|
|
|
|
Sizes. A `job` line is under 300 bytes; a `share` under 120; a `vote` about 600 (hex of 280). A mode A `template` is the block, which at 1 BPS and the design document's budgets is a few KB to a few hundred KB (Designed, unmeasured; O-9.5); a mode B `template` is about 1 KB plus the coinbase.
|
|
|
|
## 9.6 Identity: one vote key per operator
|
|
|
|
Rule of 3 October 2026 (ledger F17; section 3.1 W6): keys are free, weight is the only Sybil-resistant quantity, and a rule that counts keys is a rule a splitter wins. This section applies it to pools.
|
|
|
|
1. A member's vote key is the operator's key, the one its client created under section 8.5 item 4. A pool MUST NOT create, derive or hold vote keys for its members, and a conforming client defaults to one key per operator. The devnet launcher's `MINERS=8` identities per vendor is a devnet convenience and is withdrawn by this rule for any public network (ledger F17, "the client's one-key default" item, closed here as a rule and Open as code, O-9.2).
|
|
2. The header of every block a member finds names the member's key, in every mode. The pool's blocks therefore carry as many keys as it has members, and each member's 30-day weight (W2) is the blocks that member found, under any pool or alone, because weight follows the key and not the payout address. Moving between pools moves nothing.
|
|
3. Many rigs, one key: the key lives in one signer process (9.1) and the rigs are workers. An operator who runs two signers under one key risks equivocation (3.6): BLS signatures are deterministic, so two signers that see the same checkpoint hash produce the same vote, but two signers whose verifiers disagree during a partition sign two hashes at one index and strip the key's weight for 30 days. Two signers under one key is therefore a MUST NOT, and the client enforces it by refusing to start a second signer on the same key file.
|
|
4. Succession (W5) is the operator's: a pool never signs a succession message, because it never holds the key.
|
|
5. A custodial pool (`vote_mode: "pool"`): a pool MAY name its own key in every header it issues, and a member MAY accept that. The member's client MUST show the mode before the first share and MUST default to refusing it. A custodial pool's blocks all name one key, so its share of vote weight is visible to anyone who counts keys per payout address, and the explorer labels it. Section 3.7 item 3 describes this pool and no other.
|
|
6. Dust (W3) is per key: a member who finds fewer than 100 blocks in 30 days has no vote whichever pool it joins, which at one block a second is about 0.004% of hashrate (section 3.7 item 6). Pooling changes a member's variance of income, not its weight.
|
|
|
|
## 9.7 Votes: signed by the member, relayed by the pool
|
|
|
|
1. The member's signer reads checkpoints from its own verifier (a full node's `getFinalityCheckpoints`, or the light client of section 10), signs every checkpoint of the presence window it has not signed (section 3.3 Q2, as the devnet miner does, `Voter::tick` in `main.rs`), and sends each vote to the pool as a `vote` message. Nothing in the vote depends on the pool: the message signed is `(chain_id, index, hash(C_i))` (C2), and the pool only sees the signature.
|
|
2. A member whose only source of checkpoints is the pool's `checkpoint` message MUST NOT vote. A silent key leaves the active denominator after the presence window and loses nothing but its say (Q2); a key that signs what it was told loses 30 days of weight if it was told wrong (3.6). The client enforces this: voting is on only when a verifier is configured, and the light client of section 10 counts as one.
|
|
3. The pool MUST relay every member vote to the network as its own node received it (the p2p vote message of section 3.10, C2) within `relay_ms` (Designed 1,000 ms, under the certificate grace of Q4, Open O-3.4) and MUST carry it in its next template's finality section unless a block in that template's past already carries it, under the per-block vote bound of O-3.3.
|
|
4. Aggregation in templates. Votes for one `(index, checkpoint hash)` pair MAY be aggregated inside a block into one signature with a bitmap (Q2). A pool with many members SHOULD aggregate its members' votes per index before carriage: at 10,000 voters and 2,880 checkpoints a day, unaggregated carriage needs 333 votes per block at 280 bytes each (93 KB per block), while an aggregate per index per block is one signature and a bitmap (Designed; the bitmap's size and canonical list are O-3.12). Aggregation needs no secret key, so the pool can do it, and the member's participation is credited either way (Q2 counts a vote carried "as a vote or inside a certificate", and an in-block aggregate is a vote carrier; the spec 03 wording should say "or inside an aggregate", O-9.3).
|
|
5. Three roads. A member's vote reaches the chain through the pool's template (item 3), through the member's own declared templates (mode C, its own finality section), and through `submitFinalityVote` on any node it can reach, including seed nodes; the signer SHOULD use at least two of the three. A vote carried twice costs block space and nothing else (section 3.10: "votes not already in its past").
|
|
6. Detecting a pool that drops votes, from the chain alone. For member key k with pool payout address A, over a presence window: let `V_k` be the indices at which k voted (k's own log, or the votes seen on any road), `B_A` the blocks whose `miner` is A, and `C_A(k)` the indices among `V_k` carried by some block in `B_A` within 60 blocks of k's vote. A conforming pool's `|C_A(k)| / |V_k|` is near 1 minus the fraction already carried by other blocks (item 3 lets it skip votes already in the past); a pool that drops votes shows a ratio near 0 while the same votes appear in other producers' blocks or in k's own. The ratio is computable by anyone from headers and bodies (k's key hash is in the header of k's blocks and k's key is revealed in the body; A is in every coinbase), so a pool's carriage ratio is a public statistic the explorer and the phone app show per pool. The threshold below which the client warns is Open (O-9.6).
|
|
|
|
## 9.8 Shares
|
|
|
|
The hash is 64 bits and is defined over an aligned group of 32 nonces (section 1.9), so a share is defined the same way.
|
|
|
|
1. A share for job `j` is a nonce `n` in the job's range with `hash(n) <= share_target64(j)`, where `hash(n)` is lane `n AND 31` of the group `n AND ~31` under the job's `prehash`, init words `I = seed_words_from_bytes("igneum-block/" || prehash || nonce_hi_le32)` (section 1.6, header binding, O-1.9) and the job's seed pair.
|
|
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).
|
|
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.
|
|
|
|
## 9.9 Seeds and the epoch boundary
|
|
|
|
The program changes every epoch and the dataset every day (section 1.12), and an ahead-of-time worker needs the next pair before the boundary (the `prepare` command of the worker protocol, measured across boundaries in `docs/bench-log.md`, hot-swap entry).
|
|
|
|
1. The pool MUST send `seeds` on connection and whenever the current or next pair changes, with `next_at_daa` the DAA score of the boundary.
|
|
2. The member MUST check `epoch_seed` and `next_epoch_seed` against its verifier (the VDF output for the epoch, section 4.3; on the devnet, the hash of the last selected-chain block below `3,600 e - 600`) and refuse jobs on a seed its verifier does not confirm (`job_refused`, code `seeds`). A pool that could choose the seed could choose the program, which is the grinding section 4 exists to prevent.
|
|
3. A member whose verifier is the light client of section 10 verifies the VDF proof (516 bytes, 4.5 ms, Measured, section 4.4) that the pool forwards in `seeds`, against the `seed_source` its checkpoint chain confirms.
|
|
|
|
## 9.10 Parameters in this section
|
|
|
|
| Parameter | Value | Label |
|
|
|---|---|---|
|
|
| Transport | TLS 1.3, newline-delimited JSON | Designed |
|
|
| Ports | 4461 / 4462 / 4463 | Designed |
|
|
| Required modes | A (full template) and C (declared); B optional | Designed |
|
|
| `declare_timeout_ms` | 500 | Designed, Open O-9.5 |
|
|
| `relay_ms` | 1,000 | Designed, tied to Q4's grace (O-3.4) |
|
|
| `share_interval_s` | 10 | Designed, Open O-9.5 |
|
|
| Share target | `target64 << s`, never saturated | Designed |
|
|
| Share weight | `2^-s` of a block | Designed |
|
|
| `sample_shift` | 8 | Designed |
|
|
| `stale_grace_ms` | 2,000 | Designed |
|
|
| Vardiff step | one shift per 30 s at most | Designed |
|
|
| Pool-side verification per share | 0.441 ms per 32-lane group, one M5 Max core | Measured (section 1.11); server core Open O-9.5 |
|
|
| Vote key per member | the operator's own; one signer per key; pool holds none | Designed (ledger F17) |
|
|
| Custodial mode | allowed, disclosed, refused by default, visible on chain | Designed |
|
|
| Member without a verifier | hashes, does not vote | Designed |
|
|
| Vote carriage by the pool | every member vote in the next template, aggregated per index when many | Designed |
|
|
| Member template checks | 9.4.1 items 1 to 6 | Designed |
|
|
|
|
## 9.11 Open items
|
|
|
|
| Id | Item | What closes it | Gate |
|
|
|---|---|---|---|
|
|
| O-9.1 | Section 3.7 item 3 ("pools hold their hashers' votes") describes a custodial pool once this section is implemented; the litepaper's pool sentence (ledger F10, G6, overclaims 44 and 45) should say that a conforming pool's members keep their keys and that custodial pools are visible on chain | A reference pool and a reference member run on the phase 4 devnet; the `getFinalityWeights` listing shows one key per member under the pool's payout address; then the sentence is rewritten and 3.7 narrowed | 4 |
|
|
| O-9.2 | The devnet launcher runs `MINERS=8` identities per vendor (ledger F17); the one-key default is a rule here and not code | The launcher and the official client start one signer per operator and any number of workers; the signer refuses a second instance on the same key file | 4 |
|
|
| O-9.3 | Q2 counts a vote carried "as a vote or inside a certificate"; an in-block aggregate of votes (9.7 item 4) is a third carrier and the wording should name it; the bitmap and canonical list are O-3.12 | Spec 03 wording at the next MINOR step; the per-block vote bound re-run of O-3.3 with pool-aggregated carriage in the model | 3 |
|
|
| O-9.4 | Mode A item 5 needs a node RPC that validates a block as a template without submitting it; none is named | Add the RPC to the fork (candidate: `validateBlockTemplate`), with the cost per call measured at the design document's gas budgets | 2 |
|
|
| O-9.5 | Every timing here is Designed: template size and bandwidth per member at 1 BPS, the member's check cost (9.4.1), `declare_timeout_ms`, `share_interval_s`, the pool's verification throughput on a server core | A reference pool with 100 members on the phase 4 devnet: record template bytes per second per member, check time per template on a 2019-class laptop core, declared-template acceptance latency, shares verified per second per core on a server CPU; set the four parameters from the distributions | 4 |
|
|
| O-9.6 | The carriage ratio of 9.7 item 6 has no threshold, and its 60-block window is a guess | On the same devnet, one pool conforming and one dropping every vote: record both ratios per key over a day; set the warning threshold at the point that separates them with no false warnings on the conforming pool | 4 |
|
|
| O-9.7 | The TLS exporter binding in `authorize` is named and not specified (which exporter label, which bytes are signed) | Write the exact bytes at the first implementation; test that a replayed `authorize` on a second connection is refused | 4 |
|
|
| O-9.8 | A member with only the light client of section 10 as its verifier checks headers against a checkpoint chain, not against a full node's tip, so item 4 of 9.4.1 (parents are known tips) is weaker for it: it can confirm the parents descend from the last certified checkpoint and no more | Decide at gate 4 whether a light-client member may vote (9.7 item 2 says yes) after the eclipse test of O-3.7 is re-run with light-client members in the model | 3, with 4 |
|
|
| O-9.9 | HiveOS and the rental markets need this protocol to list the algorithm (ledger entry on rental, "cannot list an algorithm whose kernel changes hourly without a stratum for it") | The reference member runs under HiveOS against the reference pool through 24 epoch changes with in-worker compilation (R3.15's acceptance test: outage under 2 s per change) | 4 |
|