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
| vCPU | RAM | Disk | AWS | Baremetal |
|---|---|---|---|---|
| 4 | 16 GB | 500 GB NVMe (pruned) | c6i.xlarge / m6i.xlarge + gp3 | 4-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).
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 .envImages 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.
| Layer | Image |
|---|---|
| CL | ghcr.io/foreseerco/krypton-beacond:v1.3.9-473374 (epoch-mint patched) |
| EL | ghcr.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
beacondhome per validator (val-0 … val-N), each holding that validator'spriv_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:
| Var | Value | Why |
|---|---|---|
KRYPTON_ROLE | validator | documentation only |
PAYLOAD_BUILDER | true | validators build blocks |
RPC_HOST | 127.0.0.1 | RPC stays on loopback — never exposed |
RPC_API | eth,net,web3 | minimal namespace |
EL_SYNC_MODE | --full | pruned |
CL_PERSISTENT_PEERS | pin the genesis set, e.g. node1@ip:26656,node2@ip:26656 | keeps the validator set connected |
EL_MEM_LIMIT | 12g | per the HW matrix |
CL_MEM_LIMIT | 6g |
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
# 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.shRun it under systemd (recommended) — run bootstrap.sh once first to lay down dirs/bundle/JWT/key, then:
sudo cp krypton-node.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now krypton-node
journalctl -u krypton-node -fThe 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 byKRYPTON_UID; keepDATA_DIR/cl/configat0700.bootstrap.shchmods the key600if 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):
| Signal | Command | Healthy |
|---|---|---|
| Sync | curl -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 height | cast block-number --rpc-url http://127.0.0.1:8545 | climbing |
| Chain-id | cast chain-id --rpc-url http://127.0.0.1:8545 | 473374 |
| W4 liveness | cast balance 0x0000000000000000000000000000000000011171 | INFLATION_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:
- Resolve the new digest(s) and edit
KRYPTON_CL_IMAGE/KRYPTON_EL_IMAGEin.env. All operators move to the same digests at the agreed height/time. sudo systemctl restart krypton-node(itsExecStartPrere-pulls), ordocker compose pull && docker compose up -d.
Stop order is CL first, then EL (the CL consumes the EL's engine API):
docker compose stop cl # 5m grace period
docker compose stop el # 2m grace periodRollback: 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
| Symptom | Likely cause | Fix |
|---|---|---|
No peers (n_peers: 0) | Wrong EXT_IP; firewall not opening 30303/26656; bad bootnodes/seeds | Confirm EXT_IP is reachable; open 30303 tcp+udp and 26656 tcp from 0.0.0.0/0; recheck peer strings |
| CL crashes at engine startup | Chain-id mismatch / wrong bundle | bootstrap 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 behind | Check peers; confirm DATA_DIR is NVMe; check EL logs for MDBX/IO errors |
| Engine auth 401s | EL and CL see different jwt.hex | Both mount the same JWT_PATH; restart both if regenerated; 0600/correct owner |
| EL OOM / restarts | EL_MEM_LIMIT too low | Raise 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 down | The 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 on127.0.0.1; engine API (8551), CometBFT RPC (26657), and metrics (9001) are loopback/VPN-only — enforced bynftables/ the AWS SG. See Ports & firewall. - Containers run as a non-root UID;
DATA_DIRis0700; the JWT is0600; the network bundle is mounted read-only. - Images pinned by
@sha256digest; 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.