Run a FLOP validator
This is the current disposable devnet procedure. FLOP validators author blocks with BABE, vote on finality with AlephBFT, and serve assigned data through a DA worker. They do not need a GPU or TEE. A public testnet or mainnet operator needs the network inputs below before joining; do not use the development genesis keys or //Alice on a public chain.
| Input from network operator | Public placeholder | Devnet rehearsal |
|---|---|---|
| Chain and genesis | <CHAIN_SPEC_URL_OR_FILE> and published genesis hash | Common flop-devnet-N preset or reviewed raw spec |
| Software | <PINNED_NODE_IMAGE> and <PINNED_DA_IMAGE> | Two pinned images from one release |
| Peer discovery | <BOOTNODE_MULTIADDRESS> | Reachable multiaddress from an existing host |
| Admission | Published funding, stake, queue, and promotion policy | Devnet root funds and promotes |
| Monitoring | <PUBLIC_EXPLORER_URL> and <TELEMETRY_URL> | Local RPC and metrics |
The bracketed values are placeholders, not live addresses. Stop if a required input is missing or if chain spec, genesis, and software release do not agree. Current devnet admission is administrator controlled. It is not an open public onboarding service.
Prepare the host
Use Linux with Docker Engine, the Docker Compose plugin, curl, and jq installed. The join helper also needs Node.js and pnpm matching the repository's pinned toolchain. Give each validator its own persistent volumes and session keys. Protect the host and backups with encrypted storage and key-only SSH. Keep node RPC on loopback, metrics on loopback or a private network, and permit P2P and DA traffic only along the required peer routes. Never run two active processes with the same session keys: simultaneous voting can equivocate. For production planning, the current FLOP profile is 8 dedicated cores, 32 GB RAM, 4 TB NVMe including DA custody, and a reliable low-latency 1 Gbps link. These are planning estimates, not measured devnet minimums. Aleph Zero's hardware reference emphasizes fast I/O and reliable networking, but its storage sizes omit FLOP's DA custody.
From a FLOP repository checkout, make an untracked env file on each host:
cp infra/devnet/.env.example infra/devnet/.env
chmod 600 infra/devnet/.env
Set the fields in that host's infra/devnet/.env:
CHAIN=flop-devnet-6
FLOP_NODE_IMAGE=<PINNED_NODE_IMAGE>
FLOP_DA_VALIDATOR_IMAGE=<PINNED_DA_IMAGE>
HOST_IP=<REACHABLE_HOST_IP>
P2P_PORT=30333
PUBLIC_ADDR=/ip4/<REACHABLE_HOST_IP>/tcp/30333
BOOTNODES=<BOOTNODE_MULTIADDRESS>
DA_PORT=7000
DA_PUBLIC_ENDPOINT=http://<REACHABLE_HOST_IP>:7000
RPC_BIND_IP=127.0.0.1
RPC_METHODS=safe
Use the same chain preset or raw spec and pinned images on all hosts. Leave BOOTNODES empty for the first genesis host. For a disposable genesis host, set GENESIS_SEED to its distinct //Validator1 through //ValidatorN preset and set DA_VALIDATOR_SEED to the matching secretSeed derived with that same pinned node image. Later joiners leave GENESIS_SEED empty and use a fresh private 32-byte sr25519 seed for DA_VALIDATOR_SEED and their account. Never commit filled env files or send seeds in public tickets. An existing five-authority chain cannot simply switch to the six-authority preset; that requires a new genesis. For a genesis host only, derive its matching DA seed from the pinned image without printing it to the terminal (run from the repository root, after filling .env):
(
set -euo pipefail
cd infra/devnet
set -a; . ./.env; set +a
test -n "$GENESIS_SEED"
install -d -m 700 "$HOME/.local/share/flop"
umask 077
docker run --rm --entrypoint zkv-relay "$FLOP_NODE_IMAGE" \
key inspect "$GENESIS_SEED" --output-type json \
| jq -er .secretSeed > "$HOME/.local/share/flop/genesis-da-seed"
)
Put the saved secret in this host's private DA_VALIDATOR_SEED field using a local editor. Protect or remove the extra seed file after its backup policy is settled. The repository's docs/runbooks/devnet-add-validator.md has more chain-specific rehearsal detail.
Start the first host and get its peer ID from its local RPC:
cd infra/devnet
docker compose config --quiet
docker compose up -d
docker compose ps
curl -fsS -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"system_localPeerId","params":[]}' \
http://127.0.0.1:9944 | jq -r .result
On every host, compare the local genesis hash with the network operator's independently supplied EXPECTED_GENESIS_HASH (a public value). Do this before funding or registering. A new devnet's administrator must first review the raw chain spec and publish that expected hash; copying an unreviewed host's result as the expectation does not establish chain identity.
FLOP · Run a FLOP validator › Still in infra/devnet; supply the published public hash first.
# Still in infra/devnet; supply the published public hash first.
(
set -euo pipefail
: "${EXPECTED_GENESIS_HASH:?set the independently published genesis hash}"
actual_genesis_hash=$(curl -fsS -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"chain_getBlockHash","params":[0]}' \
http://127.0.0.1:9944 | jq -er .result)
test "$actual_genesis_hash" = "$EXPECTED_GENESIS_HASH" || {
echo 'Genesis mismatch; stop before funding or joining' >&2
exit 1
}
)
Continue only when this check exits successfully on that host.
Set each later host's BOOTNODES to a reachable /ip4/<host>/tcp/<port>/p2p/<peer-id> address, then start its Compose project. Open P2P and DA ports to the relevant hosts. A Docker container name is not a cross-host bootnode. The DA worker connects to its own node inside Compose; the advertised DA endpoint must be reachable by other validators. Bring up all genesis authorities promptly: an incomplete genesis committee may leave the finalized head stationary.
Join after genesis
The existing join helper is suitable for a single trusted devnet administration context. Its fund and promote actions require OPERATOR_SEED even though root signs them. An independent operator must not disclose a private seed to an administrator. External onboarding needs seedless admin actions and a published admission policy.
For a disposable rehearsal controlled by one administrator, follow this order:
- Generate a fresh private account seed on the joining host and set it as
DA_VALIDATOR_SEED. LeaveGENESIS_SEEDempty. Start the node and DA worker with the same chain and pinned images as the existing hosts. - Fund the account above the live greater of
minimumValidatorStakeandvalueCoupledStakeFloor, with extra for fees. The devnet helper'sfundaction usessudo(balances.forceSetBalance); the faucet cannot cover this floor. - Temporarily recreate the joining validator with
RPC_METHODS=unsafewhile its host RPC remains bound to127.0.0.1. Run the helper'sjoinaction locally. It callsauthor_rotateKeysWithOwner, checks the keystore, submitssession.setKeyswith the ownership proof, and callsvalidators.register. RestoreRPC_METHODS=safeimmediately; do not putunsafein.env. - Fund again for fees and DA endpoint deposits: registration freezes the account's full reducible balance. Root reviews queue order before its
promoteaction submitssudo(validators.rotate(0, 1)). Registration alone only queues the account. - Run the helper's
verifyaction after promotion. It waits for session activation, checks live BABE and Aleph keys, peers, and advancing finalized height.
The exact executable helper commands are in docs/runbooks/devnet-add-validator.md from the repository. That path is part of the devnet operator checkout; access to private images and network inputs still requires the network operator. The helper runs from sdk/miner-worker after pnpm install --frozen-lockfile at repo root. Never put a private account or root seed into an issue, chat, command log, or shared shell history.
For a disposable single-admin rehearsal only, the repository runbook's command order is:
FLOP · Run a FLOP validator › From the repository root, install the helper's dependencies once.
# From the repository root, install the helper's dependencies once.
pnpm install --frozen-lockfile
FLOP · Run a FLOP validator › In sdk/miner-worker, set OPERATOR_SEED from a private 32-byte seed,
# In sdk/miner-worker, set OPERATOR_SEED from a private 32-byte seed,
FLOP · Run a FLOP validator › SUDO_SEED from the disposable devnet root, and WS_URL=ws://127.0.0.1:9944.
# SUDO_SEED from the disposable devnet root, and WS_URL=ws://127.0.0.1:9944.
cd sdk/miner-worker
DEVNET_ACTION=fund node --input-type=module < ../../infra/devnet/operator.mjs
FLOP · Run a FLOP validator › On the joining host only, briefly enable local key-generation RPC. The EXIT trap
# On the joining host only, briefly enable local key-generation RPC. The EXIT trap
FLOP · Run a FLOP validator › restores safe methods and checks the process even if join fails.
# restores safe methods and checks the process even if join fails.
(
set -e
restore_safe_rpc() {
(cd ../../infra/devnet && RPC_METHODS=safe docker compose up -d --no-deps --force-recreate validator) || return 1
(cd ../../infra/devnet && docker compose exec -T validator sh -c "tr '\000' ' ' </proc/1/cmdline") \
| grep -q -- '--rpc-methods safe'
}
trap 'join_status=$?; trap - EXIT; restore_safe_rpc || { echo "Safe RPC restoration failed; isolate port 9944 and repair the validator before continuing" >&2; exit 1; }; exit "$join_status"' EXIT
(cd ../../infra/devnet && RPC_METHODS=unsafe docker compose up -d --no-deps validator)
DEVNET_ACTION=join node --input-type=module < ../../infra/devnet/operator.mjs
)
FLOP · Run a FLOP validator › Stop here if the subshell failed; resolve join or safe-RPC restoration first.
# Stop here if the subshell failed; resolve join or safe-RPC restoration first.
DEVNET_ACTION=fund node --input-type=module < ../../infra/devnet/operator.mjs
DEVNET_ACTION=promote node --input-type=module < ../../infra/devnet/operator.mjs
DEVNET_ACTION=verify node --input-type=module < ../../infra/devnet/operator.mjs
Only the trusted devnet root can run fund and promote. The helper checks the root key and that the joining account is the next queued candidate. Do not copy this root seed arrangement into a public operator deployment. Separate seedless administrator actions are tracked in issue #2002; the public release inputs are tracked in issue #2003.
FLOP session keys are babe (sr25519 block authoring), audi (sr25519 authority discovery), and alp0 (ed25519 AlephBFT). Aleph Zero's operator guides use an Aura key and a separate validator port; those are not FLOP settings. The FLOP Aleph transport uses the node's libp2p peer port.
Verify and operate
The helper's verify action waits up to two BABE session boundaries after promotion. It confirms session.validators, the registered BABE authority key, and the matching Aleph runtime authority key. aleph.currentCommittee alone is an account snapshot, not proof that a finality key is live. Read local health and finality twice:
curl -fsS -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"system_health","params":[]}' \
http://127.0.0.1:9944 | jq .result
curl -fsS -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"chain_getFinalizedHead","params":[]}' \
http://127.0.0.1:9944 | jq -r .result
(cd ../../infra/devnet && docker compose logs --tail=100 validator da-validator)
The log command above assumes the shell is still in sdk/miner-worker after the join block. If starting a fresh shell at the repository root, use (cd infra/devnet && docker compose logs --tail=100 validator da-validator).
Healthy peers, no ongoing sync, and a finalized-head change over a short interval show the node is connected and the network is finalizing. The helper checks account membership and key activation. Monitor free disk space, DA availability, peer count, best and finalized heights, and logs; keep metrics private.
Back up the account seed, node keystore and persistent volume, libp2p node key, and DA worker data with controlled access. Test restoration offline. A chain database snapshot alone does not restore signing or DA custody. Pin images during upgrades, move one validator at a time, and re-check peers and finality after each restart. A standby must stay keyless until the original has stopped and cannot sign. Follow the network's published exit and unbonding policy before retiring an active validator.
| Symptom | Check |
|---|---|
| No peers | Reachable PUBLIC_ADDR, BOOTNODES, P2P firewall, matching chain and genesis |
| Registered but idle | Promotion, two session boundaries, babe/audi/alp0 keys on the local node |
| Best height rises, finality does not | Committee key activation and peers on independent nodes; shared provider outage |
| DA worker fails | Private seed, connection to its local node, advertised DA endpoint, persistent volume |
| Join RPC fails | Local unsafe-method window for key generation only, then safe mode restored |
For the role's stake and committee rules, use the repository's docs/onboarding/validator.md.