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.

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:

# key paths — presence of protocol_key makes this a validator
protocol_key: keys/protocol.key
pq_network_key: keys/network-pq.key
checkpoint_key: keys/checkpoint.key
checkpoint_slh_key: keys/checkpoint-slh.key
db_path: /var/lib/frost/consensus
journal_path: /var/lib/frost/driver-journal.jsonl
engine_url: http://127.0.0.1:8551
engine_jwt: /var/lib/frost/jwt.hex
ingress_listen: 0.0.0.0:8645
metrics_listen: 127.0.0.1:9184

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.
  • Fresh-chain guard. A node expecting genesis refuses a non-genesis execution layer.

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.

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.

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":["0xdb703",false]}'

Comparing a block hash against another node is the real test. Identical hashes mean your node is reproducing the network’s chain exactly, which is the whole point.