catchup: per-protocol dispatch for bitcoin, cosmos, and ripple
Extend catchup.sh with protocol-aware branches (bitcoin height-gain, cosmos CometBFT timestamp via timestamp.sh, ripple validated-ledger seq) and explicit degradation for closed RPC / unsupported protocols. Reuse sync-status/check-health idioms for protocol resolution and parsing. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
493
catchup.sh
493
catchup.sh
@@ -25,13 +25,502 @@ s_to_human_readable() {
|
||||
seconds=$((seconds % 3600))
|
||||
local minutes=$((seconds / 60))
|
||||
seconds=$((seconds % 60))
|
||||
|
||||
|
||||
printf "%d days, %02d hours, %02d minutes, %02d seconds\n" $days $hours $minutes $seconds
|
||||
}
|
||||
|
||||
BASEPATH="$(dirname "$0")"
|
||||
source $BASEPATH/.env 2>/dev/null || true
|
||||
|
||||
compose_base="$1"
|
||||
seconds_to_measure=${2:-10}
|
||||
|
||||
if [ -z "$compose_base" ]; then
|
||||
echo -e "${RED}Error: compose file path (sans .yml) required${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
compose_yml="$BASEPATH/${compose_base}.yml"
|
||||
if [ ! -f "$compose_yml" ]; then
|
||||
echo -e "${RED}Error: compose file not found: $compose_yml${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$NO_SSL" ]; then
|
||||
PROTO="http"
|
||||
DOMAIN="${DOMAIN:-0.0.0.0}"
|
||||
else
|
||||
PROTO="https"
|
||||
fi
|
||||
|
||||
# Resolve traefik RPC path from compose labels (first non-blacklisted path).
|
||||
compose_rpc_path() {
|
||||
local pathlist path
|
||||
pathlist=$(grep -oP "stripprefix\.prefixes.*?/\K[^\"]+" "$compose_yml" 2>/dev/null)
|
||||
for path in $pathlist; do
|
||||
echo "$path"
|
||||
return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
compose_rpc_url() {
|
||||
local path
|
||||
path=$(compose_rpc_path) || return 1
|
||||
echo "$PROTO://$DOMAIN/$path"
|
||||
}
|
||||
|
||||
compose_chain_slug() {
|
||||
grep -oP '^\s*chain:\s*\K\S+' "$compose_yml" 2>/dev/null | head -1
|
||||
}
|
||||
|
||||
# Registry protocol via x-upstreams chain label; path-substring fallback for legacy composes.
|
||||
resolve_protocol() {
|
||||
local chain_slug protocol path
|
||||
chain_slug=$(compose_chain_slug)
|
||||
protocol=""
|
||||
if [ -n "$chain_slug" ]; then
|
||||
protocol=$($BASEPATH/reference-rpc-endpoint.sh --protocol "$chain_slug" 2>/dev/null) || protocol=""
|
||||
fi
|
||||
if [ -z "$protocol" ]; then
|
||||
path=$(compose_rpc_path 2>/dev/null) || path=""
|
||||
if echo "$path" | grep -qi "starknet"; then
|
||||
protocol="starknet"
|
||||
elif echo "$path" | grep -qi "aztec"; then
|
||||
protocol="aztec"
|
||||
elif echo "$path" | grep -qiE "bitcoin|digibyte|dogecoin|dash"; then
|
||||
protocol="bitcoin"
|
||||
elif echo "$path" | grep -qi "ripple"; then
|
||||
protocol="ripple"
|
||||
elif echo "$path" | grep -qiE "cosmos|babylon|haqq|gaiad|tac"; then
|
||||
protocol="cosmos"
|
||||
else
|
||||
protocol="eth"
|
||||
fi
|
||||
fi
|
||||
echo "$protocol"
|
||||
}
|
||||
|
||||
# Bitcoin-family RPC URL (traefik path) and basic-auth from x-upstreams (fallback username:password).
|
||||
bitcoin_rpc_url() {
|
||||
compose_rpc_url
|
||||
}
|
||||
|
||||
bitcoin_rpc_auth() {
|
||||
# Prefer creds from compose x-upstreams basic-auth; per-chain rpcauth may differ on-host.
|
||||
local user pass
|
||||
user=$(grep -A5 'basic-auth:' "$compose_yml" 2>/dev/null | grep -oP '^\s*username:\s*\K\S+' | head -1)
|
||||
pass=$(grep -A5 'basic-auth:' "$compose_yml" 2>/dev/null | grep -oP '^\s*password:\s*\K\S+' | head -1)
|
||||
[ -z "$user" ] && user="username"
|
||||
[ -z "$pass" ] && pass="password"
|
||||
echo "${user}:${pass}"
|
||||
}
|
||||
|
||||
bitcoin_rpc_call() {
|
||||
local method="$1"
|
||||
local params="${2:-[]}"
|
||||
local url auth
|
||||
url=$(bitcoin_rpc_url) || return 1
|
||||
auth=$(bitcoin_rpc_auth)
|
||||
curl -L --ipv4 -m 5 -s -u "$auth" -X POST -H "Content-Type: application/json" \
|
||||
--data "{\"jsonrpc\":\"1.0\",\"id\":1,\"method\":\"$method\",\"params\":$params}" "$url"
|
||||
}
|
||||
|
||||
bitcoin_parse_blocks_headers() {
|
||||
local info="$1"
|
||||
local blocks headers
|
||||
blocks=$(echo "$info" | jq -r '.result.blocks // empty' 2>/dev/null)
|
||||
headers=$(echo "$info" | jq -r '.result.headers // empty' 2>/dev/null)
|
||||
if [[ ! "$blocks" =~ ^[0-9]+$ ]] || [[ ! "$headers" =~ ^[0-9]+$ ]]; then
|
||||
return 1
|
||||
fi
|
||||
echo "$blocks $headers"
|
||||
}
|
||||
|
||||
bitcoin_reference_height() {
|
||||
local chain_slug="$1"
|
||||
local ref_urls ref_url resp height
|
||||
ref_urls=$($BASEPATH/reference-rpc-endpoint.sh --chain "$chain_slug" 2>/dev/null) || return 1
|
||||
ref_url=$(echo "$ref_urls" | awk '{print $1}')
|
||||
[ -z "$ref_url" ] && return 1
|
||||
resp=$(curl -L --ipv4 -m 5 -s -X POST -H "Content-Type: application/json" \
|
||||
--data '{"jsonrpc":"1.0","id":1,"method":"getblockcount","params":[]}' "$ref_url")
|
||||
if [ -z "$resp" ] || echo "$resp" | grep -qi 'Unknown network'; then
|
||||
return 2
|
||||
fi
|
||||
height=$(echo "$resp" | jq -r '.result // empty' 2>/dev/null)
|
||||
if [[ ! "$height" =~ ^[0-9]+$ ]]; then
|
||||
return 2
|
||||
fi
|
||||
echo "$height"
|
||||
}
|
||||
|
||||
bitcoin_pick_seed_peer() {
|
||||
local chain_slug="$1" path
|
||||
path=$(compose_rpc_path 2>/dev/null) || path=""
|
||||
if echo "$chain_slug $path" | grep -qi "digibyte"; then
|
||||
echo "seed.digibyte.co:12024"
|
||||
return 0
|
||||
fi
|
||||
if echo "$chain_slug $path" | grep -qi "dogecoin"; then
|
||||
# TODO: dogecoin wire seeds
|
||||
return 1
|
||||
fi
|
||||
if echo "$chain_slug $path" | grep -qi "dash"; then
|
||||
# TODO: dash wire seeds
|
||||
return 1
|
||||
fi
|
||||
# Bitcoin mainnet / testnet / cash family default
|
||||
echo "seed.bitcoin.sipa.be:8333"
|
||||
}
|
||||
|
||||
bitcoin_try_addnode() {
|
||||
local chain_slug="$1" peer resp
|
||||
peer=""
|
||||
local ref_urls ref_url pinfo
|
||||
ref_urls=$($BASEPATH/reference-rpc-endpoint.sh --chain "$chain_slug" 2>/dev/null) || ref_urls=""
|
||||
ref_url=$(echo "$ref_urls" | awk '{print $1}')
|
||||
if [ -n "$ref_url" ]; then
|
||||
pinfo=$(curl -L --ipv4 -m 5 -s -X POST -H "Content-Type: application/json" \
|
||||
--data '{"jsonrpc":"1.0","id":1,"method":"getpeerinfo","params":[]}' "$ref_url" 2>/dev/null)
|
||||
peer=$(echo "$pinfo" | jq -r '.result[0].addr // empty' 2>/dev/null)
|
||||
fi
|
||||
if [ -z "$peer" ] || [ "$peer" = "null" ]; then
|
||||
peer=$(bitcoin_pick_seed_peer "$chain_slug") || return 0
|
||||
fi
|
||||
resp=$(bitcoin_rpc_call "addnode" "[\"$peer\", \"onetry\"]")
|
||||
if echo "$resp" | jq -e '.error == null' >/dev/null 2>&1; then
|
||||
echo "Issued harmless addnode onetry to $peer"
|
||||
fi
|
||||
}
|
||||
|
||||
report_block_catchup_rate() {
|
||||
local gap_change="$1"
|
||||
local interval="$2"
|
||||
local block_time_ms="$3"
|
||||
local gap_remaining="$4"
|
||||
local label="${5:-blocks}"
|
||||
|
||||
local block_time_sec realtime_units effective catchup_rate
|
||||
block_time_sec=$(echo "scale=6; $block_time_ms / 1000" | bc)
|
||||
realtime_units=$(echo "scale=6; $interval / $block_time_sec" | bc)
|
||||
effective=$(echo "scale=6; $realtime_units - $gap_change" | bc)
|
||||
catchup_rate=$(echo "scale=3; $effective / $realtime_units" | bc)
|
||||
|
||||
echo "First measurement: $label behind = $((gap_remaining - gap_change)) $label"
|
||||
echo "Second measurement: $label behind = $gap_remaining $label"
|
||||
echo "Gap change in $interval seconds: $gap_change $label"
|
||||
echo "Calculated catchup rate: ${catchup_rate}x realtime"
|
||||
|
||||
if (( $(echo "$catchup_rate < 0" | bc -l) )); then
|
||||
local falling_behind_rate
|
||||
falling_behind_rate=$(echo "scale=2; -1 * $catchup_rate" | bc)
|
||||
echo -e "${RED}Node is FALLING BEHIND by ${falling_behind_rate}x realtime${NC}"
|
||||
echo -e "${RED}The gap increased by $gap_change $label over $interval seconds${NC}"
|
||||
return 1
|
||||
elif (( $(echo "$catchup_rate < 1" | bc -l) )); then
|
||||
echo -e "${YELLOW}WARNING: Node is processing at ${catchup_rate}x realtime${NC}"
|
||||
echo -e "${YELLOW}This is too slow - node will never catch up to chain head${NC}"
|
||||
return 2
|
||||
else
|
||||
echo -e "${GREEN}Node is processing at ${catchup_rate}x realtime${NC}"
|
||||
local time_to_catchup
|
||||
time_to_catchup=$(echo "scale=0; $gap_remaining * $block_time_sec / ($catchup_rate - 1)" | bc)
|
||||
echo -e "${GREEN}Estimated time until synced:${NC}"
|
||||
s_to_human_readable "$time_to_catchup"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
bitcoin_catchup() {
|
||||
local chain_slug="$1"
|
||||
local interval="$2"
|
||||
local block_time_ms info1 info2 blocks1 headers1 blocks2 headers2
|
||||
local ref1 ref2 use_pseudo=false gap1 gap2 gap_change
|
||||
|
||||
block_time_ms=$($BASEPATH/reference-rpc-endpoint.sh --block-time-ms "$chain_slug" 2>/dev/null) || block_time_ms="600000"
|
||||
|
||||
echo "Checking sync status for $compose_base (bitcoin-family)..."
|
||||
info1=$(bitcoin_rpc_call "getblockchaininfo")
|
||||
if [ -z "$info1" ]; then
|
||||
echo -e "${RED}rpc-not-open (expected mid-sync for stage-sync/bootstrap clients — avalanchego, erigon/reth stage-sync, statesync bootstraps); re-run at chainhead${NC}"
|
||||
exit 1
|
||||
fi
|
||||
read -r blocks1 headers1 < <(bitcoin_parse_blocks_headers "$info1") || {
|
||||
echo -e "${RED}rpc-not-open (expected mid-sync for stage-sync/bootstrap clients — avalanchego, erigon/reth stage-sync, statesync bootstraps); re-run at chainhead${NC}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
ref1=$(bitcoin_reference_height "$chain_slug")
|
||||
local ref_rc1=$?
|
||||
if [ "$ref_rc1" -eq 2 ]; then
|
||||
use_pseudo=true
|
||||
ref1=$headers1
|
||||
echo "Reference RPC unavailable (Unknown network) — using local headers as pseudo-reference"
|
||||
elif [ "$ref_rc1" -ne 0 ]; then
|
||||
use_pseudo=true
|
||||
ref1=$headers1
|
||||
echo "Reference RPC unavailable — using local headers as pseudo-reference"
|
||||
fi
|
||||
|
||||
gap1=$((ref1 - blocks1))
|
||||
echo "Current chain head is $gap1 blocks behind reference"
|
||||
s_to_human_readable $(echo "scale=0; $gap1 * $block_time_ms / 1000" | bc)
|
||||
|
||||
echo "Measuring progress over $interval seconds..."
|
||||
sleep "$interval"
|
||||
|
||||
info2=$(bitcoin_rpc_call "getblockchaininfo")
|
||||
if [ -z "$info2" ]; then
|
||||
echo -e "${RED}rpc-not-open (expected mid-sync for stage-sync/bootstrap clients — avalanchego, erigon/reth stage-sync, statesync bootstraps); re-run at chainhead${NC}"
|
||||
exit 1
|
||||
fi
|
||||
read -r blocks2 headers2 < <(bitcoin_parse_blocks_headers "$info2") || {
|
||||
echo -e "${RED}rpc-not-open (expected mid-sync for stage-sync/bootstrap clients — avalanchego, erigon/reth stage-sync, statesync bootstraps); re-run at chainhead${NC}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if $use_pseudo; then
|
||||
ref2=$headers2
|
||||
else
|
||||
ref2=$(bitcoin_reference_height "$chain_slug")
|
||||
local ref_rc2=$?
|
||||
if [ "$ref_rc2" -ne 0 ]; then
|
||||
ref2=$headers2
|
||||
use_pseudo=true
|
||||
fi
|
||||
fi
|
||||
|
||||
gap2=$((ref2 - blocks2))
|
||||
gap_change=$((gap2 - gap1))
|
||||
|
||||
report_block_catchup_rate "$gap_change" "$interval" "$block_time_ms" "$gap2" "blocks"
|
||||
local rc=$?
|
||||
if [ "$rc" -eq 1 ] && [ -n "$chain_slug" ]; then
|
||||
bitcoin_try_addnode "$chain_slug"
|
||||
fi
|
||||
exit "$rc"
|
||||
}
|
||||
|
||||
ripple_rpc_call() {
|
||||
local url
|
||||
url=$(compose_rpc_url) || return 1
|
||||
curl -L --ipv4 -m 5 -s -X POST -H "Content-Type: application/json" \
|
||||
--data '{"method":"server_info"}' "$url"
|
||||
}
|
||||
|
||||
ripple_validated_seq() {
|
||||
local resp="$1" seq
|
||||
seq=$(echo "$resp" | jq -r '.result.info.validated_ledger.seq // .info.validated_ledger.seq // empty' 2>/dev/null)
|
||||
if [[ ! "$seq" =~ ^[0-9]+$ ]]; then
|
||||
return 1
|
||||
fi
|
||||
echo "$seq"
|
||||
}
|
||||
|
||||
ripple_reference_seq() {
|
||||
local chain_slug="$1" ref_urls ref_url resp
|
||||
ref_urls=$($BASEPATH/reference-rpc-endpoint.sh --chain "$chain_slug" 2>/dev/null) || return 1
|
||||
ref_url=$(echo "$ref_urls" | awk '{print $1}')
|
||||
[ -z "$ref_url" ] && return 1
|
||||
resp=$(curl -L --ipv4 -m 5 -s -X POST -H "Content-Type: application/json" \
|
||||
--data '{"method":"server_info"}' "$ref_url")
|
||||
ripple_validated_seq "$resp"
|
||||
}
|
||||
|
||||
ripple_catchup() {
|
||||
local chain_slug="$1"
|
||||
local interval="$2"
|
||||
local block_time_ms resp1 resp2 seq1 seq2 ref1 ref2 gap1 gap2 gap_change
|
||||
|
||||
block_time_ms=$($BASEPATH/reference-rpc-endpoint.sh --block-time-ms "$chain_slug" 2>/dev/null) || block_time_ms="4000"
|
||||
|
||||
echo "Checking sync status for $compose_base (ripple)..."
|
||||
resp1=$(ripple_rpc_call)
|
||||
if [ -z "$resp1" ]; then
|
||||
echo -e "${RED}rpc-not-open (expected mid-sync for stage-sync/bootstrap clients — avalanchego, erigon/reth stage-sync, statesync bootstraps); re-run at chainhead${NC}"
|
||||
exit 1
|
||||
fi
|
||||
seq1=$(ripple_validated_seq "$resp1") || {
|
||||
echo -e "${RED}rpc-not-open (expected mid-sync for stage-sync/bootstrap clients — avalanchego, erigon/reth stage-sync, statesync bootstraps); re-run at chainhead${NC}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
ref1=$(ripple_reference_seq "$chain_slug") || ref1=""
|
||||
if [ -z "$ref1" ]; then
|
||||
gap1=0
|
||||
echo "Reference RPC unavailable — measuring local validated-ledger progress only"
|
||||
else
|
||||
gap1=$((ref1 - seq1))
|
||||
echo "Current chain head is $gap1 ledgers behind reference"
|
||||
s_to_human_readable $(echo "scale=0; $gap1 * $block_time_ms / 1000" | bc)
|
||||
fi
|
||||
|
||||
echo "Measuring progress over $interval seconds..."
|
||||
sleep "$interval"
|
||||
|
||||
resp2=$(ripple_rpc_call)
|
||||
if [ -z "$resp2" ]; then
|
||||
echo -e "${RED}rpc-not-open (expected mid-sync for stage-sync/bootstrap clients — avalanchego, erigon/reth stage-sync, statesync bootstraps); re-run at chainhead${NC}"
|
||||
exit 1
|
||||
fi
|
||||
seq2=$(ripple_validated_seq "$resp2") || {
|
||||
echo -e "${RED}rpc-not-open (expected mid-sync for stage-sync/bootstrap clients — avalanchego, erigon/reth stage-sync, statesync bootstraps); re-run at chainhead${NC}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
ref2=$(ripple_reference_seq "$chain_slug") || ref2=""
|
||||
if [ -z "$ref1" ] || [ -z "$ref2" ]; then
|
||||
local seq_gain=$((seq2 - seq1))
|
||||
local realtime_units effective catchup_rate
|
||||
realtime_units=$(echo "scale=6; $interval * 1000 / $block_time_ms" | bc)
|
||||
effective=$(echo "scale=6; $seq_gain" | bc)
|
||||
catchup_rate=$(echo "scale=3; $effective / $realtime_units" | bc)
|
||||
echo "Validated ledger gain in $interval seconds: $seq_gain ledgers"
|
||||
echo "Calculated catchup rate: ${catchup_rate}x realtime"
|
||||
if (( $(echo "$catchup_rate < 1" | bc -l) )); then
|
||||
if (( $(echo "$catchup_rate < 0" | bc -l) )); then
|
||||
echo -e "${RED}Node is FALLING BEHIND${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${YELLOW}WARNING: Node is processing at ${catchup_rate}x realtime${NC}"
|
||||
exit 2
|
||||
fi
|
||||
echo -e "${GREEN}Node is processing at ${catchup_rate}x realtime${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
gap2=$((ref2 - seq2))
|
||||
gap_change=$((gap2 - gap1))
|
||||
|
||||
report_block_catchup_rate "$gap_change" "$interval" "$block_time_ms" "$gap2" "ledgers"
|
||||
exit $?
|
||||
}
|
||||
|
||||
cosmos_catching_up() {
|
||||
local url status catching_up
|
||||
url=$(compose_rpc_url) || return 1
|
||||
status=$(curl -L --ipv4 -m 5 -s -X POST -H "Content-Type: application/json" \
|
||||
--data '{"jsonrpc":"2.0","id":1,"method":"status"}' "$url")
|
||||
catching_up=$(echo "$status" | jq -r '.result.sync_info.catching_up // .sync_info.catching_up // empty' 2>/dev/null)
|
||||
echo "$catching_up"
|
||||
}
|
||||
|
||||
cosmos_catchup() {
|
||||
local catching_up
|
||||
|
||||
echo "Checking sync status for $compose_base (cosmos/cometbft)..."
|
||||
catching_up=$(cosmos_catching_up)
|
||||
if [ "$catching_up" = "true" ]; then
|
||||
echo "CometBFT reports catching_up=true (node is still syncing)"
|
||||
elif [ -z "$catching_up" ]; then
|
||||
echo -e "${RED}rpc-not-open (expected mid-sync for stage-sync/bootstrap clients — avalanchego, erigon/reth stage-sync, statesync bootstraps); re-run at chainhead${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Standard timestamp-lag algorithm via timestamp.sh (cosmos /status source).
|
||||
echo "Debug: Running ./timestamp.sh $compose_base"
|
||||
timestamp_output=$(./timestamp.sh "$compose_base" 2>&1)
|
||||
timestamp_exit_code=$?
|
||||
|
||||
echo "Debug: timestamp.sh output: '$timestamp_output'"
|
||||
echo "Debug: timestamp.sh exit code: $timestamp_exit_code"
|
||||
|
||||
latest_block_timestamp_decimal=$(echo "$timestamp_output" | grep -o '[0-9]\+' | head -1)
|
||||
|
||||
if [[ $timestamp_exit_code -ne 0 || -z "$latest_block_timestamp_decimal" ]]; then
|
||||
echo -e "${RED}rpc-not-open (expected mid-sync for stage-sync/bootstrap clients — avalanchego, erigon/reth stage-sync, statesync bootstraps); re-run at chainhead${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
current_time=$(date +%s)
|
||||
time_difference=$((current_time - latest_block_timestamp_decimal))
|
||||
|
||||
echo "Current chain head is $time_difference seconds behind real time"
|
||||
if [ "$catching_up" = "true" ]; then
|
||||
echo "(catching_up=true)"
|
||||
fi
|
||||
s_to_human_readable $time_difference
|
||||
|
||||
echo "Measuring progress over $seconds_to_measure seconds..."
|
||||
sleep $seconds_to_measure
|
||||
|
||||
catching_up=$(cosmos_catching_up)
|
||||
if [ "$catching_up" = "true" ]; then
|
||||
echo "CometBFT still reports catching_up=true"
|
||||
fi
|
||||
|
||||
echo "Debug: Running ./timestamp.sh $compose_base (second measurement)"
|
||||
timestamp_output=$(./timestamp.sh "$compose_base" 2>&1)
|
||||
timestamp_exit_code=$?
|
||||
|
||||
echo "Debug: timestamp.sh output (second): '$timestamp_output'"
|
||||
echo "Debug: timestamp.sh exit code (second): $timestamp_exit_code"
|
||||
|
||||
latest_block_timestamp2=$(echo "$timestamp_output" | grep -o '[0-9]\+' | head -1)
|
||||
|
||||
if [[ $timestamp_exit_code -ne 0 || -z "$latest_block_timestamp2" ]]; then
|
||||
echo -e "${RED}rpc-not-open (expected mid-sync for stage-sync/bootstrap clients — avalanchego, erigon/reth stage-sync, statesync bootstraps); re-run at chainhead${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
current_time2=$(date +%s)
|
||||
time_difference2=$((current_time2 - latest_block_timestamp2))
|
||||
|
||||
gap_change=$((time_difference2 - time_difference))
|
||||
|
||||
effective_catchup_seconds=$((seconds_to_measure - gap_change))
|
||||
catchup_rate=$(echo "scale=3; $effective_catchup_seconds / $seconds_to_measure" | bc)
|
||||
|
||||
echo "First measurement: Time behind = $time_difference seconds"
|
||||
echo "Second measurement: Time behind = $time_difference2 seconds"
|
||||
if [ "$catching_up" = "true" ]; then
|
||||
echo "(catching_up=true)"
|
||||
fi
|
||||
echo "Gap change in $seconds_to_measure seconds: $gap_change seconds"
|
||||
echo "Calculated catchup rate: $catchup_rate× realtime"
|
||||
|
||||
if (( $(echo "$catchup_rate < 0" | bc -l) )); then
|
||||
falling_behind_rate=$(echo "scale=2; -1 * $catchup_rate" | bc)
|
||||
echo -e "${RED}Node is FALLING BEHIND by $falling_behind_rate× realtime${NC}"
|
||||
echo -e "${RED}The gap increased by $gap_change seconds over $seconds_to_measure seconds${NC}"
|
||||
exit 1
|
||||
elif (( $(echo "$catchup_rate < 1" | bc -l) )); then
|
||||
echo -e "${YELLOW}WARNING: Node is processing at $catchup_rate× realtime${NC}"
|
||||
echo -e "${YELLOW}This is too slow - node will never catch up to chain head${NC}"
|
||||
exit 2
|
||||
else
|
||||
echo -e "${GREEN}Node is processing at $catchup_rate× realtime${NC}"
|
||||
time_to_catchup=$(echo "scale=0; $time_difference2 / ($catchup_rate - 1)" | bc)
|
||||
echo -e "${GREEN}Estimated time until synced:${NC}"
|
||||
s_to_human_readable $time_to_catchup
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
protocol=$(resolve_protocol)
|
||||
chain_slug=$(compose_chain_slug)
|
||||
|
||||
case "$protocol" in
|
||||
bitcoin)
|
||||
bitcoin_catchup "$chain_slug" "$seconds_to_measure"
|
||||
;;
|
||||
ripple)
|
||||
ripple_catchup "$chain_slug" "$seconds_to_measure"
|
||||
;;
|
||||
cosmos)
|
||||
cosmos_catchup
|
||||
;;
|
||||
eth|starknet|aztec)
|
||||
;;
|
||||
*)
|
||||
echo "unsupported protocol $protocol for catchup"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- eth / starknet / aztec: original timestamp-lag path (unchanged) ---
|
||||
|
||||
seconds_to_measure=${2:-10}
|
||||
|
||||
# First measurement
|
||||
@@ -55,7 +544,7 @@ fi
|
||||
current_time=$(date +%s)
|
||||
time_difference=$((current_time - latest_block_timestamp_decimal))
|
||||
|
||||
# Check for reasonable time difference
|
||||
# Check for reasonable time difference
|
||||
echo "Current chain head is $time_difference seconds behind real time"
|
||||
s_to_human_readable $time_difference
|
||||
|
||||
|
||||
44
timestamp.sh
44
timestamp.sh
@@ -16,7 +16,15 @@ else
|
||||
PROTO="https"
|
||||
fi
|
||||
|
||||
pathlist=$(cat $BASEPATH/$1.yml | grep -oP "stripprefix\.prefixes.*?/\K[^\"]+")
|
||||
compose_yml="$BASEPATH/$1.yml"
|
||||
pathlist=$(cat "$compose_yml" | grep -oP "stripprefix\.prefixes.*?/\K[^\"]+")
|
||||
|
||||
# Resolve protocol from x-upstreams chain label (reuse sync-status / catchup idiom).
|
||||
chain_slug=$(grep -oP '^\s*chain:\s*\K\S+' "$compose_yml" 2>/dev/null | head -1)
|
||||
protocol=""
|
||||
if [ -n "$chain_slug" ]; then
|
||||
protocol=$($BASEPATH/reference-rpc-endpoint.sh --protocol "$chain_slug" 2>/dev/null) || protocol=""
|
||||
fi
|
||||
|
||||
for path in $pathlist; do
|
||||
include=true
|
||||
@@ -30,6 +38,40 @@ for path in $pathlist; do
|
||||
RPC_URL="$PROTO://$DOMAIN/$path"
|
||||
response_file=$(mktemp)
|
||||
|
||||
path_protocol="$protocol"
|
||||
if [ -z "$path_protocol" ]; then
|
||||
if echo "$path" | grep -qi "starknet"; then
|
||||
path_protocol="starknet"
|
||||
elif echo "$path" | grep -qi "aztec"; then
|
||||
path_protocol="aztec"
|
||||
elif echo "$path" | grep -qiE "cosmos|babylon|haqq|gaiad|tac"; then
|
||||
path_protocol="cosmos"
|
||||
else
|
||||
path_protocol="eth"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$path_protocol" = "cosmos" ]; then
|
||||
http_status_code=$(curl -L --ipv4 -m 1 -s -X POST -w "%{http_code}" -o "$response_file" \
|
||||
-H "Content-Type: application/json" --data '{"jsonrpc":"2.0","id":1,"method":"status"}' "$RPC_URL")
|
||||
if [ $? -eq 0 ] && [[ $http_status_code -eq 200 ]]; then
|
||||
response=$(cat "$response_file")
|
||||
latest_block_time=$(echo "$response" | jq -r '.result.sync_info.latest_block_time // .sync_info.latest_block_time' 2>/dev/null)
|
||||
rm -f "$response_file"
|
||||
if [ -z "$latest_block_time" ] || [ "$latest_block_time" = "null" ]; then
|
||||
exit 1
|
||||
fi
|
||||
latest_block_timestamp_decimal=$(date -d "$latest_block_time" +%s 2>/dev/null)
|
||||
if [ -z "$latest_block_timestamp_decimal" ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "$latest_block_timestamp_decimal"
|
||||
exit 0
|
||||
fi
|
||||
rm -f "$response_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Detect Starknet vs Ethereum vs Aztec based on path
|
||||
if echo "$path" | grep -qi "starknet"; then
|
||||
rpc_method='{"jsonrpc":"2.0","method":"starknet_getBlockWithTxHashes","params":["latest"],"id":1}'
|
||||
|
||||
Reference in New Issue
Block a user