#!/bin/bash BASEPATH="$(dirname "$0")" if [ $# -lt 2 ]; then echo "Error: At least two parameters are required." exit 1 fi RPC_URL=$1 shift # Check for --starknet / --aztec / --cosmos flag is_starknet=false is_aztec=false is_cosmos=false if [ "$1" == "--starknet" ]; then is_starknet=true shift elif [ "$1" == "--aztec" ]; then is_aztec=true shift elif [ "$1" == "--cosmos" ]; then is_cosmos=true shift fi REF="" for url in "$@"; do REF+="--url $url " done # Optional: You can remove the trailing space if needed ref=${REF% } # echo "ref: $ref" timeout=3 # seconds # CometBFT / cosmos (gaiad and the cosmos batch): no EVM RPC. Use the chain's own # sync_info from the CometBFT /status method — catching_up=false means caught up to head. # Short-circuits here; the EVM/starknet/aztec block-comparison path below is not used. if $is_cosmos; then status=$(curl -L --ipv4 -m $timeout -s -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","id":1,"method":"status"}' "$RPC_URL") if [ $? -ne 0 ] || [ -z "$status" ]; then echo "timeout"; exit 1; fi catching_up=$(echo "$status" | jq -r '.result.sync_info.catching_up // .sync_info.catching_up' 2>/dev/null) node_height=$(echo "$status" | jq -r '.result.sync_info.latest_block_height // .sync_info.latest_block_height' 2>/dev/null) if [ -z "$node_height" ] || [ "$node_height" = "null" ]; then echo "error"; exit 1; fi if [ "$catching_up" = "true" ]; then echo "syncing"; exit 1; fi # catching_up=false => synced. If a reference endpoint is given, sanity-check head gap. if [ -n "$ref" ]; then ref_status=$($BASEPATH/multicurl.sh -L --ipv4 -m $timeout -s -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","id":1,"method":"status"}' $ref) ref_height=$(echo "$ref_status" | jq -r '.result.sync_info.latest_block_height // .sync_info.latest_block_height' 2>/dev/null) if [ -n "$ref_height" ] && [ "$ref_height" != "null" ] && [ "$ref_height" -gt 0 ] 2>/dev/null; then gap=$(( ref_height - node_height )) if [ "$gap" -gt 100 ]; then echo "behind ($gap)"; exit 1; fi fi fi echo "online"; exit 0 fi response_file=$(mktemp) # Use appropriate RPC method based on chain type if $is_starknet; then rpc_method='{"jsonrpc":"2.0","method":"starknet_getBlockWithTxHashes","params":["latest"],"id":1}' elif $is_aztec; then rpc_method='{"jsonrpc":"2.0","method":"node_getBlock","params":["latest"],"id":1}' else rpc_method='{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", false],"id":1}' fi http_status_code=$(curl -L --ipv4 -m $timeout -s -X POST -w "%{http_code}" -o "$response_file" -H "Content-Type: application/json" --data "$rpc_method" $RPC_URL) if [ $? -eq 0 ]; then if [[ $http_status_code -eq 200 ]]; then response=$(cat "$response_file") if $is_starknet; then # Starknet returns decimal timestamp and block_number latest_block_timestamp_decimal=$(echo "$response" | jq -r '.result.timestamp') latest_block_number=$(echo "$response" | jq -r '.result.block_number') latest_block_hash=$(echo "$response" | jq -r '.result.block_hash') elif $is_aztec; then # Aztec: node_getBlock("latest") returns blockHash, header.globalVariables.blockNumber, header.globalVariables.timestamp latest_block_number=$(echo "$response" | jq -r '.result.header.globalVariables.blockNumber') latest_block_timestamp_decimal=$(echo "$response" | jq -r '.result.header.globalVariables.timestamp') latest_block_hash=$(echo "$response" | jq -r '.result.blockHash') if [ "$latest_block_number" = "null" ] || [ "$latest_block_timestamp_decimal" = "null" ] || [ -z "$latest_block_timestamp_decimal" ]; then echo "error" exit 1 fi else # Ethereum returns hex timestamp and number latest_block_timestamp=$(echo "$response" | jq -r '.result.timestamp') latest_block_timestamp_decimal=$((16#${latest_block_timestamp#0x})) latest_block_number=$(echo "$response" | jq -r '.result.number') latest_block_hash=$(echo "$response" | jq -r '.result.hash') fi current_time=$(date +%s) time_difference=$((current_time - latest_block_timestamp_decimal)) rm "$response_file" if [ -n "$ref" ]; then MAX_RETRIES=3 attempt=1 while [ $attempt -le $MAX_RETRIES ]; do # Re-fetch local latest block for retries > 1 if [ $attempt -gt 1 ]; then sleep 3 # Re-query local latest block and update variables response_file=$(mktemp) http_status_code=$(curl -L --ipv4 -m $timeout -s -X POST -w "%{http_code}" -o "$response_file" -H "Content-Type: application/json" --data "$rpc_method" $RPC_URL) if [ $? -eq 0 ] && [[ $http_status_code -eq 200 ]]; then response=$(cat "$response_file") if $is_starknet; then latest_block_timestamp_decimal=$(echo "$response" | jq -r '.result.timestamp') latest_block_number=$(echo "$response" | jq -r '.result.block_number') latest_block_hash=$(echo "$response" | jq -r '.result.block_hash') elif $is_aztec; then latest_block_number=$(echo "$response" | jq -r '.result.header.globalVariables.blockNumber') latest_block_timestamp_decimal=$(echo "$response" | jq -r '.result.header.globalVariables.timestamp') latest_block_hash=$(echo "$response" | jq -r '.result.blockHash') if [ "$latest_block_number" = "null" ] || [ "$latest_block_timestamp_decimal" = "null" ] || [ -z "$latest_block_timestamp_decimal" ]; then echo "error" exit 1 fi else latest_block_timestamp=$(echo "$response" | jq -r '.result.timestamp') latest_block_timestamp_decimal=$((16#${latest_block_timestamp#0x})) latest_block_number=$(echo "$response" | jq -r '.result.number') latest_block_hash=$(echo "$response" | jq -r '.result.hash') fi current_time=$(date +%s) time_difference=$((current_time - latest_block_timestamp_decimal)) rm "$response_file" else rm "$response_file" echo "error" exit 1 fi fi response_file2=$(mktemp) if [ $attempt -eq 1 ]; then sleep 3 # to give the reference node more time to import the block if it is very current fi if $is_starknet; then # Starknet uses block_id object with block_number rpc_method2="{\"jsonrpc\":\"2.0\",\"method\":\"starknet_getBlockWithTxHashes\",\"params\":[{\"block_number\":$latest_block_number}],\"id\":1}" elif $is_aztec; then rpc_method2="{\"jsonrpc\":\"2.0\",\"method\":\"node_getBlock\",\"params\":[$latest_block_number],\"id\":1}" else rpc_method2="{\"jsonrpc\":\"2.0\",\"method\":\"eth_getBlockByNumber\",\"params\":[\"$latest_block_number\", false],\"id\":1}" fi http_status_code2=$($BASEPATH/multicurl.sh -L --ipv4 -m $timeout -s -X POST -w "%{http_code}" -o "$response_file2" -H "Content-Type: application/json" --data "$rpc_method2" $ref) curl_code2=$? if [ $curl_code2 -eq 0 ]; then if [[ $http_status_code2 -eq 200 ]]; then response2=$(cat "$response_file2") if $is_starknet; then latest_block_hash2=$(echo "$response2" | jq -r '.result.block_hash') elif $is_aztec; then latest_block_hash2=$(echo "$response2" | jq -r '.result.blockHash') else latest_block_hash2=$(echo "$response2" | jq -r '.result.hash') fi rm "$response_file2" # Proceed if hashes match (or both empty for Aztec when API omits hash) if [ "$latest_block_hash" == "$latest_block_hash2" ]; then response_file3=$(mktemp) status_file3=$(mktemp) if $is_aztec; then # Aztec: node_getBlock("latest") - same single-request pattern as eth/starknet rpc_method_latest='{"jsonrpc":"2.0","method":"node_getBlock","params":["latest"],"id":1}' { $BASEPATH/multicurl.sh -L --ipv4 -m $timeout -s -X POST -w "%{http_code} %{time_total}" -o "$response_file3" -H "Content-Type: application/json" --data "$rpc_method_latest" $ref > "$status_file3" } & pid3=$! response_file4=$(mktemp) status_file4=$(mktemp) { curl -L --ipv4 -m $timeout -s -X POST -w "%{http_code} %{time_total}" -o "$response_file4" -H "Content-Type: application/json" --data "$rpc_method_latest" $RPC_URL > "$status_file4" } & pid4=$! else if $is_starknet; then rpc_method_latest='{"jsonrpc":"2.0","method":"starknet_getBlockWithTxHashes","params":["latest"],"id":1}' else rpc_method_latest='{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", false],"id":1}' fi { $BASEPATH/multicurl.sh -L --ipv4 -m $timeout -s -X POST -w "%{http_code} %{time_total}" -o "$response_file3" -H "Content-Type: application/json" --data "$rpc_method_latest" $ref > "$status_file3" } & pid3=$! response_file4=$(mktemp) status_file4=$(mktemp) { curl -L --ipv4 -m $timeout -s -X POST -w "%{http_code} %{time_total}" -o "$response_file4" -H "Content-Type: application/json" --data "$rpc_method_latest" $RPC_URL > "$status_file4" } & pid4=$! fi wait $pid3 curl_code3=$? http_status_code3=$(cat "$status_file3" | cut -d ' ' -f 1) request_time3=$(cat "$status_file3" | cut -d ' ' -f 2) rm "$status_file3" wait $pid4 curl_code4=$? http_status_code4=$(cat "$status_file4" | cut -d ' ' -f 1) request_time4=$(cat "$status_file4" | cut -d ' ' -f 2) rm "$status_file4" # echo "lets check" if [ $curl_code3 -eq 0 ]; then if [[ $http_status_code3 -eq 200 ]]; then response3=$(cat "$response_file3") if $is_starknet; then ref_num=$(echo "$response3" | jq -r '.result.block_number // empty') elif $is_aztec; then ref_num=$(echo "$response3" | jq -r '.result.header.globalVariables.blockNumber // empty') else ref_num_hex=$(echo "$response3" | jq -r '.result.number // empty') ref_num=$([ -n "$ref_num_hex" ] && printf '%d' "$ref_num_hex" 2>/dev/null) fi # echo "refer: $latest_block_timestamp_decimal3" rm "$response_file3" if [ $curl_code4 -eq 0 ]; then if [[ $http_status_code4 -eq 200 ]]; then response4=$(cat "$response_file4") if $is_starknet; then local_num=$(echo "$response4" | jq -r '.result.block_number // empty') elif $is_aztec; then local_num=$(echo "$response4" | jq -r '.result.header.globalVariables.blockNumber // empty') else local_num_hex=$(echo "$response4" | jq -r '.result.number // empty') local_num=$([ -n "$local_num_hex" ] && printf '%d' "$local_num_hex" 2>/dev/null) fi #echo "local: $latest_block_timestamp_decimal4" rm "$response_file4" # Lag in BLOCKS between the reference head and the local head # (positive => local behind). Compare against dRPC's own per-chain # thresholds (LAGGING_LAG / SYNCING_LAG from chains.yaml via # sync-status.sh) so our status matches the dRPC gateway's view. # dRPC uses the two thresholds inconsistently (sometimes # lagging