Dogfood public bootstrap peers as secondary reinject seed.
Add bootstrap-peers.sh and fall back to stakesquid.eu/bootstrap when local peerset capture is empty; optional --seed-bootstrap on connect-peers. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
102
bootstrap-peers.sh
Executable file
102
bootstrap-peers.sh
Executable file
@@ -0,0 +1,102 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# bootstrap-peers.sh — fetch curated third-party bootstrap enodes for a network.
|
||||||
|
#
|
||||||
|
# Secondary seed source for peer-reinject / restore flows. Primary source remains
|
||||||
|
# the node's own peer-capture state. Same public JSON customers curl:
|
||||||
|
# https://stakesquid.eu/bootstrap/<network>.json
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# bootstrap-peers.sh <network> # print enodes (one per line)
|
||||||
|
# bootstrap-peers.sh <network> --json # print full JSON
|
||||||
|
# bootstrap-peers.sh --from-compose <path> # derive network from compose path
|
||||||
|
# bootstrap-peers.sh ... --dry-run # show URL + count, no failure on empty
|
||||||
|
#
|
||||||
|
# Env:
|
||||||
|
# BOOTSTRAP_BASE_URL default https://stakesquid.eu/bootstrap
|
||||||
|
# BOOTSTRAP_MAX max enodes to emit (default 25)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BASE_URL="${BOOTSTRAP_BASE_URL:-https://stakesquid.eu/bootstrap}"
|
||||||
|
MAX="${BOOTSTRAP_MAX:-25}"
|
||||||
|
DRY_RUN=false
|
||||||
|
JSON_OUT=false
|
||||||
|
NETWORK=""
|
||||||
|
COMPOSE=""
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 <network> [--json] [--dry-run]" >&2
|
||||||
|
echo " $0 --from-compose <compose-path> [--json] [--dry-run]" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--from-compose)
|
||||||
|
COMPOSE="${2:-}"; shift 2 || usage
|
||||||
|
;;
|
||||||
|
--json) JSON_OUT=true; shift ;;
|
||||||
|
--dry-run) DRY_RUN=true; shift ;;
|
||||||
|
-h|--help) usage ;;
|
||||||
|
-*)
|
||||||
|
echo "Unknown option: $1" >&2
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
NETWORK="$1"; shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
network_from_compose() {
|
||||||
|
local path="$1"
|
||||||
|
local base
|
||||||
|
base="$(basename "$path" | sed 's/--.*//')"
|
||||||
|
# strip client/variant suffixes (same idea as fleet-peers network_from_path)
|
||||||
|
for sep in -op-reth -op-geth -op-erigon -op-nethermind \
|
||||||
|
-reth -geth -erigon -erigon3 -besu -nethermind \
|
||||||
|
-bsc -nitro -l2geth -bor -external-node -gaiad \
|
||||||
|
-juno -rskj -avalanche -maru; do
|
||||||
|
case "$base" in
|
||||||
|
*"$sep"*) base="${base%%$sep*}"; break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
# common leftover: -pruned / -archive / -pebble / -path / -trace / -bonsai
|
||||||
|
base="$(echo "$base" | sed -E 's/-(pruned|archive|full|pebble|path|trace|bonsai|snap|hash).*//')"
|
||||||
|
echo "$base"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -n "$COMPOSE" ]]; then
|
||||||
|
NETWORK="$(network_from_compose "$COMPOSE")"
|
||||||
|
fi
|
||||||
|
if [[ -z "$NETWORK" ]]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
URL="${BASE_URL%/}/${NETWORK}.json"
|
||||||
|
TMP="$(mktemp)"
|
||||||
|
trap 'rm -f "$TMP"' EXIT
|
||||||
|
|
||||||
|
if ! curl -fsS --max-time 15 "$URL" -o "$TMP" 2>/dev/null; then
|
||||||
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
|
echo "bootstrap-peers: dry-run fetch failed for $URL" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "bootstrap-peers: fetch failed: $URL" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
COUNT="$(jq -r '.peers | length // 0' "$TMP" 2>/dev/null || echo 0)"
|
||||||
|
UPDATED="$(jq -r '.updated // empty' "$TMP" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
|
echo "bootstrap-peers: network=$NETWORK url=$URL peers=$COUNT updated=$UPDATED"
|
||||||
|
jq -r '.peers[:3][]? | " \(.stability.tier // "?") score=\(.stability.score // "?") n_vantage=\(.stability.n_vantage_nodes // "?") \(.enode[0:60])…"' "$TMP" 2>/dev/null || true
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$JSON_OUT" == true ]]; then
|
||||||
|
cat "$TMP"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
jq -r --argjson m "$MAX" '.peers[:$m][]?.enode // empty' "$TMP" 2>/dev/null | grep -E '^enode://' || true
|
||||||
@@ -9,6 +9,11 @@
|
|||||||
# target-compose-file: Optional. If provided, use this compose file for target node
|
# target-compose-file: Optional. If provided, use this compose file for target node
|
||||||
# If not provided, use the same compose file for both source and target
|
# If not provided, use the same compose file for both source and target
|
||||||
#
|
#
|
||||||
|
# Secondary seed (optional): --seed-bootstrap pulls third-party enodes from the
|
||||||
|
# public bootstrap API (https://stakesquid.eu/bootstrap/<network>.json) into the
|
||||||
|
# source node after the pairwise connect — same JSON customers use; own peerset
|
||||||
|
# remains primary via peer-reinject.sh / peer-capture.
|
||||||
|
#
|
||||||
# Exit codes:
|
# Exit codes:
|
||||||
# 0 - At least one node connected successfully (bidirectional or one-way)
|
# 0 - At least one node connected successfully (bidirectional or one-way)
|
||||||
# 1 - Failed to connect to both nodes (see output for details)
|
# 1 - Failed to connect to both nodes (see output for details)
|
||||||
@@ -65,6 +70,7 @@ fi
|
|||||||
|
|
||||||
TIMEOUT=5
|
TIMEOUT=5
|
||||||
DRY_RUN=false
|
DRY_RUN=false
|
||||||
|
SEED_BOOTSTRAP=false
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -76,6 +82,10 @@ while [[ $# -gt 0 ]]; do
|
|||||||
DRY_RUN=true
|
DRY_RUN=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--seed-bootstrap)
|
||||||
|
SEED_BOOTSTRAP=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown option: $1"
|
echo "Unknown option: $1"
|
||||||
usage
|
usage
|
||||||
@@ -600,6 +610,30 @@ fi
|
|||||||
echo -e "${CYAN}--- Summary ---${NC}"
|
echo -e "${CYAN}--- Summary ---${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# Optional secondary seed from public bootstrap list (after pairwise connect).
|
||||||
|
if [[ "$SEED_BOOTSTRAP" == true ]]; then
|
||||||
|
echo -e "${CYAN}--- Bootstrap secondary seed ---${NC}"
|
||||||
|
if [[ -x "$BASEPATH/bootstrap-peers.sh" ]]; then
|
||||||
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
|
"$BASEPATH/bootstrap-peers.sh" --from-compose "$COMPOSE_FILE" --dry-run || true
|
||||||
|
else
|
||||||
|
mapfile -t BOOT_ENODES < <("$BASEPATH/bootstrap-peers.sh" --from-compose "$COMPOSE_FILE" 2>/dev/null | grep -E '^enode://' | head -n 25 || true)
|
||||||
|
BOOT_OK=0
|
||||||
|
for be in "${BOOT_ENODES[@]:-}"; do
|
||||||
|
[[ -z "$be" ]] && continue
|
||||||
|
RESULT=$(add_static_peer "$SOURCE_URL" "$be" "$SOURCE_CLIENT")
|
||||||
|
if check_rpc_success "$RESULT"; then
|
||||||
|
BOOT_OK=$((BOOT_OK + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo -e " Added ${GREEN}${BOOT_OK}${NC}/${#BOOT_ENODES[@]} bootstrap enodes to source"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}bootstrap-peers.sh not found — skip${NC}"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$DRY_RUN" == true ]]; then
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
echo -e "${GREEN}Dry run completed - no changes made${NC}"
|
echo -e "${GREEN}Dry run completed - no changes made${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -3,7 +3,14 @@
|
|||||||
# Uses admin_addStaticPeer for geth, admin_addPeer otherwise (connect-peers.sh).
|
# Uses admin_addStaticPeer for geth, admin_addPeer otherwise (connect-peers.sh).
|
||||||
# In-network vantage (rpc_chains). Idempotent; best-effort (never fails the restart).
|
# In-network vantage (rpc_chains). Idempotent; best-effort (never fails the restart).
|
||||||
#
|
#
|
||||||
|
# Seed order (one path for us and customers):
|
||||||
|
# 1. Own persisted peerset (peer-state/<compose>.json from peer-capture)
|
||||||
|
# 2. Public bootstrap list https://stakesquid.eu/bootstrap/<network>.json
|
||||||
|
# (third-party enodes only — same JSON customers curl)
|
||||||
|
#
|
||||||
# Usage: peer-reinject.sh <compose-path>
|
# Usage: peer-reinject.sh <compose-path>
|
||||||
|
# Env: PEER_BOOTSTRAP=0 to disable bootstrap fallback
|
||||||
|
# BOOTSTRAP_BASE_URL to override (default https://stakesquid.eu/bootstrap)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
COMPOSE_PATH="${1:-}"
|
COMPOSE_PATH="${1:-}"
|
||||||
@@ -18,6 +25,7 @@ SAFE="$(echo "$COMPOSE_PATH" | sed 's|[^a-zA-Z0-9._-]|_|g')"
|
|||||||
STATE="$STATE_DIR/${SAFE}.json"
|
STATE="$STATE_DIR/${SAFE}.json"
|
||||||
CURL_IMAGE="${PEER_CURL_IMAGE:-curlimages/curl:8.5.0}"
|
CURL_IMAGE="${PEER_CURL_IMAGE:-curlimages/curl:8.5.0}"
|
||||||
LOG="${PEER_HOOK_LOG:-/var/log/peer-hooks.log}"
|
LOG="${PEER_HOOK_LOG:-/var/log/peer-hooks.log}"
|
||||||
|
USE_BOOTSTRAP="${PEER_BOOTSTRAP:-1}"
|
||||||
|
|
||||||
log() { echo "$(date '+%F %T') peer-reinject[$COMPOSE_PATH]: $*" | tee -a "$LOG" >&2; }
|
log() { echo "$(date '+%F %T') peer-reinject[$COMPOSE_PATH]: $*" | tee -a "$LOG" >&2; }
|
||||||
|
|
||||||
@@ -28,15 +36,35 @@ case "$COMPOSE_PATH" in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
if [ ! -f "$STATE" ]; then
|
CLIENT_TYPE="unknown"
|
||||||
log "no capture state at $STATE — nothing to reinject"
|
ENODES=()
|
||||||
exit 0
|
SOURCE="none"
|
||||||
|
|
||||||
|
if [ -f "$STATE" ]; then
|
||||||
|
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[@]}" -gt 0 ]; then
|
||||||
|
SOURCE="peerset"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Secondary seed: public bootstrap API (same files customers use).
|
||||||
|
if [ "${#ENODES[@]}" -eq 0 ] && [ "$USE_BOOTSTRAP" != "0" ]; then
|
||||||
|
if [ -x "$BASEPATH/bootstrap-peers.sh" ]; then
|
||||||
|
mapfile -t ENODES < <("$BASEPATH/bootstrap-peers.sh" --from-compose "$COMPOSE_PATH" 2>/dev/null | grep -E '^enode://' | head -n 25 || true)
|
||||||
|
if [ "${#ENODES[@]}" -gt 0 ]; then
|
||||||
|
SOURCE="bootstrap"
|
||||||
|
log "no local peerset — using bootstrap fallback (${#ENODES[@]} enodes)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
fi
|
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
|
if [ "${#ENODES[@]}" -eq 0 ]; then
|
||||||
log "no enodes in $STATE"
|
if [ ! -f "$STATE" ]; then
|
||||||
|
log "no capture state at $STATE and bootstrap empty — nothing to reinject"
|
||||||
|
else
|
||||||
|
log "no enodes in $STATE and bootstrap empty"
|
||||||
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -64,6 +92,11 @@ if [ -z "$EL" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
IP="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$EL" 2>/dev/null || true)"
|
IP="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$EL" 2>/dev/null || true)"
|
||||||
|
# Prefer rpc_chains IP when present
|
||||||
|
RPC_IP="$(docker inspect -f '{{(index .NetworkSettings.Networks "rpc_chains").IPAddress}}' "$EL" 2>/dev/null || true)"
|
||||||
|
if [ -n "$RPC_IP" ] && [ "$RPC_IP" != "<no value>" ]; then
|
||||||
|
IP="$RPC_IP"
|
||||||
|
fi
|
||||||
if [ -z "$IP" ]; then
|
if [ -z "$IP" ]; then
|
||||||
log "no IP for $EL"
|
log "no IP for $EL"
|
||||||
exit 0
|
exit 0
|
||||||
@@ -134,5 +167,5 @@ COUNT="$(docker exec peer-hook-probe curl -s --max-time 8 -X POST \
|
|||||||
"http://${IP}:8545" 2>/dev/null | jq -r '.result | length' 2>/dev/null || echo '?')"
|
"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
|
docker rm -f peer-hook-probe >/dev/null 2>&1 || true
|
||||||
log "reinjected added=$ADDED fail=$FAIL now_peers=$COUNT (from ${#ENODES[@]} enodes)"
|
log "reinjected source=$SOURCE added=$ADDED fail=$FAIL now_peers=$COUNT (from ${#ENODES[@]} enodes)"
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
Reference in New Issue
Block a user