Skip to content

Running frost-node

Terminal window
frost-node run --config node.yaml

One 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.

For a validator, continuously:

  1. Participates in the consensus DAG, proposing and signing blocks.
  2. Receives the committed transaction order.
  3. Drives the execution layer over the forced-transaction Engine API profile: pass the committed list, get the payload, deliver it as the new head.
  4. Accepts user submissions on the ingress, validates them, and forwards them to consensus.
  5. Serves the observer stream to allowlisted followers and, when bootstrap_serve is 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.

The node config is local to the machine:

# the chain-defining manifest, identical on every node
manifest: /etc/frost/manifest.yaml
# keys — presence of protocol_key makes this a validator
network_key: /var/lib/frost/keys/network.key
pq_network_key: /var/lib/frost/keys/network-pq.key # selects the fully post-quantum transport
protocol_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-db
journal_path: /var/lib/frost/driver-journal.jsonl # default: beside db_path
listen_address: /ip4/0.0.0.0/udp/9000 # bind override; the manifest carries the advertised address
engine_url: http://127.0.0.1:8551
engine_jwt: /var/lib/frost/jwt.hex
ingress_listen: 0.0.0.0:8645
el_url: http://127.0.0.1:8545 # required with ingress_listen
metrics_listen: 127.0.0.1:9184
# the fleet's shared bearer token: gates frost_nodeHealth here and, on a
# bootstrap-serving node, the snapshot download
bootstrap_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.

frost-node run refuses to start rather than joining a chain it cannot verify:

  • Genesis cross-check. The execution layer’s genesis extraData must 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 tampered genesis.json keeps the right extraData but 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.commits must cover the recovery replay reach and min_commits may not exceed it; every observer_allowlist entry must parse, so one mistyped key is a startup failure naming its index rather than one silently locked-out follower; and bootstrap_serve requires bootstrap_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.

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+1 stake attest a different head from this node’s, the node halts — it may be the diverged side. divergence_halt_threshold can be raised to 2f+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.

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.

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.

The reference deployment ships digest-pinned images and a generated Compose file:

Terminal window
docker compose up -d
docker compose logs -f frost-node
docker compose stop # graceful; expect clean exit codes

Run 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.

Terminal window
# the node is following the chain
curl -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 hashes
curl -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.