Files
ethereum-rpc-docker/connect-peers.sh
2025-12-16 12:58:40 +07:00

409 lines
12 KiB
Bash
Executable File

#!/bin/bash
# Connect two blockchain nodes bidirectionally
# Adds each node as a static peer to the other
#
# Usage: connect-peers.sh <compose-file> <target-host> [target-compose-file] [options]
# compose-file: Path to compose file (without .yml) for source node
# target-host: Target host identifier (e.g., "2" for 2.stakesquid.eu)
# 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
#
# Exit codes:
# 0 - Both nodes connected successfully
# 1 - Failed to connect (see output for details)
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
usage() {
echo "Usage: $0 <compose-file> <target-host> [target-compose-file] [options]"
echo ""
echo "Connects two nodes by adding each other as static peers."
echo ""
echo "Arguments:"
echo " compose-file: Path to compose file (without .yml) for source node"
echo " target-host: Target host identifier (e.g., '2' for 2.stakesquid.eu)"
echo " target-compose-file: Optional. If provided, use this compose file for target node"
echo ""
echo "Examples:"
echo " $0 ethereum/geth/mainnet 2"
echo " $0 ethereum/geth/mainnet 2 polygon/bor/mainnet"
echo " $0 ethereum/geth/mainnet 2 --timeout 5"
echo ""
echo "Options:"
echo " --timeout, -t RPC timeout in seconds (default: 5)"
echo " --dry-run, -n Show what would be done without making changes"
exit 1
}
if [[ $# -lt 2 ]]; then
usage
fi
BASEPATH="$(dirname "$0")"
source "$BASEPATH/.env" 2>/dev/null || {
echo -e "${RED}Error: Could not source $BASEPATH/.env${NC}" >&2
exit 1
}
COMPOSE_FILE="$1"
TARGET_HOST="$2"
TARGET_COMPOSE_FILE="${3:-$COMPOSE_FILE}" # Use source compose file if not provided
# Shift arguments - if 3rd arg exists and is not an option, it's the target compose file
if [[ $# -ge 3 ]] && [[ "$3" != --* ]]; then
shift 3
else
shift 2
fi
TIMEOUT=5
DRY_RUN=false
while [[ $# -gt 0 ]]; do
case "$1" in
--timeout|-t)
TIMEOUT="$2"
shift 2
;;
--dry-run|-n)
DRY_RUN=true
shift
;;
*)
echo "Unknown option: $1"
usage
;;
esac
done
# Check if DOMAIN is set
if [ -z "${DOMAIN:-}" ]; then
echo -e "${RED}Error: DOMAIN variable not found in $BASEPATH/.env${NC}" >&2
exit 1
fi
# Function to extract RPC path from compose file
extract_rpc_path() {
local compose_path="$1"
local full_path="$BASEPATH/${compose_path}.yml"
if [ ! -f "$full_path" ]; then
echo "Error: Compose file not found: $full_path" >&2
return 1
fi
# Get all services from compose file
services=$(cat "$full_path" | yaml2json - 2>/dev/null | jq -r '.services | keys | .[]' 2>/dev/null)
if [ -z "$services" ]; then
echo "Error: No services found in compose file: $full_path" >&2
return 1
fi
# Find the first service with a stripprefix.prefixes label
for service in $services; do
labels=($(cat "$full_path" | yaml2json - 2>/dev/null | jq -r ".services[\"$service\"].labels[]?" 2>/dev/null))
for label in "${labels[@]}"; do
if [[ "$label" == *"stripprefix.prefixes"* ]]; then
# Extract path from label
# Format examples:
# prefixes=/plume-mainnet-archive
# prefixes=`/plume-mainnet-archive`
# prefixes="/plume-mainnet-archive"
path=$(echo "$label" | sed -n 's/.*prefixes=\([^ `"]*\).*/\1/p')
# Remove backticks and quotes if present
path=$(echo "$path" | sed 's|`||g' | sed 's|"||g' | sed "s|'||g")
# Ensure path starts with /
if [[ ! "$path" =~ ^/ ]]; then
path="/$path"
fi
# Remove trailing slash if present
path=$(echo "$path" | sed 's|/$||')
if [ -n "$path" ] && [ "$path" != "/" ]; then
echo "$path"
return 0
fi
fi
done
done
echo "Error: Could not extract RPC path from compose file: $full_path" >&2
return 1
}
# Extract RPC paths
SOURCE_RPC_PATH=$(extract_rpc_path "$COMPOSE_FILE")
if [ $? -ne 0 ]; then
exit 1
fi
TARGET_RPC_PATH=$(extract_rpc_path "$TARGET_COMPOSE_FILE")
if [ $? -ne 0 ]; then
exit 1
fi
# Construct URLs
SOURCE_URL="https://${DOMAIN}${SOURCE_RPC_PATH}"
TARGET_URL="https://${TARGET_HOST}.stakesquid.eu${TARGET_RPC_PATH}"
echo -e "${CYAN}======================================${NC}"
echo -e "${CYAN}Node Peer Connector${NC}"
echo -e "${CYAN}======================================${NC}"
echo ""
echo -e "Source compose: ${YELLOW}${COMPOSE_FILE}${NC}"
echo -e "Target compose: ${YELLOW}${TARGET_COMPOSE_FILE}${NC}"
echo -e "Target host: ${YELLOW}${TARGET_HOST}.stakesquid.eu${NC}"
echo ""
echo -e "Source URL: ${YELLOW}${SOURCE_URL}${NC}"
echo -e "Target URL: ${YELLOW}${TARGET_URL}${NC}"
echo -e "Timeout: ${YELLOW}${TIMEOUT}s${NC}"
if [[ "$DRY_RUN" == true ]]; then
echo -e "Mode: ${YELLOW}DRY RUN${NC}"
fi
echo ""
# Function to get node info
get_node_info() {
local url="$1"
local response
response=$(curl -s -X POST "$url" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"admin_nodeInfo","params":[],"id":1}' \
--connect-timeout "$TIMEOUT" 2>/dev/null) || {
echo ""
return 1
}
echo "$response"
}
# Function to extract enode from nodeInfo response
extract_enode() {
local response="$1"
echo "$response" | grep -oP '"enode"\s*:\s*"\K[^"]+' | head -1
}
# Function to extract node name
extract_name() {
local response="$1"
echo "$response" | grep -oP '"name"\s*:\s*"\K[^"]+' | head -1
}
# Function to add static peer
add_static_peer() {
local url="$1"
local enode="$2"
local response
response=$(curl -s -X POST "$url" \
-H "Content-Type: application/json" \
-d "{\"jsonrpc\":\"2.0\",\"method\":\"admin_addStaticPeer\",\"params\":[\"$enode\"],\"id\":1}" \
--connect-timeout "$TIMEOUT" 2>/dev/null) || {
echo ""
return 1
}
echo "$response"
}
# Function to check if already peers
check_already_peers() {
local url="$1"
local pubkey="$2"
local response
response=$(curl -s -X POST "$url" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"admin_peers","params":[],"id":1}' \
--connect-timeout "$TIMEOUT" 2>/dev/null) || {
echo "error"
return 1
}
if echo "$response" | grep -qi "$pubkey"; then
echo "connected"
else
echo "not_connected"
fi
}
# Get source node info
echo -e "${CYAN}--- Fetching Node Info ---${NC}"
echo ""
echo -n "Source node info... "
SOURCE_INFO=$(get_node_info "$SOURCE_URL")
if [[ -z "$SOURCE_INFO" ]] || [[ "$SOURCE_INFO" =~ "error" && ! "$SOURCE_INFO" =~ "result" ]]; then
echo -e "${RED}FAILED${NC}"
echo -e "${RED}Could not get nodeInfo from source. Is admin API enabled?${NC}"
exit 1
fi
echo -e "${GREEN}OK${NC}"
SOURCE_ENODE=$(extract_enode "$SOURCE_INFO")
SOURCE_NAME=$(extract_name "$SOURCE_INFO")
SOURCE_PUBKEY=$(echo "$SOURCE_ENODE" | sed -E 's|enode://([^@]+)@.*|\1|')
if [[ -z "$SOURCE_ENODE" ]]; then
echo -e "${RED}Could not extract enode from source${NC}"
exit 1
fi
echo -e " Name: ${GREEN}${SOURCE_NAME}${NC}"
echo -e " Enode: ${GREEN}${SOURCE_ENODE:0:60}...${NC}"
echo ""
# Get target node info
echo -n "Target node info... "
TARGET_INFO=$(get_node_info "$TARGET_URL")
if [[ -z "$TARGET_INFO" ]] || [[ "$TARGET_INFO" =~ "error" && ! "$TARGET_INFO" =~ "result" ]]; then
echo -e "${RED}FAILED${NC}"
echo -e "${RED}Could not get nodeInfo from target. Is admin API enabled?${NC}"
exit 1
fi
echo -e "${GREEN}OK${NC}"
TARGET_ENODE=$(extract_enode "$TARGET_INFO")
TARGET_NAME=$(extract_name "$TARGET_INFO")
TARGET_PUBKEY=$(echo "$TARGET_ENODE" | sed -E 's|enode://([^@]+)@.*|\1|')
if [[ -z "$TARGET_ENODE" ]]; then
echo -e "${RED}Could not extract enode from target${NC}"
exit 1
fi
echo -e " Name: ${GREEN}${TARGET_NAME}${NC}"
echo -e " Enode: ${GREEN}${TARGET_ENODE:0:60}...${NC}"
echo ""
# Check if same node
if [[ "$SOURCE_PUBKEY" == "$TARGET_PUBKEY" ]]; then
echo -e "${RED}Error: Source and target are the same node!${NC}"
exit 1
fi
# Check existing connections
echo -e "${CYAN}--- Checking Existing Connections ---${NC}"
echo ""
echo -n "Source -> Target: "
SOURCE_HAS_TARGET=$(check_already_peers "$SOURCE_URL" "$TARGET_PUBKEY")
if [[ "$SOURCE_HAS_TARGET" == "connected" ]]; then
echo -e "${GREEN}already connected${NC}"
else
echo -e "${YELLOW}not connected${NC}"
fi
echo -n "Target -> Source: "
TARGET_HAS_SOURCE=$(check_already_peers "$TARGET_URL" "$SOURCE_PUBKEY")
if [[ "$TARGET_HAS_SOURCE" == "connected" ]]; then
echo -e "${GREEN}already connected${NC}"
else
echo -e "${YELLOW}not connected${NC}"
fi
echo ""
# Add peers
echo -e "${CYAN}--- Adding Static Peers ---${NC}"
echo ""
ERRORS=0
# Add target's enode to source
echo -n "Adding target to source... "
if [[ "$SOURCE_HAS_TARGET" == "connected" ]]; then
echo -e "${YELLOW}skipped (already connected)${NC}"
elif [[ "$DRY_RUN" == true ]]; then
echo -e "${CYAN}dry run${NC}"
echo -e " Would run: admin_addStaticPeer(\"${TARGET_ENODE:0:50}...\")"
else
RESULT=$(add_static_peer "$SOURCE_URL" "$TARGET_ENODE")
if [[ "$RESULT" =~ "true" ]]; then
echo -e "${GREEN}OK${NC}"
else
echo -e "${RED}FAILED${NC}"
echo -e " Response: $RESULT"
((ERRORS++))
fi
fi
# Add source's enode to target
echo -n "Adding source to target... "
if [[ "$TARGET_HAS_SOURCE" == "connected" ]]; then
echo -e "${YELLOW}skipped (already connected)${NC}"
elif [[ "$DRY_RUN" == true ]]; then
echo -e "${CYAN}dry run${NC}"
echo -e " Would run: admin_addStaticPeer(\"${SOURCE_ENODE:0:50}...\")"
else
RESULT=$(add_static_peer "$TARGET_URL" "$SOURCE_ENODE")
if [[ "$RESULT" =~ "true" ]]; then
echo -e "${GREEN}OK${NC}"
else
echo -e "${RED}FAILED${NC}"
echo -e " Response: $RESULT"
((ERRORS++))
fi
fi
echo ""
# Verify connection (wait a moment for handshake)
if [[ "$DRY_RUN" == false ]] && [[ "$SOURCE_HAS_TARGET" != "connected" || "$TARGET_HAS_SOURCE" != "connected" ]]; then
echo -e "${CYAN}--- Verifying Connection ---${NC}"
echo ""
echo -n "Waiting for handshake"
for i in {1..5}; do
echo -n "."
sleep 1
done
echo ""
echo ""
echo -n "Source -> Target: "
SOURCE_HAS_TARGET=$(check_already_peers "$SOURCE_URL" "$TARGET_PUBKEY")
if [[ "$SOURCE_HAS_TARGET" == "connected" ]]; then
echo -e "${GREEN}connected${NC}"
else
echo -e "${YELLOW}pending${NC}"
fi
echo -n "Target -> Source: "
TARGET_HAS_SOURCE=$(check_already_peers "$TARGET_URL" "$SOURCE_PUBKEY")
if [[ "$TARGET_HAS_SOURCE" == "connected" ]]; then
echo -e "${GREEN}connected${NC}"
else
echo -e "${YELLOW}pending${NC}"
fi
echo ""
fi
# Summary
echo -e "${CYAN}--- Summary ---${NC}"
echo ""
if [[ $ERRORS -eq 0 ]]; then
if [[ "$DRY_RUN" == true ]]; then
echo -e "${GREEN}Dry run completed - no changes made${NC}"
else
echo -e "${GREEN}Nodes connected successfully${NC}"
echo ""
echo "Both nodes should now discover each other's peers via the"
echo "devp2p discovery protocol. Give it a few minutes to propagate."
fi
echo ""
exit 0
else
echo -e "${RED}Connection failed with $ERRORS error(s)${NC}"
echo ""
exit 1
fi