Files
zeekayandClaude Opus 5.5 8290a9ff38 chain-tip reports how old each Lux chain's newest block is
An RPC that answers eth_chainId says nothing about whether the chain still makes
blocks, and the status page could not tell a halted chain from a live one.
chain-tip answers /v1/tip/<chain> with height, timestamp and age_seconds for
lux-mainnet and lux-testnet, read from their luxd-archive Services. The chains
are fixed in its env; a request names a chain, never a URL. ClusterIP only.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-23 16:31:58 -07:00

121 lines
4.5 KiB
YAML

# How old each Lux chain's newest block is, for the status pages.
#
# An RPC that answers eth_chainId says nothing about whether the chain still
# makes blocks: an archive with no peers answers every read. The status checks
# (hanzoai/universe charts/app/values/hanzo/status-lux.yaml, status-lux-config)
# read /v1/tip/<chain> here and hold age_seconds to a limit, so a chain that has
# stopped shows as down.
#
# The chain list is fixed in CHAINS; a request names a chain, never a URL. The
# only call made is eth_getBlockByNumber("latest"). ClusterIP only: nothing
# outside the cluster reaches this Service.
apiVersion: v1
kind: ConfigMap
metadata: {name: chain-tip-code, namespace: lux-mainnet}
data:
tip.py: |
import json, os, time, urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
CHAINS = json.loads(os.environ["CHAINS"])
TIMEOUT = float(os.environ.get("TIMEOUT_SECONDS", "5"))
CALL = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "eth_getBlockByNumber",
"params": ["latest", False]}).encode()
def tip(url):
req = urllib.request.Request(url, CALL, {"content-type": "application/json", "user-agent": "lux-chain-tip"})
with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
block = json.load(r)["result"]
return int(block["number"], 16), int(block["timestamp"], 16)
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/healthz":
return self.reply(200, {"status": "ok"})
prefix = "/v1/tip/"
name = self.path[len(prefix):] if self.path.startswith(prefix) else ""
if name not in CHAINS:
return self.reply(404, {"error": "unknown chain", "chains": sorted(CHAINS)})
try:
height, stamp = tip(CHAINS[name])
except Exception as e:
return self.reply(502, {"chain": name, "error": type(e).__name__})
self.reply(200, {"chain": name, "height": height, "timestamp": stamp,
"age_seconds": int(time.time()) - stamp})
def reply(self, code, body):
data = json.dumps(body).encode()
self.send_response(code)
self.send_header("content-type", "application/json")
self.send_header("content-length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def log_message(self, *args):
pass
ThreadingHTTPServer(("", 8080), Handler).serve_forever()
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: chain-tip
namespace: lux-mainnet
labels: {app.kubernetes.io/name: chain-tip, app.kubernetes.io/part-of: lux}
spec:
replicas: 1
selector:
matchLabels: {app.kubernetes.io/name: chain-tip}
template:
metadata:
labels: {app.kubernetes.io/name: chain-tip, app.kubernetes.io/part-of: lux}
spec:
automountServiceAccountToken: false
enableServiceLinks: false
nodeSelector:
kubernetes.io/arch: amd64
hanzo.ai/role: apps
securityContext:
runAsUser: 65532
runAsGroup: 65532
runAsNonRoot: true
seccompProfile: {type: RuntimeDefault}
containers:
- name: tip
image: python:3.12-alpine@sha256:6d43704baacd1bfbe7c295d7f13079d5d8104ed33568873133f8fc69980419df
command: [python, -u, /app/tip.py]
env:
- name: CHAINS
value: '{"lux-mainnet":"http://luxd-archive.lux-mainnet.svc.cluster.local:9630/v1/chain/C/rpc","lux-testnet":"http://luxd-archive.lux-testnet.svc.cluster.local:9630/v1/chain/C/rpc"}'
ports:
- {name: http, containerPort: 8080}
readinessProbe:
httpGet: {path: /healthz, port: http}
periodSeconds: 10
livenessProbe:
httpGet: {path: /healthz, port: http}
periodSeconds: 30
resources:
requests: {cpu: 5m, memory: 24Mi}
limits: {cpu: 100m, memory: 64Mi}
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: {drop: [ALL]}
volumeMounts:
- {name: code, mountPath: /app, readOnly: true}
volumes:
- name: code
configMap: {name: chain-tip-code}
---
apiVersion: v1
kind: Service
metadata:
name: chain-tip
namespace: lux-mainnet
labels: {app.kubernetes.io/name: chain-tip, app.kubernetes.io/part-of: lux}
spec:
selector: {app.kubernetes.io/name: chain-tip}
ports:
- {name: http, port: 80, targetPort: http}