Skip to main content

Run a Validator Node

Everything on this page happens before the Orbinum team hears from you. By the end your node is synced and your session keys are registered on-chain — which is exactly the state addValidator requires.

Prerequisites

A server meeting the validator requirements and Docker with the Compose plugin — see Installation. No registry token: the image is public.


1. Clone node-deploy and configure​

The Compose file and the chain spec ship together:

git clone https://github.com/orbinum/node-deploy.git
cd node-deploy/testnet/validator
cp .env.example .env

Every variable is documented in the file. These are the ones that matter:

VariableValueWhy
VALIDATOR_NAMEanything identifiableShown on telemetry
VALIDATOR_NODE_KEYopenssl rand -hex 32Your stable libp2p identity
TELEMETRY_URLleave the defaultLevel 1 publishes your validator address to the dashboard
METRICS_BINDyour private IP, or leave 127.0.0.1The default fails closed and exposes nothing
PUBLIC_ADDRleave emptyThe node auto-detects its public address
RESERVED_NODESleave emptyNot additive — a partial list isolates the node. See Peering
SYNC_MODEleave empty unless bootstrappingFull sync is the safe default — see the tip below
You do not need bootnodes

testnet-spec.json already carries them, and the Compose file already mounts it. There is nothing to fetch, paste, or add yourself to.

Impatient on a fresh server?

SYNC_MODE=--sync warp skips replaying every block, but only on a brand new volume, and the node keeps no block bodies before the sync point — see the caveats before setting it.


2. Open the firewall​

sudo ufw allow 30333/tcp   # P2P — must be reachable from the internet
sudo ufw allow 22/tcp # SSH
sudo ufw enable

No rule for 9944, and none for 9615 unless your monitoring host is on a private network that needs it.


3. Start the node​

docker compose pull
docker compose up -d
docker compose logs -f orbinum-validator

The Compose file already passes --validator, --no-mdns, the chain spec and the telemetry flags — there is nothing to add on the command line. The first lines of the log are the hardware benchmark, which must stay on.

Watch the logs for the node's own peer identity at startup, then a stream of import messages while it catches up. Wait until it reports idling at the chain head before continuing; inserting keys into a syncing node works, but you cannot tell a healthy node from a stalled one until it has caught up.

Editing .env later needs a recreate, not a restart — see Updates:

docker compose up -d --force-recreate orbinum-validator

4. Confirm it is synced​

docker exec orbinum-validator curl -s -H 'Content-Type: application/json' \
-d '{"id":1,"jsonrpc":"2.0","method":"system_health"}' \
http://localhost:9944
{ "jsonrpc": "2.0", "result": { "peers": 4, "isSyncing": false, "shouldHavePeers": true }, "id": 1 }

You want isSyncing: false and peers of at least 2. peers: 0 means the node cannot reach the bootnodes — check that port 30333 is genuinely reachable from outside, and that RESERVED_NODES is empty.

Note the docker exec: it runs the request inside the container, where the RPC port is always 9944. Compose does publish that port, but only on loopback (RPC_BIND=127.0.0.1) and under whatever RPC_PORT you set — so if you changed RPC_PORT to, say, 26722, then from the host it is curl http://localhost:26722/ while inside the container it stays 9944. Using docker exec sidesteps the difference, which is why every command on this page is written that way. The port is never reachable from the internet, and must not be: the binary forces --rpc-methods Unsafe on every role — see --rpc-methods does nothing and Ports.


5. Generate session keys​

The node generates the keys, and with them the proof of possession the chain demands in the next step. Both are bound to your validator account, so the node needs that account's public key: a 0x string of 64 hex characters, the 32 raw bytes behind the SS58 address.

docker exec orbinum-validator orbinum-node key inspect <your SS58 address>

Copy the Public key (hex) line and pass it as the owner:

docker exec orbinum-validator curl -s -H 'Content-Type: application/json' \
-d '{"id":1,"jsonrpc":"2.0","method":"author_rotateKeysWithOwner","params":["<your account hex>"]}' \
http://localhost:9944

The result has two fields:

{"jsonrpc":"2.0","id":1,"result":{"keys":"0x…","proof":"0x…"}}
  • keys — 128 hex characters, 64 bytes: your Aura sr25519 public key followed by your GRANDPA ed25519 public key, in the order the runtime declares them.
  • proof — 256 hex characters, 128 bytes: one signature per key over your account id. It is how the chain checks that the node registering the keys actually holds their private halves.

The private halves were written into the node's keystore at /data/chains/orbinum_testnet/keystore and never left the server. Only keys and proof go on-chain.

Run this once, on a synced node

If the response has no proof field, your node is still syncing and executing a runtime older than the one live on the chain. Wait until system_health reports "isSyncing": false and run it again.

Calling author_rotateKeysWithOwner again generates a new pair and both values you saved become stale. If you rotate after registering, you must submit session.setKeys again with the new pair or your node stops authoring at the next session.

Copy both values. You need them in the next step.


6. Submit session.setKeys​

This is an on-chain extrinsic signed by your validator account — the account you will send to the Orbinum team, and the same one you passed as owner in step 5. It needs a balance to pay its fee: fund the account from the faucet first.

Your account is not your Aura key

On Orbinum, ValidatorId is the AccountId directly. Your validator account is an ordinary account and needs no relationship to your Aura key.

You may have read that a validator's account is its sr25519 key — that holds only for the genesis validators, whose accounts were derived from their Aura keys when the chain was built. A validator added later registers session keys under whatever account it already controls.

  1. Open Polkadot.js Apps and connect it to wss://rpc-1.testnet.orbinum.io.
  2. Go to Developer → Extrinsics.
  3. Select your validator account, then session → setKeys(keys, proof).
  4. Paste keys from step 5 into keys.
  5. Paste proof from step 5 into proof.
  6. Submit and sign.

The runtime verifies proof on-chain: each session key must have signed the id of the account submitting the extrinsic. The call is rejected with Session.InvalidProof when

  • proof is 0x00 or empty. Runtimes before spec_version 11 ignored it; the current one does not.
  • the account signing setKeys is not the owner you passed to author_rotateKeysWithOwner.
  • you rotated again after copying, so keys and proof no longer match.

In every case the fix is the same: redo step 5 with the right account hex and resubmit with the new keys and proof.

Verify it landed. In Developer → Chain state, query session.nextKeys(yourAccount). It must return the same keys you submitted. If it returns nothing, the extrinsic did not finalize and the next step will fail.

This is the gate

addValidator is rejected with NoSessionKeys unless this extrinsic has already finalized. Do not email the team before session.nextKeys returns your keys.


7. Confirm the keystore matches the chain​

session.nextKeys proves the chain knows your keys. It does not prove your node holds the private halves — the two come apart if you ran author_rotateKeysWithOwner on a different machine, or recreated the container without its volume. Ask the node itself:

docker exec orbinum-validator curl -s -H 'Content-Type: application/json' \
-d '{"id":1,"jsonrpc":"2.0","method":"author_hasSessionKeys","params":["<your keys>"]}' \
http://localhost:9944

Pass keys from step 5 exactly as author_rotateKeysWithOwner returned it — one 0x string, Aura and GRANDPA concatenated, no separator.

{ "jsonrpc": "2.0", "result": true, "id": 1 }

true means this node can sign for the keys registered on-chain, and you are done. false means it cannot: the keystore is missing them, and the node will never author a block no matter how long you wait or how many sessions pass. Go back to step 5 and rotate on this node, then submit setKeys again with the new keys and proof.

false is silent

Nothing in the logs, on telemetry, or in validatorSet.approvedValidators reveals this mismatch. The node syncs, peers, and looks healthy — it simply skips every slot it is scheduled for. Checking here costs one command; skipping it costs two session rotations before the symptom appears.