247 lines
12 KiB
YAML
247 lines
12 KiB
YAML
# luxd StatefulSet — lux-mainnet (5 validators, C-Chain EVM 96369).
|
|
#
|
|
# This is the object the kubelet actually reads. lux-mainnet declares the luxd
|
|
# image in THREE places and only this one moves a pod:
|
|
# 1. StatefulSet/luxd — this file. No ownerReferences, so nothing
|
|
# reconciles it; a plain `kubectl apply` is the only
|
|
# writer and git is the only durable source.
|
|
# 2. LuxNetwork lux.network/v1alpha1/luxd — network.yaml.
|
|
# 3. LuxNetwork lux.cloud/v1/luxd — live object, no file in this repo.
|
|
# Both operators (lux-operator, lux-operator-devnet) sit at 0/0 and MUST STAY
|
|
# THERE, so neither CR moves anything. All three read v1.36.2 today.
|
|
#
|
|
# Committed after the 2026-07-28 v1.36.35 roll attempt, which changed this
|
|
# object in the cluster and reverted it, with no file to record either edit —
|
|
# the sister manifest ../lux-testnet/luxd-statefulset.yaml had been tracked
|
|
# since 2026-07-26 and mainnet, the higher-stakes namespace, had not. Captured
|
|
# verbatim from the live object; `kubectl diff -f` is clean, checked against a
|
|
# mutated copy as the negative control.
|
|
#
|
|
# updateStrategy is OnDelete: changing spec.template here does NOT roll the
|
|
# fleet. Pods adopt a new image only when deleted individually, which is the
|
|
# intended safety property on mainnet — see network.yaml for the two blockers
|
|
# that currently make deleting ANY luxd pod in this namespace destructive.
|
|
#
|
|
# persistentVolumeClaimRetentionPolicy is Retain/Retain: deleting or scaling
|
|
# this StatefulSet never removes a data-luxd-N PVC.
|
|
apiVersion: apps/v1
|
|
kind: StatefulSet
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/instance: luxd
|
|
app.kubernetes.io/managed-by: lux-operator
|
|
app.kubernetes.io/name: luxd
|
|
name: luxd
|
|
namespace: lux-mainnet
|
|
spec:
|
|
persistentVolumeClaimRetentionPolicy:
|
|
whenDeleted: Retain
|
|
whenScaled: Retain
|
|
podManagementPolicy: Parallel
|
|
replicas: 5
|
|
revisionHistoryLimit: 10
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/instance: luxd
|
|
app.kubernetes.io/managed-by: lux-operator
|
|
app.kubernetes.io/name: luxd
|
|
serviceName: luxd-headless
|
|
template:
|
|
metadata:
|
|
annotations:
|
|
forceRestart: '1780781328'
|
|
kubectl.kubernetes.io/restartedAt: '2026-06-06T14:28:28-07:00'
|
|
labels:
|
|
app: luxd
|
|
app.kubernetes.io/instance: luxd
|
|
app.kubernetes.io/managed-by: lux-operator
|
|
app.kubernetes.io/name: luxd
|
|
spec:
|
|
affinity:
|
|
podAntiAffinity:
|
|
requiredDuringSchedulingIgnoredDuringExecution:
|
|
- labelSelector:
|
|
matchExpressions:
|
|
- key: app
|
|
operator: In
|
|
values:
|
|
- luxd
|
|
topologyKey: kubernetes.io/hostname
|
|
containers:
|
|
- command:
|
|
- /bin/sh
|
|
- /scripts/startup.sh
|
|
env:
|
|
- name: GOMEMLIMIT
|
|
value: 6GiB
|
|
- name: ZAP_RPC_LISTEN
|
|
value: :9632
|
|
- name: LUX_NETWORK_ID
|
|
value: '1'
|
|
- name: POD_NAME
|
|
valueFrom:
|
|
fieldRef:
|
|
apiVersion: v1
|
|
fieldPath: metadata.name
|
|
- name: LUX_CONSENSUS_VIEW_CHANGE
|
|
value: 'true'
|
|
image: ghcr.io/luxfi/node:v1.36.125@sha256:f65281b532a6b2294b83f57610cad2d292a12cf5c85232986bd0b54979d76862
|
|
imagePullPolicy: Always
|
|
livenessProbe:
|
|
failureThreshold: 10
|
|
httpGet:
|
|
path: /v1/health/liveness
|
|
port: 9630
|
|
scheme: HTTP
|
|
initialDelaySeconds: 300
|
|
periodSeconds: 15
|
|
successThreshold: 1
|
|
timeoutSeconds: 5
|
|
name: luxd
|
|
ports:
|
|
- containerPort: 9632
|
|
name: zap-rpc
|
|
protocol: TCP
|
|
- containerPort: 9630
|
|
name: http
|
|
protocol: TCP
|
|
- containerPort: 9631
|
|
name: staking
|
|
protocol: TCP
|
|
- containerPort: 9090
|
|
name: metrics
|
|
protocol: TCP
|
|
readinessProbe:
|
|
failureThreshold: 3
|
|
httpGet:
|
|
path: /v1/health/liveness
|
|
port: 9630
|
|
scheme: HTTP
|
|
periodSeconds: 10
|
|
successThreshold: 1
|
|
timeoutSeconds: 5
|
|
resources:
|
|
limits:
|
|
cpu: '4'
|
|
memory: 12Gi
|
|
requests:
|
|
cpu: 100m
|
|
memory: 7Gi
|
|
startupProbe:
|
|
failureThreshold: 30
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 10
|
|
successThreshold: 1
|
|
tcpSocket:
|
|
port: 9630
|
|
timeoutSeconds: 1
|
|
terminationMessagePath: /dev/termination-log
|
|
terminationMessagePolicy: File
|
|
volumeMounts:
|
|
- mountPath: /data
|
|
name: data
|
|
- mountPath: /scripts
|
|
name: startup-script
|
|
- mountPath: /staking-keys
|
|
name: staking-keys
|
|
readOnly: true
|
|
dnsPolicy: ClusterFirst
|
|
initContainers:
|
|
- args:
|
|
- "set -eu\n# \u2500\u2500 Lux MAINNET C-Chain RLP recovery init \u2500\u2500\n# NO CHAIN ID IS NAMED HERE. The previous version hardcoded\n# CBID=<a now-dead chain ID> and ran `rm -rf /data/chainData/$NET/$CBID`. That ID\n# stopped existing at the re-genesis (the live C-Chain is\n# 2T11SNJM9KYqSenoPb5yv6qahmj8HLefCjxt4gTD2iQWTHwdRu), so the wipe was a SILENT\n# NO-OP \u2014 the same drift that made `.wipe-cchain` useless and forced the\n# snapshot-clones that corrupted luxd-0 and luxd-2.\n#\n# There is now ONE wipe implementation, in the startup script, keyed off\n# `.wipe-state`. This container no longer wipes anything itself: it verifies the\n# artifact and drops the markers. An RLP import replays from genesis and rebuilds\n# every index, so discarding ALL chain state is both correct and simpler than\n# targeting one directory \u2014 and it cannot drift.\n#\n# Order: [arm-gate] -> [sentinel-skip] -> [SHA-verify] -> [markers].\n# SHA-verify MUST precede the wipe request; a mismatch aborts before anything\n# destructive is scheduled.\nDB=/data/db\nRLP=/data/lux-mainnet-96369.rlp\nEXPECTED_SHA=dd6b71f510fca48080c383bf44fccbfda4849e2cde06710ffe4a24fa1d9bec79\nH=dd6b71f510fc\nARM=\"$DB/.rlp-import-arm\"\nSENTINEL=\"$DB/.tenant-import-c-chain-$H\"\nMARKER=\"$DB/.tenant-import-request-c-chain-$H.json\"\nif [ ! -f \"$ARM\" ]; then\n echo \"[rlp-import-init] node NOT armed (no $ARM); skipping\"\n exit 0\nfi\nif [ -f \"$SENTINEL\" ]; then\n echo \"[rlp-import-init] sentinel present; already imported; clearing arm\"\n rm -f \"$ARM\"\n exit 0\nfi\nif [ ! -f \"$RLP\" ]; then\n echo \"[rlp-import-init] ERROR: RLP not found at $RLP\" >&2\n exit 1\nfi\nGOT=$(sha256sum \"$RLP\" | awk '{print $1}')\nif [ \"$GOT\" != \"$EXPECTED_SHA\" ]; then\n echo \"[rlp-import-init] SHA MISMATCH want=$EXPECTED_SHA got=$GOT -- ABORT, nothing wiped\" >&2\n exit 1\nfi\necho \"[rlp-import-init] SHA OK ($GOT)\"\n# Request the canonical full-state wipe. The startup script consumes .wipe-state\n# and clears /data/chainData + every /data/db DATABASE, while preserving\n# /data/staking (identity) and every FILE at /data/db root (this RLP included).\ntouch \"$DB/.wipe-state\"\necho \"[rlp-import-init] requested canonical wipe via .wipe-state (no chain ID named)\"\nprintf '{\"tenant\":\"c-chain\",\"chainAlias\":\"C\",\"contentHash\":\"%s\",\"rlpPath\":\"%s\",\"sentinelPath\":\"%s\"}' \"$H\" \"$RLP\" \"$SENTINEL\" > \"$MARKER.tmp\"\nmv \"$MARKER.tmp\" \"$MARKER\"\nrm -f \"$ARM\"\necho \"[rlp-import-init] marker=$MARKER; import driven on /v1/chain/C/rpc\"\n"
|
|
command:
|
|
- /bin/sh
|
|
- -c
|
|
env:
|
|
- name: LUX_CONSENSUS_VIEW_CHANGE
|
|
value: 'true'
|
|
image: ghcr.io/luxfi/node:v1.36.125@sha256:f65281b532a6b2294b83f57610cad2d292a12cf5c85232986bd0b54979d76862
|
|
imagePullPolicy: IfNotPresent
|
|
name: rlp-import
|
|
resources: {}
|
|
terminationMessagePath: /dev/termination-log
|
|
terminationMessagePolicy: File
|
|
volumeMounts:
|
|
- mountPath: /data
|
|
name: data
|
|
- args:
|
|
- "#!/bin/sh\nset -e\n\nPOD_INDEX=${HOSTNAME##*-}\necho \"[GATE] Startup gate for $HOSTNAME (luxd,\
|
|
\ network-id=1)\"\nDNS_PREFIX=\"luxd\"\nDNS_SUFFIX=\"luxd-headless.lux-mainnet.svc.cluster.local\"\
|
|
\nGATE_MIN_PEERS=2\nGATE_TIMEOUT=60\nGATE_INTERVAL=5\nGATE_START=$(date +%s)\necho \"[GATE]\
|
|
\ Waiting for $GATE_MIN_PEERS peers (timeout ${GATE_TIMEOUT}s)...\"\n\nwhile true; do\n REACHABLE=0\n\
|
|
\ if [ \"$POD_INDEX\" != \"0\" ]; then\n if nc -z -w 2 \"${DNS_PREFIX}-0.${DNS_SUFFIX}\"\
|
|
\ 9631 2>/dev/null; then\n REACHABLE=$((REACHABLE + 1))\n fi\n fi\n if [ \"$POD_INDEX\"\
|
|
\ != \"1\" ]; then\n if nc -z -w 2 \"${DNS_PREFIX}-1.${DNS_SUFFIX}\" 9631 2>/dev/null; then\n\
|
|
\ REACHABLE=$((REACHABLE + 1))\n fi\n fi\n if [ \"$POD_INDEX\" != \"2\" ]; then\n\
|
|
\ if nc -z -w 2 \"${DNS_PREFIX}-2.${DNS_SUFFIX}\" 9631 2>/dev/null; then\n REACHABLE=$((REACHABLE\
|
|
\ + 1))\n fi\n fi\n if [ \"$POD_INDEX\" != \"3\" ]; then\n if nc -z -w 2 \"${DNS_PREFIX}-3.${DNS_SUFFIX}\"\
|
|
\ 9631 2>/dev/null; then\n REACHABLE=$((REACHABLE + 1))\n fi\n fi\n if [ \"$POD_INDEX\"\
|
|
\ != \"4\" ]; then\n if nc -z -w 2 \"${DNS_PREFIX}-4.${DNS_SUFFIX}\" 9631 2>/dev/null; then\n\
|
|
\ REACHABLE=$((REACHABLE + 1))\n fi\n fi\n\n if [ \"$REACHABLE\" -ge \"$GATE_MIN_PEERS\"\
|
|
\ ]; then\n echo \"[GATE] $REACHABLE peers reachable, proceeding with startup\"\n break\n\
|
|
\ fi\n\n ELAPSED=$(( $(date +%s) - GATE_START ))\n if [ \"$ELAPSED\" -ge \"$GATE_TIMEOUT\"\
|
|
\ ]; then\n echo \"[GATE] WARNING: Timeout after ${ELAPSED}s with only $REACHABLE peers.\
|
|
\ Proceeding (onTimeout=StartAnyway).\"\n break\n fi\n\n echo \"[GATE] $REACHABLE/$GATE_MIN_PEERS\
|
|
\ peers reachable (${ELAPSED}/${GATE_TIMEOUT}s), waiting ${GATE_INTERVAL}s...\"\n sleep \"\
|
|
$GATE_INTERVAL\"\ndone\n\necho \"[GATE] Gate passed, luxd may start.\"\n"
|
|
command:
|
|
- /bin/sh
|
|
- -c
|
|
env:
|
|
- name: LUX_CONSENSUS_VIEW_CHANGE
|
|
value: 'true'
|
|
image: ghcr.io/luxfi/node:v1.36.125@sha256:f65281b532a6b2294b83f57610cad2d292a12cf5c85232986bd0b54979d76862
|
|
imagePullPolicy: IfNotPresent
|
|
name: startup-gate
|
|
resources: {}
|
|
terminationMessagePath: /dev/termination-log
|
|
terminationMessagePolicy: File
|
|
priorityClassName: luxd-validator-critical
|
|
restartPolicy: Always
|
|
schedulerName: default-scheduler
|
|
securityContext:
|
|
fsGroup: 1000
|
|
runAsGroup: 1000
|
|
runAsUser: 1000
|
|
serviceAccount: default
|
|
serviceAccountName: default
|
|
terminationGracePeriodSeconds: 150
|
|
tolerations:
|
|
- effect: NoExecute
|
|
key: node.kubernetes.io/not-ready
|
|
operator: Exists
|
|
tolerationSeconds: 300
|
|
- effect: NoExecute
|
|
key: node.kubernetes.io/unreachable
|
|
operator: Exists
|
|
tolerationSeconds: 300
|
|
volumes:
|
|
- configMap:
|
|
defaultMode: 420
|
|
name: luxd-config
|
|
name: config
|
|
- configMap:
|
|
defaultMode: 493
|
|
name: luxd-startup
|
|
name: startup-script
|
|
- name: staking-keys
|
|
secret:
|
|
defaultMode: 420
|
|
secretName: luxd-staking
|
|
updateStrategy:
|
|
type: OnDelete
|
|
volumeClaimTemplates:
|
|
- apiVersion: v1
|
|
kind: PersistentVolumeClaim
|
|
metadata:
|
|
name: data
|
|
spec:
|
|
accessModes:
|
|
- ReadWriteOnce
|
|
resources:
|
|
requests:
|
|
storage: 200Gi
|
|
volumeMode: Filesystem
|
|
status:
|
|
phase: Pending
|