igneum/pool
2026-10-07 14:03:05 +00:00
..
src Open pool: the seeds check accepts another node's view of an epoch when its seed is a block the member's node holds at the seed depth (a DAG settling below the lead), the node's epochs kept by span; the gate harness starts the nodes in sequence with --addpeer and reads their peers and DAA scores by RPC before and during the run (the first box run was three DAG partitions) 2026-10-07 14:03:05 +00:00
tests/fixtures Pool finished (mission item 11): pool-0's fee published beside the dev fee, TLS 1.3 on the member port with the authorize binding (spec 9.3, O-9.7 closed), the page rows Q68 to Q73, and the open pool: a share sidechain with no operator (spec 9.12) 2026-10-07 14:03:05 +00:00
tools Open pool: the seeds check accepts another node's view of an epoch when its seed is a block the member's node holds at the seed depth (a DAG settling below the lead), the node's epochs kept by span; the gate harness starts the nodes in sequence with --addpeer and reads their peers and DAA scores by RPC before and during the run (the first box run was three DAG partitions) 2026-10-07 14:03:05 +00:00
web Pool finished (mission item 11): pool-0's fee published beside the dev fee, TLS 1.3 on the member port with the authorize binding (spec 9.3, O-9.7 closed), the page rows Q68 to Q73, and the open pool: a share sidechain with no operator (spec 9.12) 2026-10-07 14:03:05 +00:00
.gitignore Pool v0: igneum-pool (spec 09 newline JSON, per-member templates with the member's vote key, vardiff, CPU warp-verified shares, PPLNS on the EVM side with a 1% default fee and dry run, stats API and page), the Hive hooks' pool:// mode, the private-network measurement harness 2026-10-06 18:40:21 +00:00
Cargo.lock Pool finished (mission item 11): pool-0's fee published beside the dev fee, TLS 1.3 on the member port with the authorize binding (spec 9.3, O-9.7 closed), the page rows Q68 to Q73, and the open pool: a share sidechain with no operator (spec 9.12) 2026-10-07 14:03:05 +00:00
Cargo.toml Pool finished (mission item 11): pool-0's fee published beside the dev fee, TLS 1.3 on the member port with the authorize binding (spec 9.3, O-9.7 closed), the page rows Q68 to Q73, and the open pool: a share sidechain with no operator (spec 9.12) 2026-10-07 14:03:05 +00:00
README.md Open pool: the retarget clamps against the window's mean (never compounded share by share), the ceiling in u128 (a 2^49 first target shifted by 20 wrapped to 0 on the box), a deeper reorg depth and a progressive sync when parents are missing; the fast-time file carries pool_split_activation_daa (never); the gate runs on build-1 2026-10-07 14:03:05 +00:00

igneum-pool: the Igneum mining pool (pool-0, and the open pool)

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
--tls-cert, --tls-key none TLS 1.3 on the member port (spec 9.3) from a PEM chain and key
--tls-self-signed off TLS 1.3 from a self-signed pair made under the data dir on first start; the pool prints the certificate's pin, members pass it as --pool-pin
--allow-plain off plain TCP members on testnet or mainnet (the devnet allows plain by default; the public networks refuse to start in the clear without this)
--alert-webhook none Q72: an http:// URL POSTed once per address that sent shares before and none for ten minutes
--open off the open pool: the share sidechain with no operator (below)
--p2p-listen, --peer 0.0.0.0:<member port + 10>, none the open pool's share gossip: where to listen, who to connect to
--chain-share-s, --window-shares, --open-dev-fee-percent, --open-chain, --chain-genesis-target64 10, 2160, 1, igneum-open-v1, the network's genesis block target eight times easier the share chain's constants; every member of one chain holds the same or its shares are refused

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.

TLS on the member port (spec 9.3, 7 October 2026)

--tls-self-signed makes tls-cert.pem and tls-key.pem under the data directory on first start and prints the certificate's pin (BLAKE2b("igneum-pool-cert-pin-v1" || DER), 64 hex); a member connects with igneum-miner ... --pool host:4463 --pool-pin <pin>. A public pool with a certificate from a root the members' systems trust runs --tls-cert chain.pem --tls-key key.pem, and members use --pool-tls. Over TLS every authorize carries the binding: the member's BLS signature over this connection's TLS exporter (label EXPORTER-igneum-pool-binding, context the chain id), so an authorize replayed on another connection is refused (src/tls.rs, the test self_signed_tls_pins_exports_and_binds_one_connection). HiveOS: pools://host:4463 in the Flight Sheet for --pool-tls, POOL_PIN=<pin> in the extra config for a pinned pool. Testnet and mainnet pools refuse to start in the clear unless --allow-plain says so.

The open pool (--open, 7 October 2026; docs/plans/pool.md section 10; spec 09 section 9.12)

The same binary beside a member's own node, with no payout key: the member's miners connect to it on loopback (or a rig's LAN), it builds their templates from the member's node with the member's key in the header and the member's own address in the coinbase, stamps the share chain's parent (IGNS) and the window's split (IGNP) into every coinbase, and gossips shares with the other members' daemons (--peer). A block found on the chain pays the window from its own coinbase by the execution layer's split rule (pool_split_activation_daa in the node's override file; blocks below it pay the finder alone). Nobody holds a balance; the only fee is the software dev fee as one entry of the split (the solo miner's 1 percent).

igneum-pool --open --node grpc://127.0.0.1:26610 --listen 127.0.0.1:4463 --http 127.0.0.1:4480 --data-dir ~/.igneum-open \
            --p2p-listen 0.0.0.0:4473 --peer other-member.example:4473 --network devnet
igneum-miner mine grpc://127.0.0.1:26610 1 100000000 me --pool 127.0.0.1:4463 --evm-address 0xYOURADDRESS --worker-name gpu0 --worker ./igneum-worker-cuda

Every share the daemon's own members find is one line of <data-dir>/shares.log, self-certifying; igneum-pool verify-share '<line>' [--block '<raw block json>'] re-hashes it and, against a block (its raw JSON from /api/open/share/<hash>), says whether that block's split pays the share's address (BLOCK PAYS, exit 0; BLOCK DROPS, exit 3). /api/open is the chain (height, tip, target, the window's split), /api/open/shares?from=&limit= the shares, /api/payments the coinbase credits by block. The gate: pool/tools/open-gate.mjs (100 members, 10 daemons, 4 nodes on a fast-time network; pool.md section 10.5 has the numbers).

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 --snapshot-interval-s (15 s): balances, blocks, payments, the hashrate samples and the check costs (Q70: a restart keeps the rate tiles and the luck; the loss window is one interval), and the hourly history per address (7 days). 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)
/metrics Prometheus text (Q72)
/health 200 when the node is synced and answered a template in the last 30 s, else 503 with the node's state (Q72)
/api/open, /api/open/shares, /api/open/share/<hash> the open pool's share chain

The page (Q69 to Q73): every number formatted as the site's are (en-GB separators, hash rate to one decimal on a kH to PH ladder, IGN to 4 decimals on tiles and 6 in tables), luck in MiningPoolStats' convention (expected over actual), the payments of a looked-up address under its workers with a 7-day hourly sparkline, the node's state in words, and the network's finality state beside the sentence "payouts follow blue confirmation, not finality".

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, /metrics, /health
src/tls.rs TLS 1.3 on the member port, the self-signed pair, the exporter of the binding
src/sidechain.rs the open pool's share chain: shares, the target, the window's split, fork choice, verify-share
src/open.rs the open pool's daemon side: stamping templates, accepting shares, the shares log, the API
src/p2p.rs the share gossip between members
tools/open-gate.mjs the open pool's gate on a fast-time network
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, with the line vendor/igneum-node in /srv/builds/<worktree>/.igneum-scratch-spare so the mirror's clean before every build keeps it (7 October 2026: without the line the first box build of the pool crate removed it and cargo found no fork). 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.