The fleet agent's row from the 6 October night: the daemon ran 20 minutes as a process serving nothing while its node
answered no template and its member port had been held. Now main binds the member and API listeners before anything
else (server::bind_listener; a failure is "POOL NOT STARTED: cannot bind the members listener on <addr>: <os error>",
exit 2), probes the node for a template with pow_epoch, retried for --node-wait-secs (default 60) with a line per
attempt, else exit 3, and prints a STATUS line every 30 s (members, jobs issued, shares, blocks, template failures, the
node's state: ok, NODE NOT ANSWERING, NO TEMPLATE YET). fetch_job counts template failures and the last success.
Test server::bind_tests: a held port is refused with the address in the message, the freed port binds. Suite 23 of 23
on igneum-build-1. Protocol and API unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 90b5243e31)
176 lines
11 KiB
Markdown
176 lines
11 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 |
|
|
|
|
## Building on igneum-build-1 (6 October 2026)
|
|
|
|
Every Linux build and suite runs on the box through `tools/build-remote.sh` from this directory (`IGNEUM_AGENT=pool`). The
|
|
crate reads the fork through the `vendor/igneum-node` symlink; the build library syncs the fork worktree it points at as a
|
|
whole repository, and the symlink is made once on the box by hand: `ln -s <fork worktree name> /srv/builds/<worktree>/vendor/igneum-node`.
|
|
Artefacts land in `pool/target-remote/release/igneum-pool` (x86_64 Linux); the Mac builds no Linux binary.
|
|
|
|
Start-up contract (7 October 2026): the daemon exits 2 when `--listen` or `--http` cannot be bound, exits 3 when the node
|
|
answers no template with `pow_epoch` within `--node-wait-secs` (default 60), and prints `node <url> answers templates: ...`
|
|
before it accepts a member; a `STATUS` line every 30 s names the member count, jobs issued, shares and the node's state.
|