Skip to content

Run a node

The JunoClaw node binary is slay3rd. It takes one argument: the path to a node.toml.

The chain source is on the commonware branch of Dragonmonk111/layer-sdk.

Terminal window
git clone -b commonware https://github.com/Dragonmonk111/layer-sdk.git junoclaw-chain
cd junoclaw-chain
git checkout 90451d8 # the commit the devnet runs (2026-10-06)
# Option A — native build (Linux x86_64; same steps as the Dockerfile)
sudo apt install -y clang g++ libclang-dev cmake
cc -c -o /tmp/shim.o docker/shim.c # wasmer still links __rust_probestack
RUSTFLAGS="-C link-arg=/tmp/shim.o" \
cargo build --release -p slay3rd --features rocksdb # → target/release/slay3rd
# Option B — Docker image (what the devnet runs)
docker build -f docker/Dockerfile.slay3rd -t junoclaw-chain:latest .
  • Build with Rust 1.97 or newer (the image uses rust:1.97-bookworm).
  • --features rocksdb gives the persistent store under data_dir. Without it, app state is not persisted across restarts.
  • On non-Unix hosts the binary verifies MAYO signatures but cannot produce them, so it cannot vote in hybrid mode.
  • Builds from before 2026-10-06 are incompatible. They stamp blocks with view-derived time, while the current set uses wall-clock time, so each side rejects the other’s proposals. Rebuild before rejoining.

For G2, accepted operators get a pinned commit and image digest so that every validator runs identical code.

Your keys.json holds:

  • BLS12-381 threshold share. Signs consensus votes
  • MAYO2 key (hybrid mode). The post-quantum half of every vote
  • Ed25519 identity. Authenticates your P2P connections

On devnet the coordinator generates keys centrally. Those shares come from a public seed, so the node only accepts them with insecure_devnet = true (see below). For G2, keys come out of the key ceremony: you generate your Ed25519 and MAYO2 keys locally, and the coordinator deals your BLS share from OS randomness and sends it to you encrypted (see Validators). Back up keys.json offline once. If you lose it, you lose your seat until the set is changed.

This mirrors the running devnet config:

validator_index = 0 # assigned at genesis; must match your key share
chain_id = "junoclaw-1" # devnet value — G2 chain id is issued with genesis
p2p_listen = "0.0.0.0:7001"
grpc_listen = "0.0.0.0:9090"
bls_key_path = "/keys/keys.json"
identity_key_path = "/keys/keys.json"
data_dir = "/data"
genesis_path = "/config/genesis.json" # from the genesis bundle; identical on every validator
mempool_max_pending = 10000
leader_timeout_ms = 3000
certification_timeout_ms = 5000
hybrid_consensus = true # BLS + MAYO2 certificates
# insecure_devnet = true # devnet only, see below
# Optional: bootstrap from a certified snapshot instead of replaying from genesis.
# The anchor must be served identically by >= min_anchor_agree peers.
[state_sync]
peers = ["<peer-a>:9090", "<peer-b>:9090"]
min_anchor_agree = 2
# max_snapshot_bytes = 4294967296 # download cap (default 4 GiB)
# One entry per *other* validator
[[peers]]
address = "<host>:7001"
public_key = "<ed25519-pubkey-hex>"

Rules

  • validator_index must match the index your key share was generated for.
  • chain_id, the peer set and the binary version must be identical across the set. Mixed versions do not interoperate on P2P, so upgrades are all-or-none.
  • [[peers]] lists the other validators.
  • Clock. Your proposals carry your wall-clock time, and validators reject a block stamped more than 2 s ahead of their own clock. Run NTP (chrony or systemd-timesyncd).

insecure_devnet is for the devnet only. With it, the node accepts an empty genesis_path (the built-in devnet genesis), coordinator-dealt keys, and genesis files that fund or empower a built-in devnet address. Without it, the node refuses all three at startup. Never set it on G2.

Terminal window
# Native
slay3rd /etc/junoclaw/node.toml
# Docker
docker run -d --name junoclaw-node --restart unless-stopped \
-p 7001:7001 -p 127.0.0.1:9090:9090 \
-v /etc/junoclaw/node.toml:/config/node.toml:ro \
-v /etc/junoclaw/genesis.json:/config/genesis.json:ro \
-v /etc/junoclaw/keys:/keys:ro \
-v junoclaw_data:/data \
-e RUST_LOG=info \
junoclaw-chain:latest slay3rd /config/node.toml

Signs of a healthy node in the logs:

Hybrid BLS+MAYO2 consensus scheme initialized
Authenticated P2P network initialized (N peers)
Block finalized with BLS threshold certificate (CONS-05) height=…
Block finalized — certificate stored … app_hash=… digest=…
Finality record stored (certificate, proposal, timestamp) height=…
  • Heights should rise steadily — about one finalized block every ~3.5 s on the multi-operator network (0.11 s in the best sustained hour of the G1 soak).
  • app_hash should be identical across all validators at the same height. It equals the certified state_root.

Node up but not signing? Check that:

  1. validator_index matches your key share.
  2. Every peer is reachable on 7001, and their public keys are correct.
  3. chain_id and the binary version match the set.
  4. The clock is synced over NTP. If your clock is skewed by more than 2 s, peers refuse your proposals or you refuse theirs.
  5. If you just restarted after downtime: while the node is behind it logs verify: rejecting proposal / payload unavailable. That is expected. It backfills and then rejoins.
  • Pruning. pruning = "validator" (default) keeps the last 540,000 heights of sidecar data and finality records, about 24 hours at devnet speed. "rpc" keeps 16,200,000 heights (about 30 days) and "archive" keeps everything. Peers can backfill your node across that whole window. Beyond it, use state sync.
  • MAYO signature log. data_dir/mayo_sigs.log records the MAYO signature for every vote you cast. After a restart the node re-sends those exact signatures. A freshly signed vote would differ byte-for-byte, and peers treat that as a conflicting message. Keep the log with the data dir and never delete it while you are in the set.
  • Restarts. Nodes resume from persisted state. A node that falls far behind catches up with height-range backfill, or with certified state-sync if its data dir is empty.
  • Upgrades. Coordinated: everyone switches binary/config at an agreed height.
  • Incidents. Report in Telegram straight away: the height, the last log lines, and what changed.