Files
proxmox/scripts/besu/collect-enodes-from-all-besu-nodes.sh
defiQUG fbda1b4beb
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
docs: Ledger Live integration, contract deploy learnings, NEXT_STEPS updates
- ADD_CHAIN138_TO_LEDGER_LIVE: Ledger form done; public code review repo bis-innovations/LedgerLive; init/push commands
- CONTRACT_DEPLOYMENT_RUNBOOK: Chain 138 gas price 1 gwei, 36-addr check, TransactionMirror workaround
- CONTRACT_*: AddressMapper, MirrorManager deployed 2026-02-12; 36-address on-chain check
- NEXT_STEPS_FOR_YOU: Ledger done; steps completable now (no LAN); run-completable-tasks-from-anywhere
- MASTER_INDEX, OPERATOR_OPTIONAL, SMART_CONTRACTS_INVENTORY_SIMPLE: updates
- LEDGER_BLOCKCHAIN_INTEGRATION_COMPLETE: bis-innovations/LedgerLive reference

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 15:46:57 -08:00

224 lines
9.6 KiB
Bash

#!/usr/bin/env bash
# Collect enode from each of the 32 Besu nodes and regenerate static-nodes.json and
# permissions-nodes.toml with 32 unique entries (canonical IPs). Fixes duplicate enode (2400/2401).
#
# Usage: bash scripts/besu/collect-enodes-from-all-besu-nodes.sh [--dry-run] [--missing-only]
# --missing-only Only try to collect from VMIDs whose IP is not yet in static-nodes.json (fix failures only).
# Output: config/besu-node-lists/static-nodes.json and permissions-nodes.toml (backups as *.bak).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
source "${PROJECT_ROOT}/config/ip-addresses.conf" 2>/dev/null || true
DRY_RUN=false
MISSING_ONLY=false
for arg in "${@:-}"; do
[[ "$arg" == "--dry-run" ]] && DRY_RUN=true
[[ "$arg" == "--missing-only" ]] && MISSING_ONLY=true
done
STATIC_FILE="${PROJECT_ROOT}/config/besu-node-lists/static-nodes.json"
PERM_FILE="${PROJECT_ROOT}/config/besu-node-lists/permissions-nodes.toml"
SSH_OPTS="-o ConnectTimeout=8 -o StrictHostKeyChecking=accept-new"
# All 32 Besu VMIDs in stable order (validators, sentries, RPCs)
BESU_VMIDS=(1000 1001 1002 1003 1004 1500 1501 1502 1503 1504 1505 1506 1507 1508 2101 2102 2201 2301 2303 2304 2305 2306 2400 2401 2402 2403 2500 2501 2502 2503 2504 2505)
# VMID -> Proxmox host
declare -A HOST_BY_VMID
for v in 1000 1001 1002 1500 1501 1502 2101 2500 2501 2502 2503 2504 2505; do HOST_BY_VMID[$v]="${PROXMOX_R630_01:-192.168.11.11}"; done
for v in 2201 2303 2401; do HOST_BY_VMID[$v]="${PROXMOX_R630_02:-192.168.11.12}"; done
for v in 1003 1004 1503 1504 1505 1506 1507 1508 2102 2301 2304 2305 2306 2400 2402 2403; do HOST_BY_VMID[$v]="${PROXMOX_ML110:-192.168.11.10}"; done
# VMID -> canonical IP (all 32)
declare -A IP_BY_VMID
IP_BY_VMID[1000]=192.168.11.100
IP_BY_VMID[1001]=192.168.11.101
IP_BY_VMID[1002]=192.168.11.102
IP_BY_VMID[1003]=192.168.11.103
IP_BY_VMID[1004]=192.168.11.104
IP_BY_VMID[1500]=192.168.11.150
IP_BY_VMID[1501]=192.168.11.151
IP_BY_VMID[1502]=192.168.11.152
IP_BY_VMID[1503]=192.168.11.153
IP_BY_VMID[1504]=192.168.11.154
IP_BY_VMID[1505]=192.168.11.213
IP_BY_VMID[1506]=192.168.11.214
IP_BY_VMID[1507]=192.168.11.244
IP_BY_VMID[1508]=192.168.11.245
IP_BY_VMID[2101]=192.168.11.211
IP_BY_VMID[2102]=192.168.11.212
IP_BY_VMID[2201]=192.168.11.221
IP_BY_VMID[2301]=192.168.11.232
IP_BY_VMID[2303]=192.168.11.233
IP_BY_VMID[2304]=192.168.11.234
IP_BY_VMID[2305]=192.168.11.235
IP_BY_VMID[2306]=192.168.11.236
IP_BY_VMID[2400]=192.168.11.240
IP_BY_VMID[2401]=192.168.11.241
IP_BY_VMID[2402]=192.168.11.242
IP_BY_VMID[2403]=192.168.11.243
IP_BY_VMID[2500]=192.168.11.172
IP_BY_VMID[2501]=192.168.11.173
IP_BY_VMID[2502]=192.168.11.174
IP_BY_VMID[2503]=192.168.11.246
IP_BY_VMID[2504]=192.168.11.247
IP_BY_VMID[2505]=192.168.11.248
get_enode() {
local vmid="$1"
local host="${HOST_BY_VMID[$vmid]:-}"
local ip="${IP_BY_VMID[$vmid]:-}"
[[ -z "$host" || -z "$ip" ]] && return 1
# 1) admin_nodeInfo (RPC)
local enode
enode=$(ssh $SSH_OPTS "root@$host" "pct exec $vmid -- curl -s -m 3 -X POST -H 'Content-Type: application/json' --data '{\"jsonrpc\":\"2.0\",\"method\":\"admin_nodeInfo\",\"params\":[],\"id\":1}' http://127.0.0.1:8545 2>/dev/null" 2>/dev/null | jq -r '.result.enode // empty' 2>/dev/null)
if [[ -n "$enode" ]]; then
echo "$enode" | sed "s/@[0-9.]*:/@$ip:/"
return 0
fi
# 2) besu public-key export: outputs "0x<128 hex>"; build enode://<128hex>@<ip>:30303
local pubkey
pubkey=$(ssh $SSH_OPTS "root@$host" "pct exec $vmid -- bash -c '/opt/besu/bin/besu public-key export --node-private-key-file=/data/besu/key 2>/dev/null || /opt/besu/bin/besu public-key export --node-private-key-file=/data/besu/nodekey 2>/dev/null'" 2>/dev/null | grep -oE '0x[0-9a-fA-F]{128}' | head -1 | sed 's/^0x//')
if [[ -n "$pubkey" && ${#pubkey} -eq 128 ]]; then
echo "enode://${pubkey}@${ip}:30303"
return 0
fi
# 3) For 1505-1508 on ml110: node may lack Besu binary; export key via helper 1504 (which has Besu)
if [[ "$host" == "${PROXMOX_ML110:-192.168.11.10}" ]] && [[ "$vmid" =~ ^(1505|1506|1507|1508)$ ]]; then
pubkey=$(ssh $SSH_OPTS "root@$host" "pct exec $vmid -- cat /data/besu/key 2>/dev/null | head -1 > /tmp/key${vmid}.$$ 2>/dev/null && pct push 1504 /tmp/key${vmid}.$$ /tmp/key${vmid} 2>/dev/null && pct exec 1504 -- /opt/besu/bin/besu public-key export --node-private-key-file=/tmp/key${vmid} 2>/dev/null; rm -f /tmp/key${vmid}.$$" 2>/dev/null | grep -oE '0x[0-9a-fA-F]{128}' | head -1 | sed 's/^0x//')
if [[ -n "$pubkey" && ${#pubkey} -eq 128 ]]; then
echo "enode://${pubkey}@${ip}:30303"
return 0
fi
fi
# 4) For 2501-2505 on r630-01: node may lack Besu binary; export key via helper 2500 (which has Besu)
if [[ "$host" == "${PROXMOX_R630_01:-192.168.11.11}" ]] && [[ "$vmid" =~ ^(2501|2502|2503|2504|2505)$ ]]; then
pubkey=$(ssh $SSH_OPTS "root@$host" "pct exec $vmid -- cat /data/besu/key 2>/dev/null | head -1 > /tmp/key${vmid}.$$ 2>/dev/null && pct push 2500 /tmp/key${vmid}.$$ /tmp/key${vmid} 2>/dev/null && pct exec 2500 -- /opt/besu/bin/besu public-key export --node-private-key-file=/tmp/key${vmid} 2>/dev/null; rm -f /tmp/key${vmid}.$$" 2>/dev/null | grep -oE '0x[0-9a-fA-F]{128}' | head -1 | sed 's/^0x//')
if [[ -n "$pubkey" && ${#pubkey} -eq 128 ]]; then
echo "enode://${pubkey}@${ip}:30303"
return 0
fi
fi
return 1
}
# Load existing IP -> enode from current static-nodes.json (so --missing-only knows what's already there and merge uses it)
declare -A EXISTING_BY_IP
if [[ -f "$STATIC_FILE" ]]; then
while IFS= read -r enode; do
[[ -z "$enode" ]] && continue
ip=$(echo "$enode" | sed -n 's|enode://[a-fA-F0-9]*@\([0-9.]*\):.*|\1|p')
[[ -n "$ip" ]] && EXISTING_BY_IP[$ip]="$enode"
done < <(jq -r '.[]' "$STATIC_FILE" 2>/dev/null)
fi
[[ ${#EXISTING_BY_IP[@]} -eq 0 && -f "${STATIC_FILE}.bak" ]] && while IFS= read -r enode; do
[[ -z "$enode" ]] && continue
ip=$(echo "$enode" | sed -n 's|enode://[a-fA-F0-9]*@\([0-9.]*\):.*|\1|p')
[[ -n "$ip" ]] && EXISTING_BY_IP[$ip]="$enode"
done < <(jq -r '.[]' "${STATIC_FILE}.bak" 2>/dev/null)
# When --missing-only: only VMIDs whose canonical IP is not yet in the list
VMIDS_TO_TRY=()
if $MISSING_ONLY; then
for vmid in "${BESU_VMIDS[@]}"; do
ip="${IP_BY_VMID[$vmid]:-}"
[[ -z "$ip" ]] && continue
[[ -z "${EXISTING_BY_IP[$ip]:-}" ]] && VMIDS_TO_TRY+=( "$vmid" )
done
echo "Missing-only: collecting from ${#VMIDS_TO_TRY[@]} VMIDs not in current list (${VMIDS_TO_TRY[*]:-none})."
[[ ${#VMIDS_TO_TRY[@]} -eq 0 ]] && echo "All 32 IPs already present. Nothing to collect." && exit 0
else
VMIDS_TO_TRY=( "${BESU_VMIDS[@]}" )
echo "Collecting enodes from 32 Besu nodes..."
fi
declare -A COLLECTED_BY_VMID
collect_ok=0
collect_fail=0
declare -A SEEN_NODE_ID
for vmid in "${VMIDS_TO_TRY[@]}"; do
enode=$(get_enode "$vmid" 2>/dev/null) || true
if [[ -n "$enode" ]]; then
node_id=$(echo "$enode" | sed -n 's|enode://\([a-fA-F0-9]*\)@.*|\1|p')
if [[ -n "${SEEN_NODE_ID[$node_id]:-}" ]]; then
echo " VMID $vmid: duplicate node_id (same as VMID ${SEEN_NODE_ID[$node_id]}) — using anyway with IP ${IP_BY_VMID[$vmid]}" >&2
else
SEEN_NODE_ID[$node_id]=$vmid
fi
COLLECTED_BY_VMID[$vmid]="$enode"
((collect_ok++)) || true
echo " $vmid ${IP_BY_VMID[$vmid]} OK"
else
((collect_fail++)) || true
echo " $vmid ${IP_BY_VMID[$vmid]} FAIL (no enode)"
fi
done
echo ""
echo "Collected $collect_ok enodes, $collect_fail failed."
# Merge: for each of 32 VMIDs use collected (if any) else existing enode for that IP; dedupe by node_id
declare -a FINAL_ENODES
declare -a MISSING_VMIDS
declare -A USED_NODE_ID
for vmid in "${BESU_VMIDS[@]}"; do
ip="${IP_BY_VMID[$vmid]:-}"
[[ -z "$ip" ]] && continue
enode=""
if [[ -n "${COLLECTED_BY_VMID[$vmid]:-}" ]]; then
enode="${COLLECTED_BY_VMID[$vmid]}"
elif [[ -n "${EXISTING_BY_IP[$ip]:-}" ]]; then
existing="${EXISTING_BY_IP[$ip]}"
node_id=$(echo "$existing" | sed -n 's|enode://\([a-fA-F0-9]*\)@.*|\1|p')
if [[ -z "${USED_NODE_ID[$node_id]:-}" ]]; then
enode=$(echo "$existing" | sed "s/@[0-9.]*:/@$ip:/")
fi
fi
if [[ -n "$enode" ]]; then
node_id=$(echo "$enode" | sed -n 's|enode://\([a-fA-F0-9]*\)@.*|\1|p')
USED_NODE_ID[$node_id]=1
FINAL_ENODES+=( "$enode" )
else
MISSING_VMIDS+=( "$vmid" )
fi
done
echo "Merged total: ${#FINAL_ENODES[@]} unique enodes (target 32)."
if [[ ${#FINAL_ENODES[@]} -lt 32 && ${#MISSING_VMIDS[@]} -gt 0 ]]; then
echo "WARNING: ${#FINAL_ENODES[@]}/32. Missing VMIDs: ${MISSING_VMIDS[*]}. Re-run when those nodes are up or add enodes manually." >&2
fi
if [[ ${#FINAL_ENODES[@]} -eq 0 ]]; then
echo "No enodes to write. Aborting." >&2
exit 1
fi
# Build static-nodes.json (JSON array)
if ! $DRY_RUN; then
cp -a "$STATIC_FILE" "${STATIC_FILE}.bak" 2>/dev/null || true
cp -a "$PERM_FILE" "${PERM_FILE}.bak" 2>/dev/null || true
# JSON: ["enode://...", "enode://...", ...]
printf '%s\n' "${FINAL_ENODES[@]}" | jq -R . | jq -s . > "$STATIC_FILE"
# TOML: nodes-allowlist= [ "enode://...", ... ]
{
echo '# Node Permissioning — SINGLE SOURCE OF TRUTH for all Besu nodes'
echo '# Must match config/besu-node-lists/static-nodes.json and be deployed to every node.'
echo "# Generated by scripts/besu/collect-enodes-from-all-besu-nodes.sh — ${#FINAL_ENODES[@]} nodes, no duplicates."
echo ''
echo 'nodes-allowlist=['
for e in "${FINAL_ENODES[@]}"; do
echo " \"$e\","
done | sed '$ s/,$//'
echo ']'
} > "$PERM_FILE"
echo "Wrote $STATIC_FILE and $PERM_FILE (${#FINAL_ENODES[@]} entries)"
else
echo "[dry-run] Would write ${#FINAL_ENODES[@]} enodes to static-nodes.json and permissions-nodes.toml"
fi