8.6 KiB
MPC Operator Runbook (Outline)
Day-to-day operator reference for a mpcd consensus-mode cluster.
This document is intentionally an outline. Each section lists the
shape of the procedure and the commands an operator runs; protocol-level
detail (exact reshare sequence, slashing semantics, recovery state
machine) is owned by the maintainers and is marked TODO(maintainer)
where it has not been filled in yet. PRs welcome.
Scope. Consensus-mode (
mpcd) only. Legacylux-mpcclusters are covered inINSTALLATION.mdAppendix A and are not in scope here.
0. Cluster topology at a glance
nnodes (StatefulSet pods / VMs), each runningmpcd start ...- Quorum:
ready_count >= threshold + 1for signing; all peers must be ready for keygen of fresh wallets - Default ports per node:
9999— P2P ZAP transport (peer ↔ peer)9800— internal MPC API (bearer-auth, K8s-internal only)8081— dashboard API (JWT-auth, may be ingressed)9653— KMS-facing ZAP (consumed byluxfi/kms)7301— embedded threshold dispatcher (loopback by default)
- Persistent state:
--datadirectory (key shares in ZapDB, dashboard in SQLite, backups underbackups/)
1. Bring-up checklist
Run through this when standing up a new cluster or after a planned full restart.
- All node identities (
<data>/keys/<node-id>_identity.json) backed up off-host before first start. - HSM password provider reachable (
aws kms decryptor equivalent smoke test from the host network). MPC_INTERNAL_API_KEYset on every node and identical across the cluster.JWT_SECRETset for the dashboard API.mpcd startsucceeds on each node — logs show[READY] Node is ready (consensus mode)./healthzreturnssigning_quorum: trueon every node.- Smoke test passes (see §8).
2. Health endpoints and probes
mpcd exposes two health surfaces. Both are unauthenticated so that
Kubernetes probes work without secret injection.
GET /healthz(port 9800) — node-level. Returns:200 status=healthy— signing quorum holds and every peer is ready200 status=healthy-reduced— signing quorum holds, ≥1 non-critical peer unreachable503 status=degraded— signing quorum lost
GET /health(legacy alias of/healthz).
Recommended Kubernetes probes:
- Liveness:
/healthz, period30s, failure threshold5. Restart only on prolonged failure — transient quorum gaps are recoverable. - Readiness:
/healthz, period10s, failure threshold2. A 503 removes the pod from the dashboard service endpoints.
TODO(maintainer): confirm whether a dedicated readiness path is
preferred over reusing /healthz for very large (n>7) clusters.
3. Key rotation (reshare)
The reshare flow rotates per-share material without changing the wallet's public key. Use it on a regular cadence and on personnel change.
- Stage the new participant set and threshold.
- Confirm the cluster is at full quorum (
/healthzhealthy on every node) before starting — reshare requires all current participants. - Trigger reshare via the dashboard API or by publishing to the
mpc:resharetopic (ConsensusMPCBackend.TriggerReshareincmd/mpcd/main.go). - Verify the new key info on every node (
GET /keyson port 9800). - Retire old share material from the previous participant set once every node has acknowledged the new shares.
TODO(maintainer): document the exact wire shape of the reshare event
(org_id, wallet_id, new_threshold, new_participants[]) and the expected
result topic.
4. Backup and restore
mpcd runs a periodic ZapDB backup driven by backup_period_seconds
(default 300 s). Backups land in <data>/backups/ and, when the
MPC_BACKUP_S3_* env vars are set, are uploaded to S3 by
pkg/backup.Manager.
Backup
- Verify the periodic backup is running (
grep "Periodic ZapDB backup completed" mpcd.logon each node). - On demand:
curl -X POST -H "Authorization: Bearer ${MPC_INTERNAL_API_KEY}" http://<node>:9800/backup.
Restore (single node)
- Stop
mpcdon the affected node. - Restore the latest
<data>/backups/<node-id>.zapdb.bakfile into<data>/db/<node-id>/. - Restore
<data>/keys/<node-id>_identity.jsonfrom off-host backup (the identity is not in the ZapDB backup). - Restart
mpcd. The node rejoins, repopulates the peer registry, and/healthzflips back tohealthy.
TODO(maintainer): describe the restore-from-S3 flow and the
verification step that a restored share is byte-identical to peers'
view of the same wallet.
5. Add / remove a node without resharing
This procedure changes cluster size without rotating shares. It is appropriate for scaling capacity, not for rotating key material — for that, use §3 (reshare).
Add
- Provision the new pod / VM with a clean
--datavolume. - Generate the new node's identity (first start creates it under
<data>/keys/). - Update the
--peerflags on every existing node to include the new address. - Rolling-restart the existing nodes. Quorum is preserved as long as
ready_count >= threshold + 1throughout the rollout. - The new node will not hold shares for existing wallets until a reshare is triggered — see §3.
Remove
- Confirm the cluster can tolerate
n - 1(i.e.n - 1 >= threshold + 1). - Drain and stop the node.
- Remove the node's address from
--peeron remaining nodes; rolling-restart. TODO(maintainer): confirm whether the removed node's share material should be cryptographically retired (re-shared away) or whether physical destruction is sufficient given the share is single-party-useless.
6. Recovery from share loss
If a single node loses its --data volume but the cluster still has
t-of-n quorum.
- Confirm
n - 1 >= threshold— the cluster can still sign. TODO(maintainer): document whetherpkg/mpc/recovery.goexposes an operator-facing restore-from-quorum flow, or whether the only path is reshare into a fresh share for the affected node.- After recovery, run the smoke test (§8) before returning the node to production traffic.
If the cluster has lost more than n - t nodes (below signing
quorum), restore from off-host backups (§4) is the only option.
7. Slashing / misbehaviour response
TODO(maintainer): the cluster currently treats peer misbehaviour as a
liveness signal (ArePeersReady flips false, /healthz may downgrade).
Operator response should cover:
- Identifying the offending peer from
mpcdlogs (signature verification failure, repeated wire-protocol violations). - Quarantining the peer (remove from
--peerset, rolling-restart). - Forensic capture of the peer's
<data>/and recent log window. - Re-keying via reshare (§3) once root cause is established.
Document the threshold above which misbehaviour requires share rotation vs. simple peer removal.
8. Deployment-validation smoke test
Run after every cluster start, every K8s rolling update, and as a post-deploy gate in CI.
for n in node0 node1 node2; do curl -fs http://${n}:9800/healthz | jq -e '.signing_quorum == true'; done- Keygen a throwaway wallet via the dashboard API or
/keygenon port 9800. - Sign a fixed nonce against that wallet.
- Verify the signature off-cluster (e.g.
secp256k1/ed25519verify with the returned public key). - Tear down the throwaway wallet.
TODO(maintainer): a packaged scripts/smoke-test.sh that wraps the
above into a single exit-coded command would be a useful follow-up;
issue #1 proposed it explicitly.
9. Log locations and verbosity
| Location | Contents |
|---|---|
| stderr (default) | Structured logs from pkg/logger. Capture via the systemd journal or the K8s pod log stream. |
<data>/backups/ |
Periodic ZapDB backups. Not log data — but useful in incident timelines. |
<data>/dashboard.db |
Dashboard SQLite (audit/admin trail). |
Increase verbosity with --log-level debug or --debug. Do not run
production with debug logging — wire messages and identities are not
logged, but timing data leaks downstream operator information.
Key audit log lines to grep for:
Audit: keygen triggered— every/keygeninvocationAudit: backup triggered— every manual backup[READY] Node is ready— successful startPeriodic ZapDB backup completed successfully— backup heartbeatShutdown signal received— graceful stop