Running frost-node
frost-node run --config node.yamlOne process is one node: a consensus authority (validator or follower), an execution driver, and an ingress. The execution layer runs as a separate process alongside it, reached over the Engine API.
What the process does
Section titled “What the process does”For a validator, continuously:
- Participates in the consensus DAG, proposing and signing blocks.
- Receives the committed transaction order.
- Drives the execution layer over the forced-transaction Engine API profile: pass the committed list, get the payload, deliver it as the new head.
- Accepts user submissions on the ingress, validates them, and forwards them to consensus.
- Serves the observer stream to allowlisted followers and, when
bootstrap_serveis set, the checkpoint-bootstrap endpoints joining nodes use.
For a follower, the same minus consensus participation: it streams the committed order from a validator’s observer endpoint and drives its own execution layer to the identical chain.
Configuration
Section titled “Configuration”The node config is local to the machine:
# the chain-defining manifest, identical on every nodemanifest: /etc/frost/manifest.yaml
# keys — presence of protocol_key makes this a validatornetwork_key: /var/lib/frost/keys/network.keypq_network_key: /var/lib/frost/keys/network-pq.key # selects the fully post-quantum transportprotocol_key: /var/lib/frost/keys/protocol.key# checkpoint_keys_dir defaults to protocol_key's directory, which is where# keygen wrote checkpoint.key and checkpoint-slh.key
db_path: /var/lib/frost/consensus-dbjournal_path: /var/lib/frost/driver-journal.jsonl # default: beside db_pathlisten_address: /ip4/0.0.0.0/udp/9000 # bind override; the manifest carries the advertised address
engine_url: http://127.0.0.1:8551engine_jwt: /var/lib/frost/jwt.hex
ingress_listen: 0.0.0.0:8645el_url: http://127.0.0.1:8545 # required with ingress_listenmetrics_listen: 127.0.0.1:9184
# the fleet's shared bearer token: gates frost_nodeHealth here and, on a# bootstrap-serving node, the snapshot downloadbootstrap_token: <fleet bearer token>Relative paths resolve against the config file’s directory. manifest,
network_key, db_path, engine_url and engine_jwt are required;
el_url is required whenever ingress_listen is set. Local policy —
consensus_retention, observer_allowlist, bootstrap_serve,
bootstrap_from — is described under
Keys and committee.
The chain-defining manifest is separate and identical on every node. See Keys and committee.
Startup checks
Section titled “Startup checks”frost-node run refuses to start rather than joining a chain it cannot verify:
- Genesis cross-check. The execution layer’s genesis
extraDatamust carry the manifest’s committee digest. - Journal reconciliation. The write-ahead commit journal is reconciled against the execution layer’s head before anything is driven.
- Genesis hash pin. When the manifest pins
el_genesis_hash, the execution layer’s genesis block hash must match it — a tamperedgenesis.jsonkeeps the rightextraDatabut moves the hash. - Fresh-chain guard. A node expecting genesis refuses a non-genesis execution layer.
- Prune floor versus journal. If startup replay would begin below the commit the consensus store has pruned to, the node cannot recover locally and says so, rather than failing mid-replay.
- Configuration sanity.
consensus_retention.commitsmust cover the recovery replay reach andmin_commitsmay not exceed it; everyobserver_allowlistentry must parse, so one mistyped key is a startup failure naming its index rather than one silently locked-out follower; andbootstrap_serverequiresbootstrap_token.
Each of these is a case where continuing would mean building a different chain than the network. Refusing to start is the correct outcome.
Fail-stop
Section titled “Fail-stop”The node halts loudly on any execution-layer error. This is deliberate and it is part of the execution/consensus contract.
The sharpest case is the journal assertion. Every rebuild during recovery is checked against the block hash the journal recorded. A mismatch means nondeterministic execution, and the node halts rather than continuing. A node that quietly proceeds past a determinism failure is exactly how a chain splits; halting is a bad outcome, forking is a worse one.
Three more conditions halt a node the same way:
- Divergence. Validators attest the head they executed. When peers holding
at least
f+1stake attest a different head from this node’s, the node halts — it may be the diverged side.divergence_halt_thresholdcan be raised to2f+1; nothing weaker is offered. - Checkpoint-root signing failure. A validator verifies its own checkpoint-root signature after producing it. A signature that does not verify is a fail-stop, not a retry — only a custody-backend outage is retried. And a halting node stops signing head attestations at once.
- Checkpoint staleness. The freshness gate alarms and then halts when certification falls too far behind the head. See Bootstrap and recovery.
Configure your supervisor to restart on exit, but alert on repeated restarts rather than treating them as routine — a node halting repeatedly is telling you something.
Shutdown
Section titled “Shutdown”SIGTERM or SIGINT stops gracefully. Let it finish; a clean shutdown lets the
journal and the consensus database close consistently, which makes the next
start a resume rather than a recovery.
Restart safety
Section titled “Restart safety”The driver appends every block-producing commit to a write-ahead journal before the committing Engine API call. On startup:
- Clean restart → resume after the last journaled commit.
- Execution layer lost blocks (crash, SIGKILL, recovered at an earlier state) → those blocks are re-driven from replayed consensus commits, each rebuild asserted against the journaled hash.
- Irreconcilable disagreement → the node refuses to start.
This has been verified by fault injection in the exact crash window between journal append and commit.
Containerised deployment
Section titled “Containerised deployment”The reference deployment ships digest-pinned images and a generated Compose file:
docker compose up -ddocker compose logs -f frost-nodedocker compose stop # graceful; expect clean exit codesRun with a restart policy. Node data lives on volumes; keys should not live in the image.
The runtime images are a digest-pinned base plus measured files, and the
recorded digests cover the binary, the image configuration and the whole root
filesystem. On a frost-reth host the image’s geth entrypoint is a POSIX
shell compatibility shim that maps the shared command line onto frost-reth,
so the Compose file is the same for either client.
Verify image digests against the recorded values before running — see Requirements. Consensus-critical parameters are compiled in, so running an unverified build risks splitting from the network rather than joining it.
Expose the ingress (8645) if you serve users. Keep the execution layer RPC (8545), the Engine API (8551), and metrics (9184) on localhost. See Requirements.
Checking it works
Section titled “Checking it works”# the node is following the chaincurl -s http://127.0.0.1:8645 -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
# and agrees with the public network on block hashescurl -s https://rpc.frostfi.net -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["latest",false]}'Comparing a block hash against another node is the real test: take the height the public endpoint reports and ask your node for the same block. Identical hashes mean your node is reproducing the network’s chain exactly, which is the whole point.