Files
node/scripts/publish_plugin_set.sh
T
zeekayandHanzo Dev d5eff75934 genesis: install the M-Chain plugin under the vmID genesis actually declares
The build installed M-Chain's plugin binary as
tGVBwRxpmD2aFdg3iYjgRvrCe8Jcmq9UNKxyHMus2NZ8WcD8t — the CB58 of the
retired `thresholdvm` identifier — while the genesis builder declares the
chain with constants.MPCVMID (qCURact1n41FcoNBch8iMVBwc9AWie48D118ZNJ5tBdWrvryS).
The plugin registry resolves a CreateChainTx's vmID to an implementation
by filename, so the two never met: M-Chain was declared in genesis and no
node could start it. The Dockerfile comment had already been renamed to
"mpcvm" without the CB58 being recomputed, which is why it read as correct.

Fixes all five sites (plugin build target, the build-verify list, the
runtime COPY, the comment, and publish_plugin_set.sh) and adds
genesis/builder/mchain_test.go, which pins the vmID literally and asserts
M-Chain is present in the height-0 chain set for mainnet, testnet and
local. A vmID is an immutable one-way door once a chain is created with
it, so it is now covered by a test rather than by five copies of a string.

Bumps luxfi/genesis to v1.16.3, where mchain.json states its quorum as
"policy": "7-of-10" instead of a bare mpcThreshold that reads as the
signer count to an operator and as the polynomial degree to a library.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-25 14:53:07 -07:00

139 lines
5.8 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# publish_plugin_set.sh — publish the VM plugin SET to S3 from a node image.
#
# The ONE canonical way to produce lux release artifact #2 (the plugin set the
# operator's plugin-fetch init container downloads per the LuxNetwork CR
# pluginSource). It is decoupled from, and DRY with, artifact #1 (the node
# image): the plugins are ALREADY built + baked into the image by the single
# Dockerfile multi-stage build, so this step EXTRACTS them — it never compiles
# them a second time. One source of truth, two distribution surfaces.
#
# NO GitHub. Runs on any fleet host or DOKS Job that has `crane` (or docker) +
# the MinIO client `mc`. Typically invoked by the same arcd runner that just
# built + pushed the image (it has the image locally), or on demand from a
# fleet host against the published image.
#
# Usage:
# publish_plugin_set.sh <image-ref> <s3-dest> [mc-alias]
#
# <image-ref> Fully-qualified node image, e.g. ghcr.io/luxfi/node:v1.30.41
# (digest-pinned forms are accepted and preferred for releases).
# <s3-dest> Bucket/prefix WITHOUT scheme, e.g.
# lux-plugins-testnet/v1.3.5 (matches the LuxNetwork CR
# pluginSource.bucket = s3://lux-plugins-testnet/v1.3.5/).
# [mc-alias] Configured `mc` alias for the target MinIO/S3 (default: lux).
# Configure once: `mc alias set lux <endpoint> <key> <secret>`.
#
# The plugin set is the 12 VM-ID files under /luxd/build/plugins/ in the image.
# A SHA256SUMS manifest (objectKey<two-spaces>sha256, one per line) is generated
# and uploaded alongside — the operator plugin-fetch init container verifies
# each plugin's sha256 against the CR (fail-closed on mismatch), so the manifest
# is also the source for the CR's pluginSource.vmPlugins[].sha256 fields.
#
# Idempotent: re-running with the same image + dest re-uploads byte-identical
# objects. A pluginset version is immutable by convention — bump <s3-dest> for a
# new release, never overwrite a published prefix that a live network points at.
set -euo pipefail
IMAGE_REF="${1:-}"
S3_DEST="${2:-}"
MC_ALIAS="${3:-lux}"
if [[ -z "${IMAGE_REF}" || -z "${S3_DEST}" ]]; then
echo "usage: $0 <image-ref> <s3-dest> [mc-alias]" >&2
echo " e.g. $0 ghcr.io/luxfi/node:v1.30.41 lux-plugins-testnet/v1.3.5 lux" >&2
exit 2
fi
# Canonical plugin set: the 12 VM-ID filenames the Dockerfile writes to
# /luxd/build/plugins/. Kept in lockstep with the Dockerfile plugin stages.
PLUGINS=(
mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6 # evm (C-Chain EVM, 0x9999)
mDVT5EWMumBp3LCqvKwuyZQeY1VXr1jvjGNAt8nL4UFiXvqXr # dexvm (native D-Chain DEX)
juFxSrbCM4wszxddKepj1GWwmrn9YgN1g4n3VUWPpRo9JjERA # aivm
kMhHABHM8j4bH94MCc4rsTNdo5E9En37MMyiujk4WdNxgXFsY # bridgevm
nZQm4Dmg1rjX18rb8maL9gamYyXPf1xCvF7ymWzxp6a1nSQTt # graphvm
oR6tnZHezwogyf9fRnomNXC9ojwCEBAU6jdUzpgy2PB1tD7fM # identityvm
pJJCSV7hHYVY6TUZwR8qUPAfuhX8JLb2C1AzNSezrYNbgau8M # keyvm
r5m1ujrmXxVcQetG3CQfuDLHp2RHKh6vCDaFgBRQfUcTZh7eS # oraclevm
ry9Sg8rZdT26iEKvJDmC2wkESs4SDKgZEhk5BgLSwg1EpcNug # quantumvm
sP6dLqrrBR9w3soP18fbJ3YzZecZdD7DDdfH2cFhhLq7Hy9bz # relayvm
qCURact1n41FcoNBch8iMVBwc9AWie48D118ZNJ5tBdWrvryS # mpcvm
vv3qPfyTVXZ5ArRZA9Jh4hbYDTBe43f7sgQg4CHfNg1rnnvX9 # zkvm
)
WORK="$(mktemp -d)"
trap 'rm -rf "${WORK}"' EXIT
OUT="${WORK}/plugins"
mkdir -p "${OUT}"
echo "==> extracting plugin set from ${IMAGE_REF}"
# Build the tar member list (paths inside the image rootfs).
members=()
for id in "${PLUGINS[@]}"; do
members+=("luxd/build/plugins/${id}")
done
# Prefer crane (no docker daemon needed). Fall back to docker create+cp.
if command -v crane >/dev/null 2>&1; then
crane export "${IMAGE_REF}" - | tar -x -C "${WORK}" "${members[@]}"
elif command -v docker >/dev/null 2>&1; then
cid="$(docker create "${IMAGE_REF}")"
for id in "${PLUGINS[@]}"; do
docker cp "${cid}:/luxd/build/plugins/${id}" "${OUT}/${id}"
done
docker rm -f "${cid}" >/dev/null
# normalize layout to match crane's export path
mkdir -p "${WORK}/luxd/build/plugins"
mv "${OUT}"/* "${WORK}/luxd/build/plugins/" 2>/dev/null || true
OUT="${WORK}/luxd/build/plugins"
else
echo "FATAL: neither crane nor docker is available to extract the image" >&2
exit 1
fi
# crane export wrote to ${WORK}/luxd/build/plugins; converge OUT to it.
[[ -d "${WORK}/luxd/build/plugins" ]] && OUT="${WORK}/luxd/build/plugins"
echo "==> generating SHA256SUMS manifest"
( cd "${OUT}"
: > SHA256SUMS
for id in "${PLUGINS[@]}"; do
[[ -s "${id}" ]] || { echo "FATAL: plugin ${id} missing from image ${IMAGE_REF}" >&2; exit 1; }
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "${id}" >> SHA256SUMS
else
printf '%s %s\n' "$(shasum -a 256 "${id}" | awk '{print $1}')" "${id}" >> SHA256SUMS
fi
done
)
echo "----- SHA256SUMS -----"
cat "${OUT}/SHA256SUMS"
echo "----------------------"
echo "==> uploading plugin set + manifest to s3://${S3_DEST}/ (alias ${MC_ALIAS})"
# Ensure the bucket exists (no-op if present); never deletes/overwrites siblings.
bucket="${S3_DEST%%/*}"
mc mb --ignore-existing "${MC_ALIAS}/${bucket}" >/dev/null 2>&1 || true
for id in "${PLUGINS[@]}"; do
mc cp "${OUT}/${id}" "${MC_ALIAS}/${S3_DEST}/"
done
mc cp "${OUT}/SHA256SUMS" "${MC_ALIAS}/${S3_DEST}/"
echo "==> verifying round-trip integrity (remote sha == local sha)"
fail=0
while read -r want id; do
got="$(mc cat "${MC_ALIAS}/${S3_DEST}/${id}" | { sha256sum 2>/dev/null || shasum -a 256; } | awk '{print $1}')"
if [[ "${got}" != "${want}" ]]; then
echo "MISMATCH ${id}: local ${want} != remote ${got}" >&2
fail=1
else
echo "ok ${id} ${got}"
fi
done < "${OUT}/SHA256SUMS"
[[ "${fail}" -eq 0 ]] || { echo "FATAL: upload integrity check failed" >&2; exit 1; }
echo "==> published plugin set to s3://${S3_DEST}/ (${#PLUGINS[@]} plugins + SHA256SUMS)"