Skip to content

Validator node

A validator signs and proposes blocks — the highest-stakes role on Krypton. It runs the same two-container stack (beacond + bera-reth) as every node, plus a CometBFT signing key (priv_validator_key.json) and PAYLOAD_BUILDER=true. This page covers install → deploy → operate for a validator on the public testnet (chain-id 473374).

The double-sign hazard is the #1 way to lose stake

Never run the same priv_validator_key.json on two nodes at once. Two nodes signing one key produce conflicting votes at the same height — equivocation, which is slashable. No active/active, no failover that can run both. See Operate → Key management and use threshold signing (Horcrux) in production.

Hardware

vCPURAMDiskAWSBaremetal
416 GB500 GB NVMe (pruned)c6i.xlarge / m6i.xlarge + gp34-core x86-64, 16 GB, 500 GB NVMe

A validator must be 24/7 with a stable public IP — pay for a budget dedicated server, not a residential line. Downtime has a consensus cost now and a slashing/inactivity cost on mainnet that dwarfs any hardware saving. Full matrix and cost tiers: Hardware specs.

Install

Every host needs the shared base first — see Prerequisites: Docker Engine + Compose v2, an NVMe volume mounted at DATA_DIR, NTP time-sync (CometBFT is time-sensitive — skew causes missed proposals), a host firewall, and the non-root UID (10000:10000).

bash
git clone https://github.com/foreseerco/block-l1-evm-mainnet krypton
sudo mkdir -p /opt/krypton/deploy
sudo cp krypton/l1/from-source/deploy/{docker-compose.yml,.env.example,bootstrap.sh,nftables.conf,krypton-node.service} /opt/krypton/deploy/
cd /opt/krypton/deploy
sudo cp .env.example .env

Images default to the testnet tags below. In production, pin both by @sha256 digest in .env — mutable tags are a consensus and supply-chain risk, and all operators must pin the same digests. See Building the images.

LayerImage
CLghcr.io/foreseerco/krypton-beacond:v1.3.9-473374 (epoch-mint patched)
ELghcr.io/foreseerco/krypton-bera-reth:v1.3.3 (fee-router patched, chain-agnostic)

Deploy

Genesis vs join

There are two paths, and which one you are on changes what you do with your key.

  • Genesis ceremony (Path A) — run once by the launch coordinator to mint the network. It emits one beacond home per validator (val-0 … val-N), each holding that validator's priv_validator_key.json + node_key.json. Each home is distributed privately to its operator over a secure channel. Full procedure: Genesis & the network bundle.
  • Join an existing testnet (Path B) — the normal case. You fetch the published bundle, set EL_BOOTNODES / CL_SEEDS, and place your ceremony key before first start.

Place your key BEFORE bootstrap

Copy your ceremony val-i/config/priv_validator_key.json to DATA_DIR/cl/config/priv_validator_key.json before running bootstrap.sh. If it is absent, bootstrap WARNs and the node will not sign.

Configure .env (validator preset)

Set the shared core (MONIKER, EXT_IP = this host's reachable public IP, EL_BOOTNODES, CL_SEEDS, DATA_DIR, NETWORK_DIR, JWT_PATH), then uncomment the validator preset. The validator-specific differences:

VarValueWhy
KRYPTON_ROLEvalidatordocumentation only
PAYLOAD_BUILDERtruevalidators build blocks
RPC_HOST127.0.0.1RPC stays on loopback — never exposed
RPC_APIeth,net,web3minimal namespace
EL_SYNC_MODE--fullpruned
CL_PERSISTENT_PEERSpin the genesis set, e.g. node1@ip:26656,node2@ip:26656keeps the validator set connected
EL_MEM_LIMIT12gper the HW matrix
CL_MEM_LIMIT6g

EXT_IP is mandatory

EXT_IP must be this host's reachable public IP (advertised via --nat=extip: on the EL and --p2p.external-address on the CL). A wrong EXT_IP means no inbound peers.

Bring it up

bash
# Installs the bundle (asserts chainId == 473374), generates the Engine JWT,
# sanity-checks the validator key, then `docker compose up -d`:
BUNDLE_URL=https://…/krypton-testnet-bundle.tar.gz sudo -E ./bootstrap.sh

Run it under systemd (recommended) — run bootstrap.sh once first to lay down dirs/bundle/JWT/key, then:

bash
sudo cp krypton-node.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now krypton-node
journalctl -u krypton-node -f

The CL depends_on the EL being healthy, so the EL starts first automatically.

Operate

Key management

The signing key lives at DATA_DIR/cl/config/priv_validator_key.json (inside the CL container: /home/beacond/.beacond/config/priv_validator_key.json).

  • Permissions: 0600, owned by KRYPTON_UID; keep DATA_DIR/cl/config at 0700. bootstrap.sh chmods the key 600 if present.
  • Backup: back it up off-host, encrypted. Losing it loses the validator identity; leaking it enables impersonation/equivocation.
  • Migrating hardware: fully stop the old node (docker compose down, confirm the container is gone) before starting the new one with the key.

Production key path — threshold signing + secrets

A raw single key on the node is the double-sign foot-gun. The production path is threshold signing (Horcrux) — a 2-of-3 remote signer so no single host holds the whole key and no two co-signers can double-sign — with the key shards and Engine JWT delivered by secrets (Vault / KMS) rather than living on disk. Do this before any value-bearing deployment.

Monitoring signals

A ready Prometheus + Grafana stack is documented in Monitoring; enable CL metrics in beacond's config.toml ([instrumentation] prometheus = true). Quick CLI checks (RPC is loopback-only on a validator):

SignalCommandHealthy
Synccurl -s http://127.0.0.1:26657/status | jq '.result.sync_info'catching_up: false, height climbing
Peers (CL)curl -s http://127.0.0.1:26657/net_info | jq '.result.n_peers'> 0, stable
EL heightcast block-number --rpc-url http://127.0.0.1:8545climbing
Chain-idcast chain-id --rpc-url http://127.0.0.1:8545473374
W4 livenesscast balance 0x0000000000000000000000000000000000011171INFLATION_SINK grows every block

A stalled INFLATION_SINK (0x…011171) on an advancing chain confirms the epoch-mint patch is misbehaving — alert on it.

Upgrade & rollback

Coordinated, pinned-digest bumps across the whole validator set:

  1. Resolve the new digest(s) and edit KRYPTON_CL_IMAGE / KRYPTON_EL_IMAGE in .env. All operators move to the same digests at the agreed height/time.
  2. sudo systemctl restart krypton-node (its ExecStartPre re-pulls), or docker compose pull && docker compose up -d.

Stop order is CL first, then EL (the CL consumes the EL's engine API):

bash
docker compose stop cl      # 5m grace period
docker compose stop el      # 2m grace period

Rollback: revert the digests and restart. If the EL head must roll back, beacond rollback reverts the CL one block — use only when coordinated, and never in a way that could double-sign.

No window where both nodes hold the key

On any upgrade or migration, ensure there is no moment where the old and new node both hold priv_validator_key.json. Announce coordinated upgrades with a target height/time so the set moves together.

Troubleshooting

SymptomLikely causeFix
No peers (n_peers: 0)Wrong EXT_IP; firewall not opening 30303/26656; bad bootnodes/seedsConfirm EXT_IP is reachable; open 30303 tcp+udp and 26656 tcp from 0.0.0.0/0; recheck peer strings
CL crashes at engine startupChain-id mismatch / wrong bundlebootstrap aborts if chainId != 473374; re-fetch the testnet bundle; CL image must be the -473374 tag
Stuck sync (catching_up: true)Few peers; slow disk; EL behindCheck peers; confirm DATA_DIR is NVMe; check EL logs for MDBX/IO errors
Engine auth 401sEL and CL see different jwt.hexBoth mount the same JWT_PATH; restart both if regenerated; 0600/correct owner
EL OOM / restartsEL_MEM_LIMIT too lowRaise per the HW matrix; ensure NVMe; on AWS use gp3/io2
Chain halt (nothing finalizing)>1/3 of voting power offline — e.g. an over-weighted validator went downThe offline-whale halt. Bring the offline validators back; the chain self-heals once ≥2/3 power is online. Prevent by honouring max-effective-balance / staged activation — bound any one validator's weight

Validator-set sizing is a safety property

A single validator whose stake exceeds the >1/3 BFT fault budget can wedge finalization if it goes offline — and the chain has no self-heal while halted (the inactivity leak needs the chain to progress, which it can't). Recovery is operational: restore enough online voting power. Honour the spec's max-effective-balance cap and staged activation; do not deploy a single dominant validator. More: Troubleshooting.

Security

  • Public surface is only the P2P ports (30303, 26656). JSON-RPC stays on 127.0.0.1; engine API (8551), CometBFT RPC (26657), and metrics (9001) are loopback/VPN-only — enforced by nftables / the AWS SG. See Ports & firewall.
  • Containers run as a non-root UID; DATA_DIR is 0700; the JWT is 0600; the network bundle is mounted read-only.
  • Images pinned by @sha256 digest; SSH restricted to an admin CIDR.
  • Key handling: 0600, off-host encrypted backup, the double-sign prohibition, and the threshold-signing + secrets path for production.

Status

The deploy artifacts are config-validated but not yet run end-to-end on real cloud/baremetal hardware; the live determinism/join/exit proofs ran under Kurtosis. Treat your first validator as a bring-up — verify chain-id, peers, sync, and INFLATION_SINK growth before trusting it. Mainnet (47337) reuses this exact procedure post-audit. See Networks & chain IDs.

Operator docs. Testnet chain-id 473374; mainnet 47337 (gated on external audit). Not financial advice.