Skip to main content

High availability

An Edge scales horizontally. Every replica serves the full local API, and exactly one may deliver webhooks — two would double-deliver every event.

Why replicas need no coordination

The Edge identity key is derived from the mnemonic, at m/83696968'/0'/0', rather than enrolled. Ten containers started from the same mnemonic all compute the same identity, so they present to Cloud as one Edge with no registration handshake and no shared state between them.

That is the whole reason horizontal scaling is easy here. Nothing has to be distributed because nothing is negotiated.

The leader lease

Cloud grants the lease, not the Edge fleet. A partial unique index is the election:

CREATE UNIQUE INDEX idx_one_leader ON edge_sessions(account_id, environment)
WHERE is_leader;

The database decides. No consensus protocol, no external coordinator, no fourth container.

Both replicas ask Cloud for the leader lease. One is granted an epoch and opens the stream and delivers webhooks; the other is refused with 409 LEASE_HELD and stays a follower serving the local API.

Leaders and followers are identical from your application's point of view. Both serve the full API on 8787 and both sign. The difference is only which one holds the stream to Cloud and therefore relays webhooks and answers pool requests for Solana and Stellar.

Epoch fencing

Each grant carries a monotonically increasing epoch, and Cloud rejects any frame presenting a stale one with 409 LEASE_EPOCH_STALE, closing the stream.

This is the part that matters under a network partition. A replica cut off from Cloud may still believe it holds the lease — it has no way to know otherwise — but it cannot act, because Cloud has already issued a higher epoch to whoever took over. The stale leader's frames are refused before they are read.

Without the epoch, a partition that healed would produce two replicas both convinced they were the leader, both delivering, and every webhook arriving twice.

Failover

Graceful shutdown releases the lease, so a planned restart fails over in under a second rather than waiting for the TTL. An unplanned loss — a killed pod, a partition — waits for EDGE_LEASE_TTL_SECONDS (45 by default, which is one missed heartbeat of slack) before another replica can acquire.

How many replicas

ReplicasWhat you get
1Correct. Webhooks queue in the outbox during a restart and drain after
2Continuous delivery through a rolling restart
3+Headroom, and safe with EDGE_OUTBOX_MODE=disabled

Two is the usual answer. Go to three if you have disabled the outbox, because then a rolling restart with two replicas can briefly leave zero leaders and there is nowhere for the undelivered writes to go.

EDGE_LEADER_ELIGIBLE=false pins a replica as follower-only — useful for a replica in a different region that you want serving reads without becoming the delivery path.

Configuration for a fleet

spec:
replicas: 2
template:
spec:
containers:
- name: edge
env:
- name: PASS_PHRASE_FILE
value: /run/secrets/chainos/mnemonic # the same mnemonic on every replica
- name: EDGE_POD_HINT
valueFrom:
fieldRef: { fieldPath: metadata.name } # display only, never identity
- name: EDGE_LEASE_TTL_SECONDS
value: "45"
terminationGracePeriodSeconds: 20 # room to release the lease
The same mnemonic on every replica

Every replica must mount the same mnemonic. A replica with a different one derives a different identity, and Cloud treats that as an identity rotation: it quarantines the keys and pages your account owner. That is the correct behaviour and it will happen the moment a misconfigured replica starts.

Each replica still needs its own persistent data directory — a volumeClaimTemplate on a StatefulSet, not a shared volume. Two processes writing one bbolt file corrupt it.

Watching for split-brain

Settings → Edge sessions in the console lists every active session with its role, version, liveness, lease epoch, outbox mode, last heartbeat and pod hint. Ended sessions are reaped after an hour, so the list reflects the running fleet rather than an accumulating history.

The lease epoch is the column to look at. Every per-session health indicator will be green during a split-brain — each replica genuinely is healthy — and the epoch is the only thing that shows two replicas disagreeing about who leads. It is surfaced for exactly this reason.

curl -s $CLOUD/v1/edge/sessions -H "Authorization: Bearer $TOKEN" \
| jq '.data[] | { podHint, role, leaseEpoch, liveness, outboxDepth }'

Expect one leader and the rest follower, all reporting the same leaseEpoch. Two sessions claiming leader, or a follower reporting an epoch above the leader's, is a genuine incident — see Incident response.

What a fleet does not fix

  • Signing still needs at least one live replica. Zero live replicas means 423 EDGE_OFFLINE on anything requiring a signature.
  • Pool replenishment is leader-only. Followers do not derive pool addresses, so a fleet with no leader and an exhausted pool cannot issue Solana or Stellar addresses.
  • Deposits are unaffected either way. Monitoring, confirmation tracking and webhook generation are entirely Cloud-side. A completely offline Edge means webhooks arrive late, never that they are lost.
  • A shared mnemonic is a shared blast radius. Replicas do not compartmentalise key material; they multiply the number of processes holding it. That is the trade for the no-coordination property, and it is why the container hardening in Installation matters more as you add replicas.