diff --git a/.github/workflows/test-examples.yml b/.github/workflows/test-examples.yml index a16a931..560393b 100644 --- a/.github/workflows/test-examples.yml +++ b/.github/workflows/test-examples.yml @@ -49,48 +49,22 @@ jobs: solana config set --url localhost solana-keygen new --no-bip39-passphrase --silent --outfile ~/.config/solana/id.json - - name: Start Solana Test Validator + - name: Configure Validator Limits run: | - mb-test-validator \ - --ledger ./my-ledger-1 \ - --reset & - - # Wait for validator to be ready - timeout 30 bash -c 'until solana cluster-version --url http://localhost:8899 >/dev/null 2>&1; do sleep 1; done' - - - name: Start MagicBlock Ephemeral Validator - run: | - # Magic voodoo to increase resource limits - sudo prlimit --pid $$ --nofile=1048576:1048576 sudo sysctl fs.inotify.max_user_instances=1280 sudo sysctl fs.inotify.max_user_watches=655360 - RUST_LOG=info ephemeral-validator \ - --remotes "http://localhost:8899" \ - --remotes "ws://localhost:8900" \ - -l "7799" \ - --lifecycle ephemeral \ - > /tmp/ephemeral-validator.log 2>&1 & - - # Wait for validator to be ready - timeout 60 bash -c 'until curl -s http://localhost:7799 >/dev/null 2>&1; do sleep 1; done' || { - echo "ephemeral-validator did not become ready within 60 seconds" - echo "=== Last 100 lines of validator log ===" - tail -100 /tmp/ephemeral-validator.log || true - exit 1 - } + - name: Test anchor-counter + env: + PROVIDER_ENDPOINT: http://127.0.0.1:8899 + WS_ENDPOINT: ws://127.0.0.1:8900 + EPHEMERAL_PROVIDER_ENDPOINT: http://127.0.0.1:7799 + EPHEMERAL_WS_ENDPOINT: ws://127.0.0.1:7800 run: | + sudo prlimit --pid $$ --nofile=1048576:1048576 cd anchor-counter - anchor build && anchor deploy --provider.cluster localnet yarn install - EPHEMERAL_PROVIDER_ENDPOINT="http://localhost:7799" \ - EPHEMERAL_WS_ENDPOINT="ws://localhost:7800" \ - PROVIDER_ENDPOINT=http://localhost:8899 \ - WS_ENDPOINT=http://localhost:8900 \ - anchor test \ - --provider.cluster localnet \ - --skip-local-validator \ - --skip-deploy + anchor test --provider.cluster localnet # - name: Test anchor-minter # run: | diff --git a/.gitignore b/.gitignore index 472cec5..69aa647 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,9 @@ **/.next/ **/.env **/tsconfig.tsbuildinfo -!/anchor-counter/target/deploy/anchor_counter-keypair.json +!/anchor-counter/target/deploy/ +/anchor-counter/target/deploy/* +!/anchor-counter/target/deploy/*-keypair.json !/bolt-counter/target/deploy/counter-keypair.json !/bolt-counter/target/deploy/increase-keypair.json diff --git a/anchor-counter/.gitignore b/anchor-counter/.gitignore index 33fe907..3cf3032 100644 --- a/anchor-counter/.gitignore +++ b/anchor-counter/.gitignore @@ -10,7 +10,9 @@ **/.next/ **/.env **/magicblock-test-storage -!/anchor-counter/target/deploy/anchor_counter-keypair.json +!target/deploy/ +target/deploy/* +!target/deploy/*-keypair.json !/bolt-counter/target/deploy/counter-keypair.json !/bolt-counter/target/deploy/increase-keypair.json diff --git a/anchor-counter/Anchor.toml b/anchor-counter/Anchor.toml index aaa99bd..747175b 100644 --- a/anchor-counter/Anchor.toml +++ b/anchor-counter/Anchor.toml @@ -9,7 +9,7 @@ skip-lint = false url = "https://api.apr.dev" [provider] -cluster = "devnet" +cluster = "localnet" wallet = "~/.config/solana/id.json" [scripts] diff --git a/anchor-counter/programs/private-counter/src/lib.rs b/anchor-counter/programs/private-counter/src/lib.rs index 0ef4e45..4325a1d 100644 --- a/anchor-counter/programs/private-counter/src/lib.rs +++ b/anchor-counter/programs/private-counter/src/lib.rs @@ -9,7 +9,7 @@ use ephemeral_rollups_sdk::consts::PERMISSION_PROGRAM_ID; use ephemeral_rollups_sdk::cpi::DelegateConfig; use ephemeral_rollups_sdk::ephem::MagicIntentBundleBuilder; -declare_id!("91L33vBqfNaNfieqNCoqpxGZ2xVyJ29N3VcErR6LoepZ"); +declare_id!("7Y2rYVGqRY31m7ogMHjmtdRMUjeWakoJ6iVx12i6voCY"); pub const COUNTER_SEED: &[u8] = b"counter"; diff --git a/anchor-counter/programs/public-counter/src/lib.rs b/anchor-counter/programs/public-counter/src/lib.rs index b7f48e4..699b426 100644 --- a/anchor-counter/programs/public-counter/src/lib.rs +++ b/anchor-counter/programs/public-counter/src/lib.rs @@ -3,7 +3,7 @@ use ephemeral_rollups_sdk::anchor::{commit, delegate, ephemeral}; use ephemeral_rollups_sdk::cpi::DelegateConfig; use ephemeral_rollups_sdk::ephem::MagicIntentBundleBuilder; -declare_id!("9RPwaXayVZHna1BYuRS4cLPJZuNGU1uS5V3heXB7v6Qi"); +declare_id!("A4D1N6zdiwtAFr7mQcHkNbk2yXmDcpvovvU5D27kZoGv"); pub const COUNTER_SEED: &[u8] = b"counter"; diff --git a/anchor-counter/target/deploy/private_counter-keypair.json b/anchor-counter/target/deploy/private_counter-keypair.json new file mode 100644 index 0000000..397428b --- /dev/null +++ b/anchor-counter/target/deploy/private_counter-keypair.json @@ -0,0 +1 @@ +[119,46,244,54,52,50,191,114,206,197,170,24,175,166,19,3,6,17,59,122,49,186,70,124,216,43,13,202,198,181,103,214,97,25,57,34,18,212,173,207,238,164,136,58,10,45,107,233,195,189,65,84,182,242,104,176,149,52,246,45,128,129,82,141] \ No newline at end of file diff --git a/anchor-counter/target/deploy/public_counter-keypair.json b/anchor-counter/target/deploy/public_counter-keypair.json new file mode 100644 index 0000000..d3e09b8 --- /dev/null +++ b/anchor-counter/target/deploy/public_counter-keypair.json @@ -0,0 +1 @@ +[106,108,44,14,228,142,138,192,251,141,149,227,72,172,124,173,42,3,203,113,154,21,153,82,212,199,168,15,115,100,198,39,134,139,128,19,183,247,83,39,234,34,183,10,57,232,90,108,188,49,31,188,22,201,57,242,183,210,9,200,162,251,250,195] \ No newline at end of file diff --git a/anchor-counter/tests/private-counter.ts b/anchor-counter/tests/private-counter.ts index 2688a07..17e01a9 100644 --- a/anchor-counter/tests/private-counter.ts +++ b/anchor-counter/tests/private-counter.ts @@ -7,12 +7,12 @@ import * as nacl from "tweetnacl"; const COUNTER_SEED = "counter"; -describe.only("private-counter", () => { +describe("private-counter", () => { console.log("private-counter.ts"); let provider = new anchor.AnchorProvider( new anchor.web3.Connection(process.env.PROVIDER_ENDPOINT || "https://api.devnet.solana.com", { - wsEndpoint: process.env.PROVIDER_ENDPOINT?.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://") || undefined, + wsEndpoint: process.env.WS_ENDPOINT || undefined, commitment: "confirmed", }), anchor.Wallet.local(), @@ -27,7 +27,7 @@ describe.only("private-counter", () => { let providerEphemeralRollup = new anchor.AnchorProvider( new anchor.web3.Connection(ephemeralRpcEndpoint, { - wsEndpoint: process.env.TEE_PROVIDER_ENDPOINT?.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://") || undefined, + wsEndpoint: process.env.TEE_WS_ENDPOINT || undefined, commitment: "confirmed", }), anchor.Wallet.local(), diff --git a/anchor-counter/tests/public-counter.ts b/anchor-counter/tests/public-counter.ts index dd831ff..8b908ba 100644 --- a/anchor-counter/tests/public-counter.ts +++ b/anchor-counter/tests/public-counter.ts @@ -6,14 +6,14 @@ import { GetCommitmentSignature } from "@magicblock-labs/ephemeral-rollups-sdk"; const COUNTER_SEED = "counter"; -describe("public-counter", () => { +describe.only("public-counter", () => { console.log("public-counter.ts"); const provider = new anchor.AnchorProvider( new anchor.web3.Connection( process.env.PROVIDER_ENDPOINT || "https://api.devnet.solana.com", { - wsEndpoint: process.env.PROVIDER_ENDPOINT?.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://") || undefined, + wsEndpoint: process.env.WS_ENDPOINT || undefined, commitment: "confirmed", }, ), @@ -27,7 +27,7 @@ describe("public-counter", () => { "https://devnet-as.magicblock.app/", { wsEndpoint: - process.env.EPHEMERAL_PROVIDER_ENDPOINT?.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://") || "wss://devnet-as.magicblock.app/", + process.env.EPHEMERAL_WS_ENDPOINT || "wss://devnet-as.magicblock.app/", commitment: "confirmed", }, ), diff --git a/anchor-counter/yarn.lock b/anchor-counter/yarn.lock index a1def62..f880650 100644 --- a/anchor-counter/yarn.lock +++ b/anchor-counter/yarn.lock @@ -76,11 +76,13 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@magicblock-labs/ephemeral-rollups-sdk@0.8.8": - version "0.8.8" - resolved "https://registry.yarnpkg.com/@magicblock-labs/ephemeral-rollups-sdk/-/ephemeral-rollups-sdk-0.8.8.tgz#5df525ceedf1a667d0f2931f8ddcd83e2c06e062" - integrity sha512-qrhZXSjt4lY3io6pY/VTVkNj2FYGSu/uNwNuT6g3WUFvBVK9pH+enWQHEMSk/f9MsqRbn+prBPCYYVo+1QKMUA== +"@magicblock-labs/ephemeral-rollups-sdk@0.11.1": + version "0.11.1" + resolved "https://registry.yarnpkg.com/@magicblock-labs/ephemeral-rollups-sdk/-/ephemeral-rollups-sdk-0.11.1.tgz#cfc8eb49b625e1f8971cbde7de474f4c39f381c9" + integrity sha512-4z0uFiCqpjUqdbavTVenvSdrLp4DD6EIcvlDYhwl6MMevogj9yrW9w0epyDigyB0xcGW2YH3C4ObzZ6H1a6o3w== dependencies: + "@noble/curves" "^1.4.2" + "@noble/hashes" "^1.4.0" "@phala/dcap-qvl" "^0.3.9" "@solana/web3.js" "^1.98.0" bs58 "^6.0.0" @@ -1041,16 +1043,7 @@ stream-json@^1.9.1: dependencies: stream-chain "^2.2.5" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -1068,14 +1061,7 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -1219,16 +1205,7 @@ workerpool@^9.2.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-9.3.4.tgz#f6c92395b2141afd78e2a889e80cb338fe9fca41" integrity sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== diff --git a/fullstack-test.sh b/fullstack-test.sh index ebaef66..dcdfc03 100755 --- a/fullstack-test.sh +++ b/fullstack-test.sh @@ -51,6 +51,46 @@ has_ancestor_flag() { return 1 } +get_provider_cluster_override() { + # Inspect this script's own args first. + local prev="" + for arg in "$@"; do + case "$arg" in + --provider.cluster=*) + echo "${arg#--provider.cluster=}" + return 0 + ;; + esac + if [ "$prev" = "--provider.cluster" ]; then + echo "$arg" + return 0 + fi + prev="$arg" + done + + # Then walk ancestor processes — anchor's own argv is what we usually find. + local pid=$$ + local depth=0 + local cmd + local match + while [ -n "$pid" ] && [ "$pid" -ne 1 ] && [ "$depth" -lt 20 ]; do + cmd="$(ps -p "$pid" -o args= -ww 2>/dev/null | tr '\n' ' ')" + match="$(echo "$cmd" | sed -nE 's/.*--provider\.cluster[ =]+([^ ]+).*/\1/p' | head -1)" + if [ -n "$match" ]; then + echo "$match" + return 0 + fi + local ppid + ppid="$(ps -p "$pid" -o ppid= 2>/dev/null | tr -d ' ')" + if [ -z "$ppid" ] || [ "$ppid" = "$pid" ] || [ "$ppid" = "0" ]; then + break + fi + pid="$ppid" + depth=$((depth + 1)) + done + return 1 +} + has_anchor_cli_skip_local_validator() { if pgrep -af "anchor" 2>/dev/null | awk '($0 ~ /test/) && ($0 ~ /--skip-local-validator/) { found=1; exit } END { exit (found?0:1) }'; then return 0 @@ -58,6 +98,19 @@ has_anchor_cli_skip_local_validator() { return 1 } +dump_validator_logs() { + if [ -f /tmp/ephemeral-validator.log ]; then + echo "=== ephemeral-validator log (size=$(wc -c < /tmp/ephemeral-validator.log) bytes) ===" + # Strip ANSI escape sequences from TUI output so the log stays readable. + sed -E $'s/\x1b\\[[0-9;?]*[a-zA-Z]//g; s/\x1b[()][AB012]//g' /tmp/ephemeral-validator.log \ + | sed -n '1,200p' + fi + if [ -f /tmp/mb-test-validator.log ]; then + echo "=== mb-test-validator log (size=$(wc -c < /tmp/mb-test-validator.log) bytes) ===" + sed -n '1,200p' /tmp/mb-test-validator.log + fi +} + airdrop_upgrade_authority() { local cluster_url=$1 local keypair=${2:-"${HOME}/.config/solana/id.json"} @@ -105,16 +158,27 @@ if [ ! -f "$ANCHOR_TOML" ]; then exit 1 fi -CLUSTER=$(grep -A 1 "^\[provider\]" "$ANCHOR_TOML" | grep "cluster" | sed 's/.*cluster = "\(.*\)".*/\1/' | tr -d ' ') +# --provider.cluster on the anchor CLI wins over Anchor.toml so that callers +# (CI, local overrides) can target a different cluster without rewriting the file. +CLUSTER="$(get_provider_cluster_override "$@" || true)" +if [ -z "$CLUSTER" ]; then + CLUSTER=$(grep -A 1 "^\[provider\]" "$ANCHOR_TOML" | grep "cluster" | sed 's/.*cluster = "\(.*\)".*/\1/' | tr -d ' ') +fi if [ -z "$CLUSTER" ]; then - echo -e "${RED}Error: Could not determine cluster from Anchor.toml${NC}" + echo -e "${RED}Error: Could not determine cluster (no --provider.cluster flag and no [provider] cluster in Anchor.toml)${NC}" exit 1 fi +# Restore the terminal before printing anything in case a previous validator run +# left newline translation disabled. +[ -t 1 ] && stty sane < /dev/tty 2>/dev/null || true + echo -e "${GREEN}Detected Cluster: ${CLUSTER}${NC}" # Cleanup function cleanup() { + # Belt-and-braces: restore the TTY if a validator's progress UI left it in raw mode. + [ -t 1 ] && stty sane < /dev/tty 2>/dev/null || true if [ "$CLUSTER" = "localnet" ]; then # Only cleanup validators if we started them if [ "$MB_VALIDATOR_STARTED_BY_US" = true ] || [ "$EPHEMERAL_VALIDATOR_STARTED_BY_US" = true ]; then @@ -162,8 +226,22 @@ if [ "$CLUSTER" = "localnet" ]; then if check_port 8899 && ! pgrep -f "mb-test-validator" > /dev/null 2>&1; then echo -e "${YELLOW}Non-MagicBlock validator detected on port 8899, killing it...${NC}" echo -e "${YELLOW}Tip: run with 'anchor test --skip-local-validator --skip-build --skip-deploy' to avoid this${NC}" - lsof -ti :8899 | xargs kill 2>/dev/null - sleep 1 + lsof -ti :8899 | xargs -r kill -TERM 2>/dev/null || true + for _ in $(seq 1 10); do + check_port 8899 || break + sleep 0.5 + done + if check_port 8899; then + lsof -ti :8899 | xargs -r kill -KILL 2>/dev/null || true + for _ in $(seq 1 10); do + check_port 8899 || break + sleep 0.5 + done + fi + if check_port 8899; then + echo -e "${RED}Error: port 8899 is still in use after kill${NC}" + exit 1 + fi fi fi @@ -198,16 +276,37 @@ if [ "$CLUSTER" = "localnet" ]; then MB_VALIDATOR_PID=$! MB_VALIDATOR_STARTED_BY_US=true - # Wait for solana-test-validator to be ready - echo -e "${YELLOW}Waiting for solana-test-validator to be ready...${NC}" - for i in {1..60}; do - if curl -s http://127.0.0.1:8899/health > /dev/null 2>&1; then - echo -e "${GREEN}solana-test-validator is ready${NC}" + # Wait for solana-test-validator to be producing slots. /health returns "ok" + # well before the bank is producing — ephemeral-validator's chainlink fails to + # bootstrap against a non-producing remote and exits silently. + echo -e "${YELLOW}Waiting for solana-test-validator to be producing slots...${NC}" + for i in $(seq 1 90); do + if [ "$MB_VALIDATOR_STARTED_BY_US" = true ] && ! kill -0 "$MB_VALIDATOR_PID" 2>/dev/null; then + echo -e "${RED}Error: mb-test-validator exited before becoming ready${NC}" + if [ -f /tmp/mb-test-validator.log ]; then + echo "Startup log (/tmp/mb-test-validator.log):" + sed -n '1,200p' /tmp/mb-test-validator.log + fi + exit 1 + fi + # commitment=processed reports the latest slot the bank has processed, + # so we see slot>0 within ~1s instead of waiting ~15s for finalization. + # `sed -nE` is portable across BSD sed (macOS) and GNU sed (Linux/CI); + # plain BRE `\+` silently fails on BSD sed and never extracts a value. + slot=$(curl -s --max-time 1 -X POST -H "content-type: application/json" \ + -d '{"jsonrpc":"2.0","method":"getSlot","params":[{"commitment":"processed"}],"id":1}' http://127.0.0.1:8899 2>/dev/null \ + | sed -nE 's/.*"result":([0-9]+).*/\1/p') + if [ -n "$slot" ] && [ "$slot" -gt 0 ]; then + echo -e "${GREEN}solana-test-validator is ready (slot=$slot)${NC}" + [ -t 1 ] && stty sane < /dev/tty 2>/dev/null || true break fi - if [ $i -eq 60 ]; then - echo -e "${RED}Error: solana-test-validator failed to start${NC}" - echo "Check logs at /tmp/mb-test-validator.log" + if [ $i -eq 90 ]; then + echo -e "${RED}Error: solana-test-validator failed to produce slots within 90s${NC}" + if [ -f /tmp/mb-test-validator.log ]; then + echo "Startup log (/tmp/mb-test-validator.log):" + sed -n '1,200p' /tmp/mb-test-validator.log + fi exit 1 fi sleep 1 @@ -221,27 +320,46 @@ if [ "$CLUSTER" = "localnet" ]; then # Try to get the PID of the running validator EPHEMERAL_VALIDATOR_PID=$(lsof -ti :7799 | head -1) else - # Start ephemeral-validator + # Start ephemeral-validator. 0.8.x ships a mandatory ratatui/crossterm TUI; + # without a controlling TTY (e.g. when stdout is redirected to a file as in + # CI or when run as a background job), crossterm's alternate-screen setup + # fails and the binary exits silently with code 0 before opening port 7799. + # Wrap in a pty via python3 so the validator sees a real terminal; the TUI + # ANSI escape codes go into the log file (which is fine for diagnostics). echo -e "[SETUP] ${GREEN}Starting ephemeral-validator...${NC}" - RUST_LOG=info ephemeral-validator \ - --remotes "http://127.0.0.1:8899" \ - --remotes "ws://127.0.0.1:8900" \ - -l "127.0.0.1:7799" \ - --reset \ - > /tmp/ephemeral-validator.log 2>&1 & + RUST_LOG=info python3 -c ' +import pty, os, sys +status = pty.spawn([ + "ephemeral-validator", + "--remotes", "http://127.0.0.1:8899", + "--remotes", "ws://127.0.0.1:8900", + "-l", "127.0.0.1:7799", + "--reset", + "--lifecycle", "ephemeral", +]) +sys.exit(os.WEXITSTATUS(status) if os.WIFEXITED(status) else 128 + os.WTERMSIG(status)) +' /tmp/ephemeral-validator.log 2>&1 & EPHEMERAL_VALIDATOR_PID=$! EPHEMERAL_VALIDATOR_STARTED_BY_US=true # Wait for ephemeral-validator to be ready echo -e "${YELLOW}Waiting for ephemeral-validator to be ready...${NC}" for i in {1..60}; do - if curl -s http://127.0.0.1:7799/health > /dev/null 2>&1; then + if curl -s --max-time 1 http://127.0.0.1:7799/health > /dev/null 2>&1; then echo -e "${GREEN}ephemeral-validator is ready${NC}" + [ -t 1 ] && stty sane < /dev/tty 2>/dev/null || true break fi + if ! kill -0 "$EPHEMERAL_VALIDATOR_PID" 2>/dev/null; then + wait "$EPHEMERAL_VALIDATOR_PID" 2>/dev/null + ev_exit=$? + echo -e "${RED}Error: ephemeral-validator exited before becoming ready (exit=${ev_exit})${NC}" + dump_validator_logs + exit 1 + fi if [ $i -eq 60 ]; then - echo -e "${RED}Error: ephemeral-validator failed to start${NC}" - echo "Check logs at /tmp/ephemeral-validator.log" + echo -e "${RED}Error: ephemeral-validator failed to start within 60s${NC}" + dump_validator_logs exit 1 fi sleep 1