mirror of
https://github.com/hanzoai/bootnode.git
synced 2026-09-30 20:46:32 +00:00
wip: preserve in-flight work
Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
module github.com/hanzoai/bootnode
|
||||
|
||||
go 1.26.4
|
||||
go 1.26.5
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.1.0
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: v2
|
||||
name: hanzo
|
||||
description: Hanzo sovereign-L1 node deployment for K8s (networkID == EVM chainID; 0x0300 AI-mining precompile active at genesis)
|
||||
type: application
|
||||
version: 1.0.0
|
||||
# luxd binary the image ships — Hanzo runs the same node, differentiated by genesis (networkID 36963, NOT Lux C-Chain 96369)
|
||||
appVersion: "1.23.15"
|
||||
keywords:
|
||||
- blockchain
|
||||
- hanzo
|
||||
- validator
|
||||
- ai-mining
|
||||
- kubernetes
|
||||
maintainers:
|
||||
- name: Hanzo AI
|
||||
email: [email protected]
|
||||
@@ -0,0 +1,53 @@
|
||||
# Hanzo sovereign-L1 — provisioning
|
||||
|
||||
Deploy **target**: DOKS `hanzo-k8s`, via **platform.hanzo.ai** / the hanzo operator
|
||||
(GitOps). Namespaces `hanzo-{mainnet,testnet,devnet}`. Exposure: LoadBalancer →
|
||||
`rpc.hanzo.network` / `explorer.hanzo.network`.
|
||||
|
||||
This chart deploys Hanzo's **own sovereign primary network** — `networkID == EVM
|
||||
chainID` (mainnet **36963** / testnet 36962 / devnet 36964). Hanzo does **not** run on
|
||||
the Lux C-Chain (96369); that is Lux-network only. The C-Chain genesis embedded in
|
||||
`genesis/<env>.json` activates the **`0x0300` AI-mining precompile at genesis**
|
||||
(`aiMiningConfig: {blockTimestamp: 0}`) — this is what makes desktop AI-mining
|
||||
`verifyAndMintWork` claims settle on-chain.
|
||||
|
||||
## What's in the chart (committed, safe)
|
||||
- `Chart.yaml`, `values.yaml` + `values-<env>.yaml` — networkID=chainID, custom-network
|
||||
`upgradeConfig` (REQUIRED — 36963/62/64 have no built-in upgrade schedule), branding.
|
||||
- `genesis/<env>.json` — platform genesis: networkID + the **Hanzo C-Chain genesis**
|
||||
(chainId + `aiMiningConfig`) embedded as `cChainGenesis`.
|
||||
- `templates/` — reused from the lux chart (parameterized by `.Values`).
|
||||
|
||||
## What the operator MUST provision (from KMS — NEVER commit keys)
|
||||
1. **Validator staking keys** → create the `hanzo-staking` Secret (per `values.yaml
|
||||
staking.secretName`) from KMS-generated keys, then populate, in `genesis/<env>.json`:
|
||||
- `initialStakers[]` — each `{nodeID, rewardAddress, delegationFee, signer:{publicKey,
|
||||
proofOfPossession}}` (PUBLIC keys only).
|
||||
- `initialStakedFunds[]` — the staked P-chain addresses.
|
||||
And set `bootstrap.nodeIDs` (in `values-<env>.yaml`) to the same NodeIDs.
|
||||
2. **Treasury / allocations** → `allocations[]` in `genesis/<env>.json` (P-chain `luxAddr`
|
||||
+ C-chain `ethAddr` funded addresses; KMS treasury). The C-Chain genesis already
|
||||
pre-funds the dev treasury `0x9011E888251AB053B7bD1cdB598Db4f9DEd94714`.
|
||||
3. **Derived C-Chain blockchain ID** (computed at `CreateChainTx` time, only known after
|
||||
the network is created) →
|
||||
- `networks.<env>.cChainID` in `values.yaml`.
|
||||
- **`templates/statefulset.yaml`** plugin-copy lines still carry the **lux** C-Chain
|
||||
blockchain ID (`mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6`). Replace with
|
||||
Hanzo's derived C-Chain plugin/blockchain ID, or parameterize via a new
|
||||
`.Values.cChainPluginID` before the second deploy.
|
||||
|
||||
## Bring-up order
|
||||
1. Operator generates validator keys (KMS) → `hanzo-staking` Secret + fills genesis
|
||||
`initialStakers`/`allocations` + `bootstrap.nodeIDs`.
|
||||
2. `helm upgrade --install hanzo ./charts/hanzo -f values-mainnet.yaml -n hanzo-mainnet`
|
||||
(via platform.hanzo.ai GitOps, not `helm` by hand).
|
||||
3. After first genesis, capture the C-Chain blockchain ID → set `cChainID` + the
|
||||
statefulset plugin ID → redeploy.
|
||||
4. Point `rpc.hanzo.network` / `explorer.hanzo.network` LoadBalancers; verify
|
||||
`eth_chainId` == `0x9063` (36963), validators healthy, `0x0300` responds.
|
||||
|
||||
## Why not a one-command deploy here
|
||||
Validator BLS keys and the derived blockchain ID are network-creation outputs that live
|
||||
in KMS / are computed by the node at genesis — they are intentionally **not** in git.
|
||||
The desktop side (chainId 36963, the Rust claim signer, the C-Chain genesis) is already
|
||||
committed; this chart + the operator's KMS provisioning complete the loop.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"networkID": 36964,
|
||||
"message": "Hanzo AI \u2014 sovereign L1 (networkID == chainID; 0x0300 AI-mining precompile active at genesis)",
|
||||
"startTime": 1766704800,
|
||||
"initialStakeDuration": 31536000,
|
||||
"initialStakeDurationOffset": 5400,
|
||||
"allocations": [],
|
||||
"initialStakedFunds": [],
|
||||
"initialStakers": [],
|
||||
"cChainGenesis": "{\"alloc\":{\"0200000000000000000000000000000000000005\":{\"balance\":\"0x0\",\"code\":\"0x01\",\"nonce\":\"0x1\"},\"9011E888251AB053B7bD1cdB598Db4f9DEd94714\":{\"balance\":\"0x193e5939a08ce9dbd480000000\"}},\"baseFeePerGas\":\"0x5d21dba00\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"config\":{\"aiMiningConfig\":{\"blockTimestamp\":0},\"arrowGlacierBlock\":0,\"berlinBlock\":0,\"blobSchedule\":{\"cancun\":{\"baseFeeUpdateFraction\":3338477,\"max\":6,\"target\":3}},\"byzantiumBlock\":0,\"cancunTime\":0,\"chainId\":36964,\"constantinopleBlock\":0,\"eip150Block\":0,\"eip155Block\":0,\"eip158Block\":0,\"evmTimestamp\":0,\"feeConfig\":{\"baseFeeChangeDenominator\":36,\"blockGasCostStep\":200000,\"gasLimit\":1000000000,\"maxBlockGasCost\":1000000,\"minBaseFee\":25000000000,\"minBlockGasCost\":0,\"targetBlockRate\":2,\"targetGas\":500000000},\"grayGlacierBlock\":0,\"homesteadBlock\":0,\"istanbulBlock\":0,\"londonBlock\":0,\"mergeNetsplitBlock\":0,\"muirGlacierBlock\":0,\"petersburgBlock\":0,\"shanghaiTime\":0,\"terminalTotalDifficulty\":0,\"warpConfig\":{\"blockTimestamp\":0,\"quorumNumerator\":67,\"requirePrimaryNetworkSigners\":false}},\"difficulty\":\"0x0\",\"extraData\":\"0x\",\"gasLimit\":\"0x3b9aca00\",\"gasUsed\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x0\",\"number\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"timestamp\":\"0x0\"}"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"networkID": 36963,
|
||||
"message": "Hanzo AI \u2014 sovereign L1 (networkID == chainID; 0x0300 AI-mining precompile active at genesis)",
|
||||
"startTime": 1766704800,
|
||||
"initialStakeDuration": 31536000,
|
||||
"initialStakeDurationOffset": 5400,
|
||||
"allocations": [],
|
||||
"initialStakedFunds": [],
|
||||
"initialStakers": [],
|
||||
"cChainGenesis": "{\"alloc\":{\"0200000000000000000000000000000000000005\":{\"balance\":\"0x0\",\"code\":\"0x01\",\"nonce\":\"0x1\"},\"9011E888251AB053B7bD1cdB598Db4f9DEd94714\":{\"balance\":\"0x193e5939a08ce9dbd480000000\"}},\"baseFeePerGas\":\"0x5d21dba00\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"config\":{\"aiMiningConfig\":{\"blockTimestamp\":0},\"arrowGlacierBlock\":0,\"berlinBlock\":0,\"blobSchedule\":{\"cancun\":{\"baseFeeUpdateFraction\":3338477,\"max\":6,\"target\":3}},\"byzantiumBlock\":0,\"cancunTime\":0,\"chainId\":36963,\"constantinopleBlock\":0,\"eip150Block\":0,\"eip155Block\":0,\"eip158Block\":0,\"evmTimestamp\":0,\"feeConfig\":{\"baseFeeChangeDenominator\":36,\"blockGasCostStep\":200000,\"gasLimit\":1000000000,\"maxBlockGasCost\":1000000,\"minBaseFee\":25000000000,\"minBlockGasCost\":0,\"targetBlockRate\":2,\"targetGas\":500000000},\"grayGlacierBlock\":0,\"homesteadBlock\":0,\"istanbulBlock\":0,\"londonBlock\":0,\"mergeNetsplitBlock\":0,\"muirGlacierBlock\":0,\"petersburgBlock\":0,\"shanghaiTime\":0,\"terminalTotalDifficulty\":0,\"warpConfig\":{\"blockTimestamp\":0,\"quorumNumerator\":67,\"requirePrimaryNetworkSigners\":false}},\"difficulty\":\"0x0\",\"extraData\":\"0x\",\"gasLimit\":\"0x3b9aca00\",\"gasUsed\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x0\",\"number\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"timestamp\":\"0x0\"}"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"networkID": 36962,
|
||||
"message": "Hanzo AI \u2014 sovereign L1 (networkID == chainID; 0x0300 AI-mining precompile active at genesis)",
|
||||
"startTime": 1766704800,
|
||||
"initialStakeDuration": 31536000,
|
||||
"initialStakeDurationOffset": 5400,
|
||||
"allocations": [],
|
||||
"initialStakedFunds": [],
|
||||
"initialStakers": [],
|
||||
"cChainGenesis": "{\"alloc\":{\"0200000000000000000000000000000000000005\":{\"balance\":\"0x0\",\"code\":\"0x01\",\"nonce\":\"0x1\"},\"9011E888251AB053B7bD1cdB598Db4f9DEd94714\":{\"balance\":\"0x193e5939a08ce9dbd480000000\"}},\"baseFeePerGas\":\"0x5d21dba00\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"config\":{\"aiMiningConfig\":{\"blockTimestamp\":0},\"arrowGlacierBlock\":0,\"berlinBlock\":0,\"blobSchedule\":{\"cancun\":{\"baseFeeUpdateFraction\":3338477,\"max\":6,\"target\":3}},\"byzantiumBlock\":0,\"cancunTime\":0,\"chainId\":36962,\"constantinopleBlock\":0,\"eip150Block\":0,\"eip155Block\":0,\"eip158Block\":0,\"evmTimestamp\":0,\"feeConfig\":{\"baseFeeChangeDenominator\":36,\"blockGasCostStep\":200000,\"gasLimit\":1000000000,\"maxBlockGasCost\":1000000,\"minBaseFee\":25000000000,\"minBlockGasCost\":0,\"targetBlockRate\":2,\"targetGas\":500000000},\"grayGlacierBlock\":0,\"homesteadBlock\":0,\"istanbulBlock\":0,\"londonBlock\":0,\"mergeNetsplitBlock\":0,\"muirGlacierBlock\":0,\"petersburgBlock\":0,\"shanghaiTime\":0,\"terminalTotalDifficulty\":0,\"warpConfig\":{\"blockTimestamp\":0,\"quorumNumerator\":67,\"requirePrimaryNetworkSigners\":false}},\"difficulty\":\"0x0\",\"extraData\":\"0x\",\"gasLimit\":\"0x3b9aca00\",\"gasUsed\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x0\",\"number\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"timestamp\":\"0x0\"}"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{{- $network := index .Values.networks .Values.network }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Values.genesis.configMapName }}
|
||||
namespace: {{ $network.namespace }}
|
||||
data:
|
||||
genesis.json: |
|
||||
{{- .Files.Get (printf "genesis/%s.json" .Values.network) | nindent 4 }}
|
||||
@@ -0,0 +1,8 @@
|
||||
{{- $network := index .Values.networks .Values.network }}
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: {{ $network.namespace }}
|
||||
labels:
|
||||
app: lux
|
||||
network: {{ .Values.network }}
|
||||
@@ -0,0 +1,26 @@
|
||||
{{- $network := index .Values.networks .Values.network }}
|
||||
{{- $namespace := $network.namespace }}
|
||||
# Role to allow luxd pods to read their own LoadBalancer service for public IP discovery
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: luxd-service-reader
|
||||
namespace: {{ $namespace }}
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: luxd-service-reader
|
||||
namespace: {{ $namespace }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: luxd-service-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: default
|
||||
namespace: {{ $namespace }}
|
||||
@@ -0,0 +1,85 @@
|
||||
{{- $network := index .Values.networks .Values.network }}
|
||||
{{- $namespace := $network.namespace }}
|
||||
{{- $stakingPort := $network.stakingPort }}
|
||||
{{- $httpPort := $network.httpPort }}
|
||||
# Headless service for StatefulSet DNS resolution
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: luxd-headless
|
||||
namespace: {{ $namespace }}
|
||||
labels:
|
||||
app: luxd
|
||||
lux-network: {{ .Values.network }}
|
||||
spec:
|
||||
clusterIP: None
|
||||
publishNotReadyAddresses: true
|
||||
selector:
|
||||
app: luxd
|
||||
lux-network: {{ .Values.network }}
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ $httpPort }}
|
||||
- name: staking
|
||||
port: {{ $stakingPort }}
|
||||
{{- if .Values.nodeServices.enabled }}
|
||||
---
|
||||
# Main LoadBalancer service (round-robin across all pods)
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: luxd
|
||||
namespace: {{ $namespace }}
|
||||
labels:
|
||||
app: luxd
|
||||
lux-network: {{ .Values.network }}
|
||||
annotations:
|
||||
service.beta.kubernetes.io/do-loadbalancer-name: "k8s-luxd-{{ .Values.network }}"
|
||||
{{- with .Values.nodeServices.annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.nodeServices.type }}
|
||||
selector:
|
||||
app: luxd
|
||||
lux-network: {{ .Values.network }}
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ $httpPort }}
|
||||
targetPort: {{ $httpPort }}
|
||||
- name: staking
|
||||
port: {{ $stakingPort }}
|
||||
targetPort: {{ $stakingPort }}
|
||||
# Individual LoadBalancer services per pod for stable external IPs
|
||||
# CRITICAL for P2P: each validator needs its own public IP
|
||||
{{- range $i := until (int .Values.replicas) }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: luxd-{{ $i }}
|
||||
namespace: {{ $namespace }}
|
||||
labels:
|
||||
app: luxd
|
||||
lux-network: {{ $.Values.network }}
|
||||
pod-index: "{{ $i }}"
|
||||
annotations:
|
||||
service.beta.kubernetes.io/do-loadbalancer-name: "k8s-luxd-{{ $.Values.network }}-{{ $i }}"
|
||||
{{- with $.Values.nodeServices.annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ $.Values.nodeServices.type }}
|
||||
selector:
|
||||
app: luxd
|
||||
lux-network: {{ $.Values.network }}
|
||||
statefulset.kubernetes.io/pod-name: luxd-{{ $i }}
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ $httpPort }}
|
||||
targetPort: {{ $httpPort }}
|
||||
- name: staking
|
||||
port: {{ $stakingPort }}
|
||||
targetPort: {{ $stakingPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,290 @@
|
||||
{{- $network := index .Values.networks .Values.network }}
|
||||
{{- $namespace := $network.namespace }}
|
||||
{{- $stakingPort := $network.stakingPort }}
|
||||
{{- $httpPort := $network.httpPort }}
|
||||
{{- $replicas := int .Values.replicas }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: luxd-startup
|
||||
namespace: {{ $namespace }}
|
||||
data:
|
||||
startup.sh: |
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
POD_INDEX=${HOSTNAME##*-}
|
||||
echo "=== luxd startup for $HOSTNAME ({{ .Values.network }}, network-id={{ $network.networkID }}) ==="
|
||||
|
||||
# Staking keys are set up by init container
|
||||
echo "Staking keys already in place for node $POD_INDEX"
|
||||
|
||||
# Build bootstrap node list excluding self
|
||||
# Uses --bootstrap-nodes (endpoint-only, NodeID discovered from TLS cert)
|
||||
{{- if .Values.bootstrap.useHostnames }}
|
||||
# Using internal K8s DNS for bootstrap (recommended)
|
||||
DNS_PREFIX="luxd"
|
||||
DNS_SUFFIX="luxd-headless.{{ $namespace }}.svc.cluster.local"
|
||||
BOOTSTRAP_NODES=""
|
||||
{{- range $i := until $replicas }}
|
||||
if [ "$POD_INDEX" != "{{ $i }}" ]; then
|
||||
CUR_HOST="${DNS_PREFIX}-{{ $i }}.${DNS_SUFFIX}:{{ $stakingPort }}"
|
||||
if [ -n "$BOOTSTRAP_NODES" ]; then
|
||||
BOOTSTRAP_NODES="${BOOTSTRAP_NODES},${CUR_HOST}"
|
||||
else
|
||||
BOOTSTRAP_NODES="${CUR_HOST}"
|
||||
fi
|
||||
fi
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
# Using external IPs for bootstrap
|
||||
BOOTSTRAP_NODES=""
|
||||
{{- range $i := until $replicas }}
|
||||
{{- $externalIP := "" }}
|
||||
{{- if and $.Values.bootstrap.externalIPs (gt (len $.Values.bootstrap.externalIPs) $i) }}
|
||||
{{- $externalIP = index $.Values.bootstrap.externalIPs $i }}
|
||||
{{- end }}
|
||||
if [ "$POD_INDEX" != "{{ $i }}" ] && [ -n "{{ $externalIP }}" ]; then
|
||||
if [ -n "$BOOTSTRAP_NODES" ]; then
|
||||
BOOTSTRAP_NODES="${BOOTSTRAP_NODES},{{ $externalIP }}:{{ $stakingPort }}"
|
||||
else
|
||||
BOOTSTRAP_NODES="{{ $externalIP }}:{{ $stakingPort }}"
|
||||
fi
|
||||
fi
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
# Get public IP
|
||||
# Priority: 1) static externalIPs from values, 2) K8s LB service discovery, 3) pod IP fallback
|
||||
PUBLIC_IP=""
|
||||
{{- if .Values.bootstrap.externalIPs }}
|
||||
case "$POD_INDEX" in
|
||||
{{- range $i := until $replicas }}
|
||||
{{- if and $.Values.bootstrap.externalIPs (gt (len $.Values.bootstrap.externalIPs) $i) }}
|
||||
{{ $i }}) PUBLIC_IP="{{ index $.Values.bootstrap.externalIPs $i }}" ;;
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
esac
|
||||
{{- end }}
|
||||
|
||||
# If no static IP, discover from per-pod LoadBalancer service via K8s API
|
||||
if [ -z "$PUBLIC_IP" ]; then
|
||||
SA_TOKEN_FILE="/var/run/secrets/kubernetes.io/serviceaccount/token"
|
||||
CA_CERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||||
K8S_API="https://kubernetes.default.svc"
|
||||
SVC_NAME="luxd-${POD_INDEX}"
|
||||
NAMESPACE="{{ $namespace }}"
|
||||
|
||||
if [ -f "$SA_TOKEN_FILE" ]; then
|
||||
TOKEN=$(cat "$SA_TOKEN_FILE")
|
||||
echo "[PUBLIC-IP] Discovering external IP from service ${SVC_NAME} in ${NAMESPACE}..."
|
||||
ATTEMPT=0
|
||||
MAX_ATTEMPTS=12
|
||||
while [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; do
|
||||
SVC_JSON=$(curl -sf -m 5 \
|
||||
-H "Authorization: Bearer ${TOKEN}" \
|
||||
--cacert "$CA_CERT" \
|
||||
"${K8S_API}/api/v1/namespaces/${NAMESPACE}/services/${SVC_NAME}" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$SVC_JSON" ]; then
|
||||
# Extract loadBalancer ingress IP (no jq in alpine, JSON spans multiple lines)
|
||||
# Look for "ip" field that appears after "ingress" in the status block
|
||||
LB_IP=$(echo "$SVC_JSON" | tr -d ' \n' | sed -n 's/.*"loadBalancer":{"ingress":\[{"ip":"\([^"]*\)".*/\1/p')
|
||||
if [ -n "$LB_IP" ]; then
|
||||
PUBLIC_IP="$LB_IP"
|
||||
echo "[PUBLIC-IP] Discovered external IP: $PUBLIC_IP (attempt $((ATTEMPT+1)))"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then
|
||||
echo "[PUBLIC-IP] LB IP not yet assigned, retrying in 5s... (attempt $((ATTEMPT))/${MAX_ATTEMPTS})"
|
||||
sleep 5
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "[PUBLIC-IP] No service account token found, skipping LB discovery"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Final fallback to pod IP
|
||||
if [ -z "$PUBLIC_IP" ]; then
|
||||
PUBLIC_IP=$(hostname -i)
|
||||
echo "[PUBLIC-IP] WARNING: Using pod IP $PUBLIC_IP (no external IP found)"
|
||||
fi
|
||||
|
||||
{{- if .Values.chainConfigs.enabled }}
|
||||
# Write EVM chain configs for C-chain and deployed subnet chains
|
||||
CHAIN_CFG='{{ .Values.chainConfigs.evmConfig }}'
|
||||
for chain in C{{ range .Values.chainConfigs.blockchainIDs }} {{ . }}{{ end }}; do
|
||||
mkdir -p /data/configs/chains/$chain
|
||||
echo "$CHAIN_CFG" > /data/configs/chains/$chain/config.json
|
||||
done
|
||||
echo "Chain configs written for C{{ range .Values.chainConfigs.blockchainIDs }} + {{ . | trunc 8 }}{{ end }}"
|
||||
{{- end }}
|
||||
|
||||
echo " public-ip: $PUBLIC_IP"
|
||||
echo " bootstrap-nodes: $BOOTSTRAP_NODES"
|
||||
|
||||
# Build luxd arguments
|
||||
ARGS="--network-id={{ $network.networkID }}"
|
||||
ARGS="$ARGS --http-host=0.0.0.0"
|
||||
ARGS="$ARGS --http-port={{ $httpPort }}"
|
||||
ARGS="$ARGS --http-allowed-hosts={{ .Values.api.httpAllowedHosts }}"
|
||||
ARGS="$ARGS --staking-port={{ $stakingPort }}"
|
||||
ARGS="$ARGS --data-dir=/data"
|
||||
ARGS="$ARGS --genesis-file=/genesis/genesis.json"
|
||||
ARGS="$ARGS --db-type={{ .Values.dbType }}"
|
||||
{{- if .Values.chainConfigs.enabled }}
|
||||
ARGS="$ARGS --chain-config-dir=/data/configs/chains"
|
||||
{{- end }}
|
||||
ARGS="$ARGS --index-enabled={{ .Values.api.indexEnabled }}"
|
||||
ARGS="$ARGS --api-admin-enabled={{ .Values.api.adminEnabled }}"
|
||||
ARGS="$ARGS --api-metrics-enabled={{ .Values.api.metricsEnabled }}"
|
||||
ARGS="$ARGS --api-keystore-enabled=true"
|
||||
ARGS="$ARGS --log-level={{ .Values.logLevel }}"
|
||||
ARGS="$ARGS --plugin-dir=/data/plugins"
|
||||
ARGS="$ARGS --staking-tls-cert-file=/data/staking/staker.crt"
|
||||
ARGS="$ARGS --staking-tls-key-file=/data/staking/staker.key"
|
||||
|
||||
# Signer key is optional
|
||||
if [ -f "/data/staking/signer.key" ]; then
|
||||
ARGS="$ARGS --staking-signer-key-file=/data/staking/signer.key"
|
||||
fi
|
||||
|
||||
ARGS="$ARGS --consensus-sample-size={{ .Values.consensus.sampleSize }}"
|
||||
ARGS="$ARGS --consensus-quorum-size={{ .Values.consensus.quorumSize }}"
|
||||
ARGS="$ARGS --sybil-protection-enabled={{ .Values.consensus.sybilProtectionEnabled }}"
|
||||
ARGS="$ARGS --network-require-validator-to-connect={{ .Values.consensus.requireValidatorToConnect }}"
|
||||
ARGS="$ARGS --network-allow-private-ips={{ .Values.consensus.allowPrivateIPs }}"
|
||||
|
||||
if [ -n "$PUBLIC_IP" ]; then
|
||||
ARGS="$ARGS --public-ip=$PUBLIC_IP"
|
||||
fi
|
||||
|
||||
if [ -n "$BOOTSTRAP_NODES" ]; then
|
||||
ARGS="$ARGS --bootstrap-nodes=$BOOTSTRAP_NODES"
|
||||
fi
|
||||
|
||||
# Chain tracking
|
||||
{{- if .Values.chainTracking.trackAllChains }}
|
||||
ARGS="$ARGS --track-all-chains"
|
||||
{{- else if .Values.chainTracking.trackedChains }}
|
||||
ARGS="$ARGS --track-chains={{ join "," .Values.chainTracking.trackedChains }}"
|
||||
{{- end }}
|
||||
|
||||
# Upgrade config override (all upgrades from genesis for custom networks)
|
||||
{{- if .Values.upgradeConfig.enabled }}
|
||||
UPGRADE_JSON='{{ .Values.upgradeConfig.content | replace "\n" "" | trim }}'
|
||||
UPGRADE_B64=$(echo "$UPGRADE_JSON" | base64 | tr -d '\n')
|
||||
ARGS="$ARGS --upgrade-file-content=$UPGRADE_B64"
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.bootstrap.rlpImport.enabled }}
|
||||
# Clear C-chain data on restart to avoid trie corruption
|
||||
# (path database prunes genesis state, causing "triedb parent layer missing" on restart)
|
||||
# P-chain and X-chain data are preserved (subnet deployments, etc.)
|
||||
C_CHAIN_DIR="/data/chainData/network-{{ $network.networkID }}/{{ $network.cChainID }}"
|
||||
IMPORT_MARKER="/data/.bootstrap-{{ .Values.network }}-import.done"
|
||||
if [ -d "$C_CHAIN_DIR" ]; then
|
||||
echo "[STARTUP] Clearing C-chain data to prevent trie corruption on restart..."
|
||||
rm -rf "$C_CHAIN_DIR"
|
||||
rm -f "$IMPORT_MARKER"
|
||||
echo "[STARTUP] C-chain data cleared, will re-import RLP"
|
||||
fi
|
||||
{{- end }}
|
||||
|
||||
# Start luxd in background (allows post-start bootstrap logic)
|
||||
/luxd/build/luxd $ARGS &
|
||||
LUXD_PID=$!
|
||||
|
||||
# Disable set -e for bootstrap section
|
||||
set +e
|
||||
|
||||
# Wait for node to be healthy
|
||||
echo "[BOOTSTRAP] Waiting for node health..."
|
||||
i=0
|
||||
while [ "$i" -lt 180 ]; do
|
||||
if curl -sf -m 2 http://127.0.0.1:{{ $httpPort }}/ext/health >/dev/null 2>&1; then
|
||||
echo "[BOOTSTRAP] Node healthy after ${i}s"
|
||||
break
|
||||
fi
|
||||
i=$((i + 1))
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Set up chain aliases
|
||||
{{- if .Values.chainTracking.aliases }}
|
||||
{{- range .Values.chainTracking.aliases }}
|
||||
curl -s -m 5 -X POST -H 'Content-Type: application/json' \
|
||||
--data '{"jsonrpc":"2.0","id":1,"method":"admin.aliasChain","params":{"chain":"C","alias":"{{ . }}"}}' \
|
||||
http://127.0.0.1:{{ $httpPort }}/ext/admin >/dev/null 2>&1 || true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.bootstrap.rlpImport.enabled }}
|
||||
# C-Chain RLP import
|
||||
MARKER="/data/.bootstrap-{{ .Values.network }}-import.done"
|
||||
BOOTSTRAP_DIR="/data/bootstrap"
|
||||
|
||||
if [ ! -f "$MARKER" ]; then
|
||||
height_hex=$(curl -s -m 10 -X POST -H 'Content-Type: application/json' \
|
||||
--data '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}' \
|
||||
http://127.0.0.1:{{ $httpPort }}/ext/bc/C/rpc | sed -n 's/.*"result":"\(0x[0-9A-Fa-f]*\)".*/\1/p' | head -n1)
|
||||
|
||||
if [ -z "$height_hex" ]; then
|
||||
height_hex="0x0"
|
||||
fi
|
||||
|
||||
height_dec=$(printf '%d' "$height_hex" 2>/dev/null || echo 0)
|
||||
echo "[BOOTSTRAP] C-Chain height: $height_dec (hex: $height_hex)"
|
||||
|
||||
if [ "$height_dec" -lt "{{ int .Values.bootstrap.rlpImport.minHeight }}" ]; then
|
||||
echo "[BOOTSTRAP] C-Chain below target ({{ int .Values.bootstrap.rlpImport.minHeight }}), starting RLP import..."
|
||||
mkdir -p "$BOOTSTRAP_DIR"
|
||||
RLP_FILE="${BOOTSTRAP_DIR}/{{ .Values.bootstrap.rlpImport.rlpFilename }}"
|
||||
|
||||
{{- if .Values.bootstrap.rlpImport.multiPart }}
|
||||
# Download multi-part RLP
|
||||
if [ ! -s "$RLP_FILE" ]; then
|
||||
echo "[BOOTSTRAP] Downloading RLP parts..."
|
||||
{{- range .Values.bootstrap.rlpImport.parts }}
|
||||
PART_FILE="${BOOTSTRAP_DIR}/{{ $.Values.bootstrap.rlpImport.rlpFilename }}.part.{{ . }}"
|
||||
if [ ! -s "$PART_FILE" ]; then
|
||||
echo "[BOOTSTRAP] Downloading part {{ . }}..."
|
||||
curl -fL --retry 5 --retry-delay 2 --retry-connrefused -o "$PART_FILE" \
|
||||
"{{ $.Values.bootstrap.rlpImport.baseUrl }}/{{ $.Values.bootstrap.rlpImport.rlpFilename | replace ".rlp" "" }}.part.{{ . }}" || true
|
||||
fi
|
||||
{{- end }}
|
||||
echo "[BOOTSTRAP] Assembling RLP file..."
|
||||
cat "${BOOTSTRAP_DIR}"/{{ .Values.bootstrap.rlpImport.rlpFilename }}.part.* > "$RLP_FILE" 2>/dev/null || true
|
||||
fi
|
||||
{{- else }}
|
||||
# Download single RLP file
|
||||
if [ ! -s "$RLP_FILE" ]; then
|
||||
echo "[BOOTSTRAP] Downloading RLP..."
|
||||
curl -fL --retry 5 --retry-delay 2 --retry-connrefused -o "$RLP_FILE" \
|
||||
"{{ .Values.bootstrap.rlpImport.baseUrl }}/{{ .Values.bootstrap.rlpImport.rlpFilename }}" || true
|
||||
fi
|
||||
{{- end }}
|
||||
|
||||
if [ -s "$RLP_FILE" ]; then
|
||||
echo "[BOOTSTRAP] Starting import of $RLP_FILE..."
|
||||
import_payload=$(printf '{"jsonrpc":"2.0","id":1,"method":"admin_importChain","params":["%s"]}' "$RLP_FILE")
|
||||
import_result=$(curl -s -m {{ int .Values.bootstrap.rlpImport.timeout }} -X POST -H 'Content-Type: application/json' --data "$import_payload" http://127.0.0.1:{{ $httpPort }}/ext/bc/C/rpc || true)
|
||||
echo "[BOOTSTRAP] Import result: $import_result"
|
||||
echo "$import_result" | grep -q '"success":true' && touch "$MARKER" && echo "[BOOTSTRAP] Import complete" || echo "[BOOTSTRAP] Import did not return success"
|
||||
else
|
||||
echo "[BOOTSTRAP] RLP file missing or empty, skipping"
|
||||
fi
|
||||
else
|
||||
echo "[BOOTSTRAP] C-Chain already at height $height_dec, skipping import"
|
||||
touch "$MARKER"
|
||||
fi
|
||||
else
|
||||
echo "[BOOTSTRAP] Import marker exists, skipping bootstrap"
|
||||
fi
|
||||
{{- end }}
|
||||
|
||||
wait "$LUXD_PID"
|
||||
@@ -0,0 +1,226 @@
|
||||
{{- $network := index .Values.networks .Values.network }}
|
||||
{{- $namespace := $network.namespace }}
|
||||
{{- $stakingPort := $network.stakingPort }}
|
||||
{{- $httpPort := $network.httpPort }}
|
||||
{{- $replicas := int .Values.replicas }}
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: luxd
|
||||
namespace: {{ $namespace }}
|
||||
annotations:
|
||||
lux.network/protect-pvc: "true"
|
||||
spec:
|
||||
serviceName: luxd-headless
|
||||
replicas: {{ .Values.replicas }}
|
||||
podManagementPolicy: Parallel
|
||||
updateStrategy:
|
||||
type: {{ .Values.upgradeStrategy.type | default "OnDelete" }}
|
||||
persistentVolumeClaimRetentionPolicy:
|
||||
whenDeleted: Retain
|
||||
whenScaled: Retain
|
||||
selector:
|
||||
matchLabels:
|
||||
app: luxd
|
||||
lux-network: {{ .Values.network }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: luxd
|
||||
lux-network: {{ .Values.network }}
|
||||
spec:
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
terminationGracePeriodSeconds: 30
|
||||
{{- if .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml .Values.imagePullSecrets | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: luxd
|
||||
initContainers:
|
||||
- name: init-plugins
|
||||
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
runAsGroup: 0
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
set -e
|
||||
IDX=${HOSTNAME##*-}
|
||||
echo "=== Init for luxd-${IDX} ({{ .Values.network }}) ==="
|
||||
|
||||
{{- if .Values.initMode.clearData }}
|
||||
echo "=== Clearing data for re-genesis (preserving RLP) ==="
|
||||
rm -rf /data/chainData /data/db /data/genesis.bytes /data/.bootstrap-*-import.done /data/.seeded
|
||||
echo "Data cleared"
|
||||
{{- else }}
|
||||
echo "=== Preserving existing data ==="
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.seedRestore.enabled }}
|
||||
# ─── Seed Restore (idempotent via marker file) ───
|
||||
SEED_MARKER="{{ .Values.seedRestore.markerPath | default "/data/.seeded" }}"
|
||||
if [ ! -f "$SEED_MARKER" ]; then
|
||||
echo "[SEED] Starting seed restore..."
|
||||
{{- if eq .Values.seedRestore.sourceType "ObjectStore" }}
|
||||
echo "[SEED] Downloading snapshot from {{ .Values.seedRestore.objectStoreUrl }}..."
|
||||
curl -fL --retry 5 --retry-delay 5 -o /tmp/snapshot.tar.zst "{{ .Values.seedRestore.objectStoreUrl }}" && \
|
||||
echo "[SEED] Extracting snapshot..." && \
|
||||
(zstd -d /tmp/snapshot.tar.zst -o /tmp/snapshot.tar 2>/dev/null || cp /tmp/snapshot.tar.zst /tmp/snapshot.tar) && \
|
||||
tar xf /tmp/snapshot.tar -C /data && \
|
||||
rm -f /tmp/snapshot.tar /tmp/snapshot.tar.zst && \
|
||||
touch "$SEED_MARKER" && \
|
||||
echo "[SEED] Restore complete" || echo "[SEED] Restore failed"
|
||||
{{- else if or (eq .Values.seedRestore.sourceType "PVCClone") (eq .Values.seedRestore.sourceType "VolumeSnapshot") }}
|
||||
# PVC was created from snapshot/clone
|
||||
if [ -d "/data/db" ] || [ -d "/data/chainData" ]; then
|
||||
echo "[SEED] Data found from PVC clone/snapshot"
|
||||
touch "$SEED_MARKER"
|
||||
else
|
||||
echo "[SEED] WARNING: PVC clone/snapshot has no data"
|
||||
fi
|
||||
{{- else }}
|
||||
echo "[SEED] No seed source configured, skipping"
|
||||
{{- end }}
|
||||
else
|
||||
echo "[SEED] Marker $SEED_MARKER exists, skipping restore"
|
||||
fi
|
||||
{{- end }}
|
||||
|
||||
# Set up plugins
|
||||
echo "=== Setting up plugins ==="
|
||||
mkdir -p /data/plugins
|
||||
# C-Chain EVM plugin
|
||||
cp /luxd/build/plugins/mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6 /data/plugins/
|
||||
# Subnet EVM plugin (same binary, different VM ID)
|
||||
cp /luxd/build/plugins/mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6 /data/plugins/ag3GReYPNuSR17rUP8acMdZipQBikdXNRKDyFszAysmy3vDXE
|
||||
|
||||
# Set up staking keys
|
||||
echo "=== Setting up staking keys ==="
|
||||
mkdir -p /data/staking
|
||||
cp /staking-keys/staker-${IDX}.crt /data/staking/staker.crt
|
||||
cp /staking-keys/staker-${IDX}.key /data/staking/staker.key
|
||||
if [ -f "/staking-keys/signer-${IDX}.key" ]; then
|
||||
cp /staking-keys/signer-${IDX}.key /data/staking/signer.key
|
||||
fi
|
||||
chown -R 1000:1000 /data/staking
|
||||
chmod 600 /data/staking/*
|
||||
echo "=== Init complete ==="
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: staking-keys
|
||||
mountPath: /staking-keys
|
||||
readOnly: true
|
||||
{{- if .Values.startupGate.enabled }}
|
||||
- name: startup-gate
|
||||
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
set -e
|
||||
POD_INDEX=${HOSTNAME##*-}
|
||||
MIN_PEERS={{ .Values.startupGate.minPeers | default 2 }}
|
||||
TIMEOUT={{ .Values.startupGate.timeoutSeconds | default 300 }}
|
||||
INTERVAL={{ .Values.startupGate.checkIntervalSeconds | default 5 }}
|
||||
DNS_PREFIX="luxd"
|
||||
DNS_SUFFIX="luxd-headless.{{ $namespace }}.svc.cluster.local"
|
||||
START=$(date +%s)
|
||||
|
||||
echo "[GATE] Waiting for $MIN_PEERS peers on staking port {{ $stakingPort }} (timeout ${TIMEOUT}s)..."
|
||||
|
||||
while true; do
|
||||
REACHABLE=0
|
||||
{{- range $i := until $replicas }}
|
||||
if [ "$POD_INDEX" != "{{ $i }}" ]; then
|
||||
if nc -z -w 2 "${DNS_PREFIX}-{{ $i }}.${DNS_SUFFIX}" {{ $stakingPort }} 2>/dev/null; then
|
||||
REACHABLE=$((REACHABLE + 1))
|
||||
fi
|
||||
fi
|
||||
{{- end }}
|
||||
|
||||
if [ "$REACHABLE" -ge "$MIN_PEERS" ]; then
|
||||
echo "[GATE] $REACHABLE peers reachable, proceeding"
|
||||
break
|
||||
fi
|
||||
|
||||
ELAPSED=$(( $(date +%s) - START ))
|
||||
if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
|
||||
echo "[GATE] WARNING: Timeout after ${ELAPSED}s with only $REACHABLE peers. Starting anyway."
|
||||
break
|
||||
fi
|
||||
|
||||
echo "[GATE] $REACHABLE/$MIN_PEERS peers reachable (${ELAPSED}/${TIMEOUT}s), waiting ${INTERVAL}s..."
|
||||
sleep "$INTERVAL"
|
||||
done
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: luxd
|
||||
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
command: ["/bin/sh", "/scripts/startup.sh"]
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ $httpPort }}
|
||||
- name: staking
|
||||
containerPort: {{ $stakingPort }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /ext/health
|
||||
port: {{ $httpPort }}
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 15
|
||||
failureThreshold: 5
|
||||
timeoutSeconds: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ext/health
|
||||
port: {{ $httpPort }}
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
timeoutSeconds: 5
|
||||
startupProbe:
|
||||
tcpSocket:
|
||||
port: {{ $httpPort }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
failureThreshold: 60
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 10 }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: genesis
|
||||
mountPath: /genesis
|
||||
readOnly: true
|
||||
- name: startup-script
|
||||
mountPath: /scripts
|
||||
- name: staking-keys
|
||||
mountPath: /staking-keys
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: genesis
|
||||
configMap:
|
||||
name: {{ .Values.genesis.configMapName }}
|
||||
- name: startup-script
|
||||
configMap:
|
||||
name: luxd-startup
|
||||
defaultMode: 0755
|
||||
- name: staking-keys
|
||||
secret:
|
||||
secretName: {{ .Values.staking.secretName }}
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
storageClassName: {{ .Values.storage.storageClass }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.storage.size }}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Hanzo devnet overrides — sovereign L1, networkID == chainID == 36964
|
||||
network: devnet
|
||||
|
||||
replicas: 5
|
||||
|
||||
bootstrap:
|
||||
useHostnames: true
|
||||
nodeIDs: []
|
||||
|
||||
# Devnet — sybil protection off for development
|
||||
consensus:
|
||||
sybilProtectionEnabled: false
|
||||
allowPrivateIPs: true
|
||||
|
||||
chainTracking:
|
||||
trackAllChains: true
|
||||
trackedChains: []
|
||||
aliases:
|
||||
- hanzo
|
||||
|
||||
chainConfigs:
|
||||
enabled: false
|
||||
blockchainIDs: []
|
||||
@@ -0,0 +1,29 @@
|
||||
# Hanzo mainnet overrides — sovereign L1, networkID == chainID == 36963
|
||||
# Fresh genesis (no historical RLP import; this is Hanzo's own primary network,
|
||||
# NOT a subnet on Lux and NOT the Lux C-Chain).
|
||||
network: mainnet
|
||||
|
||||
replicas: 5
|
||||
|
||||
# Validator NodeIDs are derived from Hanzo's staking keys (KMS) at provisioning —
|
||||
# populated by the operator, never committed. See PROVISIONING.md.
|
||||
bootstrap:
|
||||
useHostnames: true
|
||||
nodeIDs: []
|
||||
|
||||
# Mainnet requires sybil protection
|
||||
consensus:
|
||||
sybilProtectionEnabled: true
|
||||
|
||||
# Hanzo tracks its own chains
|
||||
chainTracking:
|
||||
trackAllChains: true
|
||||
trackedChains: []
|
||||
aliases:
|
||||
- hanzo
|
||||
|
||||
# C-Chain EVM config is carried in genesis/mainnet.json (chainId 36963 + aiMiningConfig).
|
||||
# The C-Chain blockchain ID is derived at network creation (PROVISIONING.md).
|
||||
chainConfigs:
|
||||
enabled: false
|
||||
blockchainIDs: []
|
||||
@@ -0,0 +1,22 @@
|
||||
# Hanzo testnet overrides — sovereign L1, networkID == chainID == 36962
|
||||
network: testnet
|
||||
|
||||
replicas: 5
|
||||
|
||||
bootstrap:
|
||||
useHostnames: true
|
||||
nodeIDs: []
|
||||
|
||||
# Testnet requires sybil protection
|
||||
consensus:
|
||||
sybilProtectionEnabled: true
|
||||
|
||||
chainTracking:
|
||||
trackAllChains: true
|
||||
trackedChains: []
|
||||
aliases:
|
||||
- hanzo
|
||||
|
||||
chainConfigs:
|
||||
enabled: false
|
||||
blockchainIDs: []
|
||||
@@ -0,0 +1,184 @@
|
||||
# Hanzo sovereign-L1 node Helm chart values
|
||||
# Base config (used as defaults, override per network with values-{network}.yaml)
|
||||
#
|
||||
# SOVEREIGN-L1 MODEL: Hanzo's primary networkID EQUALS its EVM chainID (one ID per
|
||||
# env), unlike Lux (networkID 1/2/3, chainID 96369/68/70). Hanzo does NOT run on the
|
||||
# Lux C-Chain (96369) — that is Lux-network only.
|
||||
|
||||
network: devnet
|
||||
|
||||
# Network-specific settings
|
||||
# networkID MUST match genesis.json networkID (node validates at startup)
|
||||
# Hanzo: networkID == chainID (mainnet 36963 / testnet 36962 / devnet 36964)
|
||||
# Because these are CUSTOM network IDs (not the well-known 1/2/3), upgradeConfig
|
||||
# below MUST be enabled — the node has no built-in upgrade schedule for them.
|
||||
networks:
|
||||
mainnet:
|
||||
networkID: 36963
|
||||
chainID: 36963
|
||||
# cChainID is DERIVED at network creation (blake-hash of the C-Chain genesis +
|
||||
# createChainTx) — populated by the operator at provisioning (see PROVISIONING.md).
|
||||
cChainID: ""
|
||||
httpPort: 9630
|
||||
stakingPort: 9631
|
||||
namespace: hanzo-mainnet
|
||||
testnet:
|
||||
networkID: 36962
|
||||
chainID: 36962
|
||||
cChainID: ""
|
||||
httpPort: 9640
|
||||
stakingPort: 9641
|
||||
namespace: hanzo-testnet
|
||||
devnet:
|
||||
networkID: 36964
|
||||
chainID: 36964
|
||||
cChainID: ""
|
||||
httpPort: 9650
|
||||
stakingPort: 9651
|
||||
namespace: hanzo-devnet
|
||||
|
||||
# 5 replicas for production consensus
|
||||
replicas: 5
|
||||
|
||||
image:
|
||||
# luxd node image (Hanzo runs the same binary; the network is defined by genesis).
|
||||
# Upstream OSS node is acceptable here per the registry rules.
|
||||
repository: registry.digitalocean.com/hanzo/bootnode
|
||||
tag: luxd-v1.23.14
|
||||
pullPolicy: Always
|
||||
|
||||
imagePullSecrets:
|
||||
- name: registry-hanzo
|
||||
|
||||
# Bootstrap configuration
|
||||
# nodeIDs are DERIVED from Hanzo's validator staking keys (KMS) at provisioning —
|
||||
# they are NOT committed here. The operator populates them from the hanzo-staking
|
||||
# secret it generates (see PROVISIONING.md). Empty = first-genesis bring-up.
|
||||
bootstrap:
|
||||
nodeIDs: []
|
||||
useHostnames: true
|
||||
externalIPs: []
|
||||
rlpImport:
|
||||
enabled: false
|
||||
baseUrl: ""
|
||||
rlpFilename: ""
|
||||
multiPart: false
|
||||
parts: []
|
||||
minHeight: 1
|
||||
timeout: 7200
|
||||
|
||||
# Consensus settings (5-node network)
|
||||
consensus:
|
||||
sampleSize: 5
|
||||
quorumSize: 4
|
||||
sybilProtectionEnabled: false
|
||||
requireValidatorToConnect: false
|
||||
allowPrivateIPs: true
|
||||
|
||||
# Chain tracking — Hanzo's own sovereign network
|
||||
chainTracking:
|
||||
trackAllChains: true
|
||||
trackedChains: []
|
||||
aliases:
|
||||
- hanzo
|
||||
|
||||
# Deployed chain configs (blockchain IDs for EVM chain config directories)
|
||||
chainConfigs:
|
||||
enabled: false
|
||||
blockchainIDs: []
|
||||
evmConfig: '{"admin-api-enabled":true,"eth-apis":["eth","eth-filter","net","web3","internal-eth","internal-blockchain","internal-transaction","internal-tx-pool","internal-account","internal-personal","debug","internal-debug","admin"],"rpc-gas-cap":50000000,"rpc-tx-fee-cap":100,"pruning-enabled":false,"allow-unfinalized-queries":true,"allow-unprotected-txs":true,"log-level":"info","state-sync-enabled":false,"allow-missing-tries":true}'
|
||||
|
||||
# Init container mode
|
||||
initMode:
|
||||
clearData: false
|
||||
|
||||
# Startup gating: wait for peers before starting the node
|
||||
startupGate:
|
||||
enabled: true
|
||||
minPeers: 2
|
||||
timeoutSeconds: 300
|
||||
checkIntervalSeconds: 5
|
||||
|
||||
# Seed/snapshot restore configuration
|
||||
seedRestore:
|
||||
enabled: false
|
||||
sourceType: None
|
||||
objectStoreUrl: ""
|
||||
markerPath: "/data/.seeded"
|
||||
|
||||
# Upgrade strategy
|
||||
upgradeStrategy:
|
||||
type: OnDelete
|
||||
maxUnavailable: 1
|
||||
|
||||
# Health policy
|
||||
healthPolicy:
|
||||
requireInboundValidators: false
|
||||
minInbound: 1
|
||||
gracePeriodSeconds: 300
|
||||
maxHeightSkew: 10
|
||||
|
||||
# Staking keys — generated by the operator from KMS (NEVER committed).
|
||||
staking:
|
||||
secretName: hanzo-staking
|
||||
|
||||
# Resources
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "2"
|
||||
|
||||
# Storage (DOKS hanzo-k8s)
|
||||
storage:
|
||||
size: 100Gi
|
||||
storageClass: do-block-storage
|
||||
|
||||
# Node exposure
|
||||
nodeServices:
|
||||
enabled: true
|
||||
type: LoadBalancer
|
||||
annotations: {}
|
||||
|
||||
# Logging
|
||||
logLevel: info
|
||||
|
||||
# Database
|
||||
dbType: badgerdb
|
||||
|
||||
# API settings
|
||||
api:
|
||||
adminEnabled: true
|
||||
metricsEnabled: true
|
||||
indexEnabled: true
|
||||
httpAllowedHosts: "*"
|
||||
|
||||
# Upgrade config override — REQUIRED for Hanzo (custom network IDs 36963/62/64 have
|
||||
# no built-in upgrade schedule). All phases activated at genesis (past timestamps).
|
||||
upgradeConfig:
|
||||
enabled: true
|
||||
content: |
|
||||
{
|
||||
"apricotPhase1Time": "2020-12-05T05:00:00Z",
|
||||
"apricotPhase2Time": "2020-12-05T05:00:00Z",
|
||||
"apricotPhase3Time": "2020-12-05T05:00:00Z",
|
||||
"apricotPhase4Time": "2020-12-05T05:00:00Z",
|
||||
"apricotPhase4MinPChainHeight": 0,
|
||||
"apricotPhase5Time": "2020-12-05T05:00:00Z",
|
||||
"apricotPhasePre6Time": "2020-12-05T05:00:00Z",
|
||||
"apricotPhase6Time": "2020-12-05T05:00:00Z",
|
||||
"apricotPhasePost6Time": "2020-12-05T05:00:00Z",
|
||||
"banffTime": "2020-12-05T05:00:00Z",
|
||||
"cortinaTime": "2020-12-05T05:00:00Z",
|
||||
"cortinaXChainStopVertexID": "11111111111111111111111111111111LpoYY",
|
||||
"durangoTime": "2020-12-05T05:00:00Z",
|
||||
"etnaTime": "2020-12-05T05:00:00Z",
|
||||
"fortunaTime": "2020-12-05T05:00:00Z",
|
||||
"graniteTime": "9999-12-01T00:00:00Z"
|
||||
}
|
||||
|
||||
# Genesis
|
||||
genesis:
|
||||
configMapName: hanzo-genesis
|
||||
Reference in New Issue
Block a user