Source mirror · commit 369512f · 2026-10-26 · MIT · canonical repo: gitlab.com/jiapunk/upgrade-airlock
git clone https://upgrade-airlock.pages.dev/source/upgrade-airlock.git
upgrade-airlock / scripts / sepolia / fork.sh
scripts/sepolia/fork.sh84 lines · 4573 bytes
1#!/usr/bin/env bash
2# A local anvil chain with Sepolia's state, for rehearsals and for the "network is down" fallback.
3#
4# FORK=19631 scripts/sepolia/fork.sh start # anvil forking live Sepolia on 127.0.0.1:19631, 12 s blocks
5# FORK=19631 scripts/sepolia/fork.sh save [dir] # snapshot the chain + deployment (default $TMPDIR/.../snapshot)
6# FORK=19632 scripts/sepolia/fork.sh load <dir> # start an OFFLINE anvil from a snapshot: no upstream RPC needed
7# FORK=19631 scripts/sepolia/fork.sh status
8# FORK=19631 scripts/sepolia/fork.sh stop # kills the anvil this script started (by its PID file)
9#
10# Env: BLOCK_TIME (default 12; 0 = mine on every transaction), FORK_SOURCE (default publicnode),
11# FORK_BLOCK (default: latest), READ_ONLY_ACCOUNTS (save: more contracts to carry into the snapshot although
12# the chain never changed them; Multicall3 is always carried).
13# Every other script in this folder takes the same FORK=<port> and then talks only to this local chain.
14source "$(dirname "$0")/lib.sh"
15[ "$MODE" = fork ] || die "set FORK=<port>"
16PIDF=$STATE/anvil.pid
17SOURCE=${FORK_SOURCE:-https://ethereum-sepolia-rpc.publicnode.com}
18BT=${BLOCK_TIME:-12}
19
20running() { [ -f "$PIDF" ] && kill -0 "$(cat "$PIDF")" 2>/dev/null && ps -p "$(cat "$PIDF")" -o comm= | grep -q anvil; }
21
22launch() { # anvil args...
23 if running; then die "a chain is already running on :$FORK (pid $(cat "$PIDF")); stop it first"; fi
24 if cast chain-id --rpc-url "$RPC" >/dev/null 2>&1; then die "port $FORK is already in use by another node"; fi
25 local args=(--port "$FORK" --chain-id "$CHAIN_ID" "$@")
26 [ "$BT" != 0 ] && args+=(--block-time "$BT")
27 nohup anvil "${args[@]}" >"$STATE/anvil.log" 2>&1 &
28 echo $! >"$PIDF"
29 for _ in $(seq 1 60); do cast chain-id --rpc-url "$RPC" >/dev/null 2>&1 && break; sleep 0.5; done
30 check_rpc
31}
32
33case "${1:-status}" in
34 start)
35 running && { say "fork already running on :$FORK (pid $(cat "$PIDF"))"; exit 0; }
36 rm -rf "$STATE" && mkdir -p "$STATE" # a new fork is a new chain: drop the old deployment and logs
37 extra=(--fork-url "$SOURCE")
38 [ -n "${FORK_BLOCK:-}" ] && extra+=(--fork-block-number "$FORK_BLOCK")
39 launch "${extra[@]}"
40 say "fork of Sepolia on $RPC (pid $(cat "$PIDF"), forked at block $(block_now), block time ${BT}s)"
41 say "state dir $STATE"
42 ;;
43 save)
44 running || die "no chain running on :$FORK"
45 OUT=${2:-$STATE/snapshot}
46 mkdir -p "$OUT"
47 # anvil_dumpState keeps only the accounts this chain changed. Contracts the workflow only reads (Multicall3 for
48 # its batched reads) would be missing from an offline replica, so set their code again to make them part of it.
49 MC=$(jq -r '.multicall // empty' "$STATE/config.fork.json" 2>/dev/null || true)
50 for a in "${MC:-0xcA11bde05977b3631167028862bE2a173976CA11}" ${READ_ONLY_ACCOUNTS:-}; do
51 code=$(cast code "$a" --rpc-url "$RPC")
52 [ "$code" = 0x ] || cast rpc anvil_setCode "$a" "$code" --rpc-url "$RPC" >/dev/null
53 done
54 # anvil_dumpState: hex of the gzipped JSON that `anvil --load-state` reads
55 cast rpc anvil_dumpState --rpc-url "$RPC" | tr -d '"' | sed 's/^0x//' | xxd -r -p | gunzip -c >"$OUT/state.json"
56 [ -f "$DEPLOY_JSON" ] && cp "$DEPLOY_JSON" "$OUT/deployment.json"
57 say "saved block $(block_now) of :$FORK to $OUT ($(du -h "$OUT/state.json" | cut -f1) state$([ -f "$OUT/deployment.json" ] && echo ' + deployment.json'))"
58 ;;
59 load)
60 SNAP=${2:-}
61 [ -f "$SNAP/state.json" ] || die "usage: FORK=<port> $0 load <snapshot dir with state.json>"
62 SNAP=$(cd "$SNAP" && pwd)
63 running && die "a chain is already running on :$FORK"
64 rm -rf "$STATE" && mkdir -p "$STATE"
65 [ -f "$SNAP/deployment.json" ] && cp "$SNAP/deployment.json" "$STATE/deployment.json"
66 launch --load-state "$SNAP/state.json"
67 say "offline Sepolia replica on $RPC from $SNAP (pid $(cat "$PIDF"), block $(block_now), block time ${BT}s; no upstream RPC)"
68 ;;
69 stop)
70 if running; then
71 P=$(cat "$PIDF")
72 kill "$P"
73 for _ in $(seq 1 20); do kill -0 "$P" 2>/dev/null || break; sleep 0.5; done
74 if kill -0 "$P" 2>/dev/null; then say "chain :$FORK (pid $P) is still shutting down"; else say "stopped chain :$FORK (pid $P)"; fi
75 else
76 say "no chain started by this script on :$FORK"
77 fi
78 rm -f "$PIDF"
79 ;;
80 status)
81 if running; then say "chain :$FORK running (pid $(cat "$PIDF")), block $(block_now)"; else say "chain :$FORK not running"; exit 1; fi
82 ;;
83 *) die "usage: FORK=<port> $0 start|save [dir]|load <dir>|stop|status" ;;
84esac