A node's C-Chain is a plugin, copied onto the volume by an init container at boot. Those containers named their own image, and drifted: devnet staged from v1.36.25 under a v1.36.123 node, mainnet from v1.36.88 under v1.36.116, testnet from v1.34.0 — a tag with no image at all — under v1.36.123. Between 28 and 98 releases apart. So every roll moved luxd and left the C-Chain built from an old evm. The plugin files are rewritten on each boot, which makes them look current by any timestamp you can check; they were written from a stale source. That is how four fleets ran a two-month-old plugin under a current binary, and why a state-sync default shipped in the node image did not reach the chain that needed it. An artifact check cannot see this. The image is internally consistent — the deployment swaps the plugin out afterwards — so it is a fact about the manifest and is checked there: scripts/check_image_match.sh refuses a StatefulSet whose init containers stage from anything other than the image its node runs. Co-authored-by: Hanzo Dev <[email protected]>
52 lines
2.0 KiB
Bash
Executable File
52 lines
2.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# One image per pod: whatever stages the plugins must be what runs them.
|
|
#
|
|
# A node's C-Chain is a plugin, and the plugin is copied onto the volume by an
|
|
# init container at boot. When that container names a different image than the
|
|
# node, a roll moves the binary and leaves the plugin built from whatever the
|
|
# init container was pinned at — freshly written each boot, from a stale source,
|
|
# which reads as current by every mtime you can look at.
|
|
#
|
|
# The image is internally consistent, so a check on the artifact cannot see this:
|
|
# the deployment swaps the plugin out afterwards. It is a manifest fact, so it is
|
|
# checked here.
|
|
#
|
|
# scripts/check_image_match.sh [manifest ...] # default: every luxd StatefulSet
|
|
set -euo pipefail
|
|
|
|
cd "$(git rev-parse --show-toplevel)"
|
|
if [ $# -gt 0 ]; then
|
|
set -- "$@"
|
|
else
|
|
set -- $(grep -rl 'kind: StatefulSet' k8s/ deploy/ 2>/dev/null | sort)
|
|
fi
|
|
|
|
fail=0
|
|
for f in "$@"; do
|
|
python3 - "$f" <<'PY' || fail=1
|
|
import sys, yaml
|
|
path = sys.argv[1]
|
|
for doc in yaml.safe_load_all(open(path)):
|
|
if not doc or doc.get('kind') != 'StatefulSet':
|
|
continue
|
|
spec = doc['spec']['template']['spec']
|
|
name = doc['metadata']['name']
|
|
# Only containers that stage into the shared volume matter. Anything not
|
|
# built from the node image is a different concern and is left alone.
|
|
node = [c['image'] for c in spec.get('containers', []) if 'luxfi/node' in c.get('image', '')]
|
|
init = [(c.get('name'), c['image']) for c in spec.get('initContainers', []) if 'luxfi/node' in c.get('image', '')]
|
|
if not node or not init:
|
|
continue
|
|
want = node[0]
|
|
for cname, image in init:
|
|
if image != want:
|
|
print(f"FAIL {path} [{name}]: initContainer {cname} stages from")
|
|
print(f" {image}")
|
|
print(f" but the node runs")
|
|
print(f" {want}")
|
|
sys.exit(1)
|
|
print(f"ok {path} [{name}]: {len(init)} init container(s) match the node image")
|
|
PY
|
|
done
|
|
exit $fail
|