Peer persistence hooks: capture before stop, reinject after start
Clients (esp. discovery-disabled OP-stack EL) lose peers across recreate. Generic peer-capture/peer-reinject hooks + force-recreate wiring restore the peerset from local peer-state via in-network admin_addPeer. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
138
peer-reinject.sh
Executable file
138
peer-reinject.sh
Executable file
@@ -0,0 +1,138 @@
|
||||
#!/bin/bash
|
||||
# peer-reinject.sh — generic .after-start hook: re-seed peers from peer-capture state.
|
||||
# Uses admin_addStaticPeer for geth, admin_addPeer otherwise (connect-peers.sh).
|
||||
# In-network vantage (rpc_chains). Idempotent; best-effort (never fails the restart).
|
||||
#
|
||||
# Usage: peer-reinject.sh <compose-path>
|
||||
set -euo pipefail
|
||||
|
||||
COMPOSE_PATH="${1:-}"
|
||||
if [ -z "$COMPOSE_PATH" ]; then
|
||||
echo "Usage: $0 <compose-path>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BASEPATH="$(cd "$(dirname "$0")" && pwd)"
|
||||
STATE_DIR="$BASEPATH/peer-state"
|
||||
SAFE="$(echo "$COMPOSE_PATH" | sed 's|[^a-zA-Z0-9._-]|_|g')"
|
||||
STATE="$STATE_DIR/${SAFE}.json"
|
||||
CURL_IMAGE="${PEER_CURL_IMAGE:-curlimages/curl:8.5.0}"
|
||||
LOG="${PEER_HOOK_LOG:-/var/log/peer-hooks.log}"
|
||||
|
||||
log() { echo "$(date '+%F %T') peer-reinject[$COMPOSE_PATH]: $*" | tee -a "$LOG" >&2; }
|
||||
|
||||
case "$COMPOSE_PATH" in
|
||||
*nitro*|*zksync*|*zk-sync*)
|
||||
log "skip (nitro/zksync)"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ ! -f "$STATE" ]; then
|
||||
log "no capture state at $STATE — nothing to reinject"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CLIENT_TYPE="$(jq -r '.client_type // "unknown"' "$STATE" 2>/dev/null || echo unknown)"
|
||||
mapfile -t ENODES < <(jq -r '.peers[].id // empty' "$STATE" 2>/dev/null | grep -E '^enode://' | head -n 50 || true)
|
||||
if [ "${#ENODES[@]}" -eq 0 ]; then
|
||||
log "no enodes in $STATE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
BASE="$(basename "$COMPOSE_PATH" | sed 's/--.*//')"
|
||||
EL=""
|
||||
# wait briefly for container after recreate
|
||||
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
||||
while read -r n; do
|
||||
[ -z "$n" ] && continue
|
||||
case "$n" in rpc-*) ;; *) continue ;; esac
|
||||
case "$n" in *-node-*|*-relay-*) continue ;; esac
|
||||
core="$(echo "$n" | sed -E 's/^rpc-(.+)-[0-9]+$/\1/')"
|
||||
if [ "$BASE" = "$core" ] || [[ "$BASE" == "$core"-* ]]; then
|
||||
EL="$n"
|
||||
break
|
||||
fi
|
||||
done < <(docker ps --format '{{.Names}}')
|
||||
[ -n "$EL" ] && break
|
||||
sleep 3
|
||||
done
|
||||
|
||||
if [ -z "$EL" ]; then
|
||||
log "no execution container yet — giving up"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
IP="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$EL" 2>/dev/null || true)"
|
||||
if [ -z "$IP" ]; then
|
||||
log "no IP for $EL"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# wait for RPC
|
||||
for _ in 1 2 3 4 5 6 7 8 9 10 11 12; do
|
||||
docker rm -f peer-hook-probe >/dev/null 2>&1 || true
|
||||
docker image inspect "$CURL_IMAGE" >/dev/null 2>&1 || docker pull -q "$CURL_IMAGE" >/dev/null
|
||||
docker run -d --name peer-hook-probe --network rpc_chains "$CURL_IMAGE" sleep 180 >/dev/null
|
||||
if docker exec peer-hook-probe curl -s --max-time 5 -X POST \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{"jsonrpc":"2.0","method":"net_version","params":[],"id":1}' \
|
||||
"http://${IP}:8545" 2>/dev/null | grep -q result; then
|
||||
break
|
||||
fi
|
||||
docker rm -f peer-hook-probe >/dev/null 2>&1 || true
|
||||
sleep 5
|
||||
done
|
||||
|
||||
add_peer() {
|
||||
local enode="$1"
|
||||
local method
|
||||
if [ "$CLIENT_TYPE" = "geth" ]; then
|
||||
method="admin_addStaticPeer"
|
||||
else
|
||||
method="admin_addPeer"
|
||||
fi
|
||||
local payload
|
||||
payload="$(jq -nc --arg m "$method" --arg e "$enode" \
|
||||
'{jsonrpc:"2.0",method:$m,params:[$e],id:1}')"
|
||||
local resp
|
||||
resp="$(docker exec peer-hook-probe curl -s --max-time 8 -X POST \
|
||||
-H 'Content-Type: application/json' --data "$payload" \
|
||||
"http://${IP}:8545" 2>/dev/null || true)"
|
||||
if echo "$resp" | grep -qi 'method not found'; then
|
||||
if [ "$method" = "admin_addStaticPeer" ]; then
|
||||
method="admin_addPeer"
|
||||
else
|
||||
method="admin_addStaticPeer"
|
||||
fi
|
||||
payload="$(jq -nc --arg m "$method" --arg e "$enode" \
|
||||
'{jsonrpc:"2.0",method:$m,params:[$e],id:1}')"
|
||||
resp="$(docker exec peer-hook-probe curl -s --max-time 8 -X POST \
|
||||
-H 'Content-Type: application/json' --data "$payload" \
|
||||
"http://${IP}:8545" 2>/dev/null || true)"
|
||||
fi
|
||||
if echo "$resp" | grep -q '"result":true'; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
ADDED=0
|
||||
FAIL=0
|
||||
for enode in "${ENODES[@]}"; do
|
||||
if add_peer "$enode"; then
|
||||
ADDED=$((ADDED + 1))
|
||||
else
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# post count
|
||||
COUNT="$(docker exec peer-hook-probe curl -s --max-time 8 -X POST \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{"jsonrpc":"2.0","method":"admin_peers","params":[],"id":1}' \
|
||||
"http://${IP}:8545" 2>/dev/null | jq -r '.result | length' 2>/dev/null || echo '?')"
|
||||
|
||||
docker rm -f peer-hook-probe >/dev/null 2>&1 || true
|
||||
log "reinjected added=$ADDED fail=$FAIL now_peers=$COUNT (from ${#ENODES[@]} enodes)"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user