Trustless mining pool: what the protocol allows, and what it would take #3

Open
opened 2026-09-01 02:59:23 +00:00 by grenade · 0 comments
Owner

Not worth building today. Raised so the analysis survives, and because it becomes worth revisiting if the miner population grows into the thousands — a pool only earns its keep for participants whose share is small enough that they wait days between blocks. On a network of ~9–21 active authors it earns nothing.

The bar this is judged against: no trust required. A custodial pool is not worth building.

Verified against Quantus-Network/chain @ 4c4a8f2f and quantus-miner v4.0.0 on 2026-09-01.

Direction 1 — pool trusting miners: solved

Shares are verifiable. Poseidon2(header, nonce) < share_target is cheap to check and impossible to forge, and crates/pool-service in the miner repo already implements the whole mechanism — it hands out low-difficulty share challenges over the real block header, verifies solves, tracks shares_accepted / shares_rejected per session, and submits any share that happens to meet network difficulty as a block. It exists as a captcha pool (proof-of-work as anti-spam) rather than a reward pool, but the share machinery is done.

Note the node↔miner protocol itself has no share concept: MiningResult carries only completed / failed / cancelled / running plus a self-reported hash_count and elapsed_time. hash_count is trivially forgeable and must never be used for accounting. Shares have to come from a pool-service-shaped intermediary, not from the miner protocol.

Direction 2 — miners trusting the operator: structurally blocked

Three compounding reasons, none of them fixable off-protocol:

  1. The beneficiary is committed before the work. The node bakes its inner_hash into the PreRuntime digest, then broadcasts the resulting pre_hash as the job (🎁 Prepared block … hash: 0xc067…⛏️ Broadcasting job 190: pre_hash=c067…). Changing the payout address changes the hash and invalidates every nonce already searched. There is no post-hoc splitting.
  2. One beneficiary per block. pallets/mining-rewards mints in on_finalize to the single author extracted from the digest. No vector, no split.
  3. No smart contracts. The runtime has 18 pallets and neither pallet-contracts nor pallet-revive. And even with them, rewards land at a wormhole address spendable only by ZK proof of a secret — a contract cannot hold a secret, so the reward cannot be paid into one.

A rotating-beneficiary scheme (pool picks whose address goes in each header, proportional to shares, so funds never touch the operator) was considered and does not work: because the payout address is fixed before mining, nobody has any reason to work a job that pays someone else. That is solo mining with extra steps.

The only thing that removes trust entirely: a payout vector

Extend the PreRuntime digest to carry several beneficiaries with weights, and have mining-rewards credit them proportionally in on_finalize.

The pool commits the split before the work, derived from the previous round's shares. Miners verify the split matches their contribution before choosing to mine that job. The split is in every header, so it is publicly auditable forever, and the operator never custodies anything.

Contained change — one pallet plus the digest format. Worth putting to the Quantus team before mainnet freezes the reward path, and it is a general capability rather than a favour to us: no chain with a single-beneficiary header can ever have a non-custodial pool.

The strongest design available without a protocol change

This chain has a primitive most do not: pallets/reversible-transfers gives an account a guardian and a delay.

  • set_high_security(delay, guardian) — guardian may not be self
  • outgoing transfers become scheduled, executing only after the delay
  • cancel_transfer on a high-security account: ensure!(who == &pending.guardian, InvalidReverser)the guardian cancels, not the owner
  • recover_funds(account) — guardian-only; cancels all pending transfers and sweeps the balance to the guardian
  • the pallet's own docs state the guardian may be a multisig: "its derived address, so a multisig can cancel and recover exactly like a plain account"

So:

pool wormhole address   (operator holds the ZK spend secret)
        │ ZK exit
        ▼
pool payout account     set_high_security(delay = N, guardian = k-of-n miner multisig)
        │ schedule_transfer   — delayed, visible on-chain
        ▼
   miner payouts

Every payout is public for the delay window. A payout to the operator instead of the miners gets cancelled by any k-of-n; a hostile operator gets recover_funds'd and the balance swept to the multisig.

What it still does not fix:

  • The pre-exit window. The wormhole address has no key — it cannot be multisig or high-security — so the operator can ZK-exit it anywhere. Trust is bounded to "everything mined since the last exit". Shrink it by exiting often; make it observable (the arena exporter already reads that balance, so miners can watch whether the operator keeps sweeping).
  • Liveness. A quorum that cannot assemble cannot cancel. And recover_funds sweeps to the guardian, so the multisig then has to distribute — the same problem one level up with n signers instead of one.

Materially better than custodial: operator discretion is reduced to one bounded, observable window, and miners hold a real veto plus a nuclear option. Still not trustless.

Revisit when

  • non-empty wallet / active-miner count reaches the low thousands, where variance genuinely hurts small participants; or
  • a payout vector lands upstream, at which point direction 2 is solved and this becomes worth building properly

Until then the existing multi-miner support is the honest answer for anyone who already trusts each other: one node, many miners, one addressnode/src/miner_server.rs broadcasts each job to every connected miner and each picks its own random starting nonce. That is a pool whose counterparty is you.

**Not worth building today.** Raised so the analysis survives, and because it becomes worth revisiting if the miner population grows into the thousands — a pool only earns its keep for participants whose share is small enough that they wait days between blocks. On a network of ~9–21 active authors it earns nothing. The bar this is judged against: **no trust required.** A custodial pool is not worth building. Verified against `Quantus-Network/chain` @ `4c4a8f2f` and `quantus-miner` v4.0.0 on 2026-09-01. ## Direction 1 — pool trusting miners: solved Shares are verifiable. `Poseidon2(header, nonce) < share_target` is cheap to check and impossible to forge, and `crates/pool-service` in the miner repo **already implements the whole mechanism** — it hands out low-difficulty share challenges over the real block header, verifies solves, tracks `shares_accepted` / `shares_rejected` per session, and submits any share that happens to meet network difficulty as a block. It exists as a *captcha* pool (proof-of-work as anti-spam) rather than a reward pool, but the share machinery is done. Note the node↔miner protocol itself has **no** share concept: `MiningResult` carries only `completed` / `failed` / `cancelled` / `running` plus a self-reported `hash_count` and `elapsed_time`. `hash_count` is trivially forgeable and must never be used for accounting. Shares have to come from a `pool-service`-shaped intermediary, not from the miner protocol. ## Direction 2 — miners trusting the operator: structurally blocked Three compounding reasons, none of them fixable off-protocol: 1. **The beneficiary is committed before the work.** The node bakes its `inner_hash` into the PreRuntime digest, then broadcasts the resulting `pre_hash` as the job (`🎁 Prepared block … hash: 0xc067…` → `⛏️ Broadcasting job 190: pre_hash=c067…`). Changing the payout address changes the hash and invalidates every nonce already searched. There is no post-hoc splitting. 2. **One beneficiary per block.** `pallets/mining-rewards` mints in `on_finalize` to the single author extracted from the digest. No vector, no split. 3. **No smart contracts.** The runtime has 18 pallets and neither `pallet-contracts` nor `pallet-revive`. And even with them, rewards land at a *wormhole* address spendable only by ZK proof of a secret — a contract cannot hold a secret, so the reward cannot be paid into one. A rotating-beneficiary scheme (pool picks whose address goes in each header, proportional to shares, so funds never touch the operator) was considered and **does not work**: because the payout address is fixed before mining, nobody has any reason to work a job that pays someone else. That is solo mining with extra steps. ## The only thing that removes trust entirely: a payout vector Extend the PreRuntime digest to carry **several beneficiaries with weights**, and have `mining-rewards` credit them proportionally in `on_finalize`. The pool commits the split *before* the work, derived from the previous round's shares. Miners verify the split matches their contribution before choosing to mine that job. The split is in every header, so it is publicly auditable forever, and the operator never custodies anything. Contained change — one pallet plus the digest format. **Worth putting to the Quantus team before mainnet freezes the reward path**, and it is a general capability rather than a favour to us: no chain with a single-beneficiary header can ever have a non-custodial pool. ## The strongest design available without a protocol change This chain has a primitive most do not: `pallets/reversible-transfers` gives an account a **guardian** and a **delay**. - `set_high_security(delay, guardian)` — guardian may not be self - outgoing transfers become *scheduled*, executing only after the delay - `cancel_transfer` on a high-security account: `ensure!(who == &pending.guardian, InvalidReverser)` — **the guardian cancels, not the owner** - `recover_funds(account)` — guardian-only; cancels all pending transfers and sweeps the balance to the guardian - the pallet's own docs state the guardian may be a multisig: *"its derived address, so a multisig can cancel and recover exactly like a plain account"* So: ``` pool wormhole address (operator holds the ZK spend secret) │ ZK exit ▼ pool payout account set_high_security(delay = N, guardian = k-of-n miner multisig) │ schedule_transfer — delayed, visible on-chain ▼ miner payouts ``` Every payout is public for the delay window. A payout to the operator instead of the miners gets cancelled by any k-of-n; a hostile operator gets `recover_funds`'d and the balance swept to the multisig. **What it still does not fix:** - **The pre-exit window.** The wormhole address has no key — it cannot be multisig or high-security — so the operator can ZK-exit it anywhere. Trust is bounded to "everything mined since the last exit". Shrink it by exiting often; make it observable (the arena exporter already reads that balance, so miners can watch whether the operator keeps sweeping). - **Liveness.** A quorum that cannot assemble cannot cancel. And `recover_funds` sweeps to the guardian, so the multisig then has to distribute — the same problem one level up with n signers instead of one. Materially better than custodial: operator discretion is reduced to one bounded, observable window, and miners hold a real veto plus a nuclear option. Still not trustless. ## Revisit when - non-empty wallet / active-miner count reaches the low thousands, where variance genuinely hurts small participants; or - a payout vector lands upstream, at which point direction 2 is solved and this becomes worth building properly Until then the existing multi-miner support is the honest answer for anyone who already trusts each other: **one node, many miners, one address** — `node/src/miner_server.rs` broadcasts each job to every connected miner and each picks its own random starting nonce. That is a pool whose counterparty is you.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: lair/quantus#3