igneum/pool/README.md

165 lines
10 KiB
Markdown

# igneum-pool: the Igneum mining pool, version 0
A public mining pool for Igneum (`docs/spec/09-pool-protocol.md`, `docs/plans/pool.md`). It talks to one or more
`igneumd` nodes, builds a template per member with the member's own vote key in the header, hands out jobs over the
spec 09 protocol (newline JSON over TCP), sets a share target per member so each sends about one share every 10 s,
verifies every share on the CPU warp verifier the node uses, submits found blocks to every node, pays PPLNS from the
pool's coinbase address on the EVM side with a configurable fee (default 1%), and serves the stats API that
WhatToMine and MiningPoolStats read plus a page.
Built 5 October 2026 on branch `pool-v0`. Nothing is deployed. Measured on a private fast-time network
(`docs/plans/pool.md` section 5): three CPU miners of different identities, 0 rejected and 0 stale shares in the
second run, 289 blocks found and 0 orphaned at 49% of the network, PPLNS paid in dry run and then for real (24
transfers, 0 failed, the miners' EVM balances rose by what the ledger said), the API at 100 requests per second with
p99 under 3 ms, share checks 2.1 ms each under load (0.44 ms idle, the crate bench).
## Build
The crate reads the node fork by path from `../vendor/igneum-node` (the fork checkout, or a symlink to the fork
worktree that carries the pool-mode miner, branch `pool-v0` of the fork) and `../igneum-pow` from this repository.
```
cd pool
cargo build --release # target/release/igneum-pool, about 2 min warm on the Mac
cargo test --release # vardiff, PPLNS, share validation against the warp verifier, the API fixture
```
The miner with pool mode: `cargo build --release -p igneum-miner` in the fork worktree (branch `pool-v0`).
## Run
```
igneum-pool --node grpc://127.0.0.1:26610 --evm-rpc http://127.0.0.1:26790 \
--listen 0.0.0.0:4463 --http 127.0.0.1:4480 --data-dir /var/lib/igneum-pool \
--network devnet --fee-percent 1 --min-payout 1 --pplns-window 2 --payout-interval-s 60 \
--name "Igneum pool" --public-url pool.example.net:4463 [--dry-run]
```
| Flag | Default | Meaning |
|---|---|---|
| `--node` | `grpc://127.0.0.1:26610` | gRPC of a node; repeat for more. The first builds templates and confirms blocks; every one receives found blocks |
| `--evm-rpc` | `http://127.0.0.1:26790` (devnet), 26890 testnet, 8545 mainnet | the first node's eth_ JSON-RPC, for payouts |
| `--listen` | `0.0.0.0:4463` (devnet), 4462 testnet, 4461 mainnet | where members connect (spec 9.3 ports) |
| `--http` | `127.0.0.1:4480` | the stats API and the page; put a TLS terminator in front |
| `--data-dir` | `data` | `state.json` (the ledger snapshot) and the payout key |
| `--payout-key` | `<data-dir>/payout-key.json` | the pool's secp256k1 key, created on first start, mode 0600 |
| `--fee-percent` | `1` | the pool fee, taken from each confirmed block's reward before the PPLNS split |
| `--min-payout` | `1.0` IGN | a member's balance is paid once it reaches this |
| `--pplns-window` | `2.0` blocks | the window, in blocks of expected work (shares weigh `2^-s`) |
| `--payout-interval-s` | `60` | a payout round every so often, at most 16 transfers per round |
| `--dry-run` | off | compute and record payouts, send nothing |
| `--network` | `devnet` | devnet, testnet or mainnet: chain id (4463, 4462, 4461), default ports |
| `--share-interval-s` | `10` | vardiff target: one share per member per interval |
| `--min-shift`, `--max-shift` | `0`, `60` | bounds on the share shift; the saturation cap always applies |
| `--stale-grace-ms` | `2000` | a share on a superseded job inside the grace still pays |
| `--orphan-after-daa` | `120` | a pending block this far behind the virtual and not blue is an orphan |
| `--verify-threads` | `2` | share checks running at once on the CPU |
At start the pool prints its payout address. That address is the pool's coinbase address: every template names it in
the `IGNA` field, so the execution layer credits it 80% of every blue block the pool finds. The 20% proving share goes
to the proving escrow by the chain's own rule (`consensus/core/src/igneum.rs`, `igneum/exec/src/executor.rs`); the
pool never touches it and cannot.
## What the operator must secure
- **The payout key** (`payout-key.json`). It holds every reward the pool earns until the payout round sends it on.
Back it up off the box before the first block. Anyone with the file can empty the pool. Keep the data directory
at mode 0700, the file at 0600 (the pool sets 0600 on creation), and never put it in a repository or a log.
- The pool's HTTP port behind a TLS terminator (Caddy or nginx) with the page on one hostname and the member port
(4463) reachable; v0 speaks plain TCP to members, so the terminator does not cover shares yet (TLS for the member
port is the v0 gap named in `docs/plans/pool.md`).
- The node's RPC (`--rpclisten`) and EVM RPC (`--evm-rpclisten`) on loopback only. The pool talks to them locally.
- `state.json`: the ledger. It is rewritten every 15 s; a lost file loses unpaid balances since the last payout, so
back it up with the key.
## Deploy on one Hetzner box (the seed-node pattern)
The seed nodes (`infra/seed-nodes/README.md`) are Hetzner VMs with a fixed IPv4, `igneumd` as a systemd unit, RPC on
loopback, built by the cross-compile on the Mac (`infra/cross/build-linux.sh`). A pool box is the same plus this
binary and two more ports.
1. Create the VM as a seed would be created: `SEED_NAME=igneum-pool-1 SEED_TYPE=cpx31 ./create-seed.sh` from
`infra/seed-nodes` (the firewall script opens 22 and 26611; add 4463 and 443). The seeds run on cx23 (2 vCPU, 4 GB,
USD 6.49 a month net plus the IPv4, approximate from the seed plan); a pool wants a core for the node, a core for
share checks (one share per 10 s per member, 0.44 ms each on an M5 Max core, so a server core at roughly half that
rate covers thousands of members, approximate) and 8 GB for the node plus the pool's 256 MiB cache per day seed.
A cpx31 (4 vCPU, 8 GB) is about USD 14 a month net (approximate, price list not re-read tonight).
2. Provision the node exactly as `provision-seed.sh` does, with `--utxoindex` added and `--evm-rpclisten=127.0.0.1:26790`
(the pool needs the EVM RPC for payouts). The node must be the devnet-v4 line or later (it reports `pow_epoch` with
every template; the pool refuses to run against one that does not).
3. Cross-compile `igneum-pool` with the same `cargo-zigbuild` target as the node (`x86_64-unknown-linux-gnu.2.36`)
from `pool/`, copy it to `/opt/igneum/bin/igneum-pool`, and install the unit below. Re-stamp copied sources with
`touch` before any build on the far side (the stale-build rule in CLAUDE.md); nothing is built on the server.
4. `caddy` (or nginx) in front of `127.0.0.1:4480` for `https://pool.<domain>/`; the member port 4463 open in the
Hetzner firewall.
5. First start with `--dry-run` for a day, read `/api/blocks` and `/api/payments`, then restart without it.
```
# /etc/systemd/system/igneum-pool.service
[Unit]
Description=Igneum mining pool
After=network-online.target igneumd.service
Requires=igneumd.service
[Service]
User=igneum
EnvironmentFile=/etc/igneum/pool.env
ExecStart=/opt/igneum/bin/igneum-pool --node grpc://127.0.0.1:26610 --evm-rpc http://127.0.0.1:26790 \
--listen 0.0.0.0:4462 --http 127.0.0.1:4480 --data-dir /var/lib/igneum-pool --network testnet \
--fee-percent 1 --min-payout 1 --name "Igneum pool" --public-url pool.igneum.network:4462
Restart=always
RestartSec=5
UMask=0077
[Install]
WantedBy=multi-user.target
```
Costs, approximate: the VM (USD 6.49 to 14 a month net by type), the IPv4 (Hetzner's primary IPv4 price, a few
dollars), outbound traffic inside Hetzner's included allowance at a few hundred members (a template is a few KB per
member per second in mode A; 1,000 members at 10 KB/s each is 10 MB/s, which is where mode B or a template RPC that
takes a list of keys comes in, O-9.4). Measure before quoting.
## Connect a miner
```
igneum-miner mine grpc://127.0.0.1:26610 1 100000000 rig1 --pool pool.example.net:4463 \
--evm-address 0xYOURADDRESS --worker-name rig1-gpu0 --worker ./igneum-worker-cuda --worker-args "--device 0 --pack packs/devnet"
igneum-miner mine none 4 100000000 rig1 --pool pool.example.net:4463 --evm-address 0xYOURADDRESS --worker-name rig1-cpu
```
The first argument is the member's own node (its verifier: seed checks, voting with its own key, a second road for
found blocks) or `none` (hashes, does not vote). `--evm-address` is the payout address, `--worker-name` the label on the
page. HiveOS: Flight Sheet pool URL `pool://pool.example.net:4463` (`packaging/hive/README.md`). In pool mode the miner
software takes no dev fee; the pool's fee is in the welcome line the miner prints.
## The API
| Path | What |
|---|---|
| `/api/stats` | `pool` (hashrate, miners, workers, shares, blocks 24 h and total, last block, fee, min payout, luck, effort, paid, share check cost) and `network` (chain id, difficulty, hashrate, DAA, reward) |
| `/api/blocks?limit=N` | blocks found: hash, DAA, time, finder, status (pending, confirmed, orphan), reward, effort, the PPLNS split |
| `/api/miners/<address>` | hashrate (10 min, 1 h, reported), shares, workers (with shift, proving flag, online), balance, paid, payments |
| `/api/payments?limit=N` | every payout with its transaction hash and receipt status |
| `/api/pool-stats` | the flat camelCase object pool dashboards poll (hashrate, miners, workers, blocks, lastBlock, fee, minPayout) |
The field lists are constants in `src/api.rs`; `cargo test` checks the fixtures in `tests/fixtures/` against them.
Captured responses from the measured run are the fixtures.
## Files
| File | What |
|---|---|
| `src/main.rs` | start-up, tasks, the status line, shutdown snapshot |
| `src/config.rs` | flags and defaults |
| `src/protocol.rs` | the spec 09 messages, newline JSON |
| `src/server.rs` | member connections: hello, authorize (proof of possession checked), shares, solutions, stats |
| `src/node.rs` | templates per member, jobs, block submission, the blue-set walk that confirms or orphans, network numbers, vardiff ticks |
| `src/verify.rs` | the share check on the warp verifier, the spec's codes |
| `src/vardiff.rs` | the share shift per member |
| `src/pplns.rs` | the window, the split, the distribution with the fee |
| `src/payout.rs` | the payout key, EIP-1559 transfers through eth_ JSON-RPC, receipts, dry run |
| `src/state.rs` | the ledger and its snapshot |
| `src/api.rs` | the HTTP server, the routes, the field contracts |
| `web/index.html` | the page |
| `tools/measure.mjs` | the private-network measurement |