igneum/docs/plans/seed-nodes.md
igneum-labs 47b08d2706 seed-nodes: igneum-seed-1 live and synced; relay path from the Mac; prices in the account's currency (USD)
igneum-seed-1 (Hetzner cx23, fsn1, 188.245.5.161:26611): built on the VM in 1,530 s, synced to the live devnet
(12,204 blocks, same sink as the live node) through a non-mining relay igneumd on the Mac (the live node's addPeer
RPC is refused in safe mode); the live node and the Windows PC learned the seed's address by peer exchange and dialled
it. seeds.txt written. Hetzner prices corrected to USD (pricing API currency) in the plans, READMEs and scripts;
current-generation types per location (cx23 EU, cpx22 sin, cpx21 US) in the cloud-devnet config.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 22:27:52 +00:00

8 KiB

Seed nodes: plan

3 October 2026. Scripts in infra/seed-nodes/. The first seed is live.

The first seed

Item Value
Name igneum-seed-1
Address 188.245.5.161:26611 (Hetzner primary IPv4, auto-delete off, so a rebuilt server keeps it)
Provider, location, type Hetzner Cloud, Falkenstein (fsn1), cx23 (2 Intel vCPU, 4 GB, 40 GB), Debian 12
Cost USD 6.49 per month net, USD 7.79 gross, USD 0.0104 per hour, 20 TB traffic included (Hetzner API, 3 Oct 2026); plus the primary IPv4, USD 0.60 per month net, 0.72 gross (pricing API). Total about USD 7.09 net, USD 8.51 gross per month. The account bills in USD: the pricing API reports currency: USD, VAT 20%
Firewall inbound tcp 26611 from anywhere, tcp 22 from anywhere (the project lead's instruction; SSH_SOURCE=me narrows it to this Mac's IP), icmp; RPC bound to 127.0.0.1 only
Network the shared devnet (--devnet, no suffix), built from vendor/igneum-node HEAD d62708a8 plus the uncommitted finality v2 work and igneum-pow HEAD, with --features igneum-pow
Role p2p open, no mining, address manager on (serves RequestAddresses), --nodnsseed, UPnP off, --externalip set
How the Mac reaches it the Mac's live node sits behind NAT (192.168.68.64), so the seed cannot dial in; the Mac dials out. The addPeer RPC on the live node is refused ("Method unavailable in safe mode. Run the node with --unsaferpc"), so the working path is a relay: infra/seed-nodes/addpeer-from-mac.sh relay start, a second non-mining igneumd on the Mac (appdir /tmp/igneum-seed-relay, RPC 127.0.0.1:26680, wRPC 28680, p2p 127.0.0.1:26681) with --addpeer=192.168.68.64:26611 --addpeer=188.245.5.161:26611; it syncs from the live node and the seed syncs from it. The live node is untouched. When the live node is next restarted by hand, add --addpeer=188.245.5.161:26611 to its flags and the relay is no longer needed

infra/seed-nodes/seeds.txt holds exactly 188.245.5.161:26611.

Verified 3 Oct 2026, 22:19 to 22:26 UTC: build on the VM 1,530 s (25.5 min, 2 vCPU, 2 jobs, 6 GB swap unused); igneumd/2.1.0 started with the finality v2 parameters, "External address is publicly routable 188.245.5.161:26611"; the relay on the Mac connected to it at once (protocol 12) and to the live node (protocol 11); the seed completed IBD from the relay and reported isSynced: true at 22:25:39 UTC with 12,204 blocks and sink a0a776bb129c..., the same block count and sink the live node reported in the same second. Peer exchange: without any configuration on their side, the live node (/kaspad:2.1.0/, protocol 11) and the Windows PC's node (192.168.68.67, /igneumd:2.1.0/, protocol 11) learned the seed's address from the relay and dialled it themselves; the live node's getConnectedPeerInfo lists 188.245.5.161:26611 as an outbound peer, and the seed shows three inbound peers from the Mac's public IP. health.sh: OK igneum-seed-1 188.245.5.161 p2p=open unit=active rpc=yes synced=True blocks=12204 headers=12204 peers=3. The seed's own known-address table is empty because all three peers are behind NAT with no routable advertised address; the first public node that connects will populate it.

How the seed list reaches clients

  1. Baked into the node. vendor/igneum-node/consensus/core/src/config/params.rs holds the per-network seed list as dns_seeders: &'static [&'static str] on Params: MAINNET_PARAMS (line 617 on 3 Oct 2026), TESTNET_PARAMS (670), SIMNET_PARAMS (721), DEVNET_PARAMS (785), all &[] since the rename commit emptied Kaspa's nine mainnet and three testnet hostnames. The connection manager resolves each entry with (seeder, default_p2p_port).to_socket_addrs() (components/connectionmanager/src/lib.rs, dns_seed_single), so a plain IPv4 literal works as an entry with no DNS at all: dns_seeders: &["188.245.5.161"] on DEVNET_PARAMS is the whole change for the devnet, and the testnet list is the same shape with the testnet seeds. The port is the network's default p2p port (devnet 26611; the testnet port is still Kaspa's and must be set with the testnet genesis). The consensus engineer owns this edit. Two consequences for packages: a client that passes --nodnsseed ignores the baked list (kaspad/src/daemon.rs line 573: dns_seeders is emptied when --nodnsseed or --connect is given), so the Windows node package (proto-cuda/windows-node/start-node.ps1) and the cloud scripts must drop --nodnsseed once the list is baked; and the list is consulted only when the node is short of outbound peers, so a node with enough --addpeer entries never asks a seed.
  2. SEED_PEERS override in every package. Each launcher (Windows node, cloud devnet, seed nodes, the observer's helper node) reads SEED_PEERS (comma-separated ip:port) and turns every entry into --addpeer=<entry>; the baked list is the default when the variable is empty. This is what an operator uses when the baked list is stale between releases.
  3. DNS names only as a convenience. seed1.igneum.network and so on can point at the same addresses (the domains are on Vercel nameservers, so a record each), and dns_seeders accepts a hostname too; but the IPs are the source of truth because a DNS failure or a registrar problem must not stop bootstrapping, and because the public key of nothing is involved: a seed only hands out addresses, it cannot forge blocks.

Public testnet seed set

Three to five seeds across two providers and three regions. Proposed:

Seed Provider Location Type Per month net
igneum-seed-1 Hetzner Falkenstein (EU) cx23 USD 6.49 (live)
igneum-seed-2 Hetzner Ashburn (US east) cpx11 (2 GB) or cpx21 (4 GB) USD 20.49 or 37.49
igneum-seed-3 DigitalOcean Singapore (sgp1) s-2vcpu-4gb USD 24 (DO pricing page)
igneum-seed-4 (optional) DigitalOcean New York or Frankfurt s-2vcpu-4gb USD 24
igneum-seed-5 (optional) Hetzner Helsinki cx23 USD 6.49

Three seeds: about USD 50 per month; five: about USD 80 (approximate, mixed currencies). The US seed is the expensive one because Hetzner's current cx line is EU-only. Every seed is created with create-seed.sh (the DigitalOcean variant reserves an IP in the same way) and provisioned with provision-seed.sh, which adds the seeds already in seeds.txt as --addpeer entries so the seeds form a full mesh among themselves. health.sh checks them all.

Rotation

  1. Add before removing: create and provision the replacement, run health.sh until it is synced and has peers.
  2. Bake the new list (params.rs) and release packages with it; keep the old address in the list for one release so clients on the previous build still bootstrap.
  3. Keep the old IP alive until the release after that (a Hetzner primary IP or a DO reserved IP costs under USD 1 per month unattached, approximate), then delete the server and the IP, and remove the entry from seeds.txt and seeds.tsv.
  4. A compromised seed is the one case to remove first: delete the server, release the IP, bake and release the same day. The damage a bad seed can do is bounded (it hands out addresses; the node's handshake and PoW checks are unchanged), which is why the list may sit in a release rather than behind a signature.

Health and operations

health.sh (one line per seed: p2p port reachable from the Mac, unit active, RPC answering, synced, blocks and headers, connected peers, known and banned addresses, disk, memory, version); --watch repeats every minute. The seed's journal: ssh -i ~/.ssh/igneum_ed25519 root@188.245.5.161 journalctl -u igneumd -f. Updating the binary: provision-seed.sh again (it rebuilds on the VM) or BUILD_WHERE=bin to push a binary built by the cloud-devnet builder. The database format changes with some fork commits; a seed that refuses to start after an update is wiped (rm -rf /var/lib/igneum/*) and resyncs from its peers.

No-spend rule

Only the first seed spends tonight (approved). The remaining seeds and the 20-node network wait for the morning.