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 / airlock.sh
scripts/sepolia/airlock.sh100 lines · 5433 bytes
1#!/usr/bin/env bash
2# The airlock listener: runs the CRE workflow's log trigger (handler 0) in listen mode. Every AirlockAdmin.Proposed
3# event is judged as it arrives and answered on chain: a pass becomes a Clear report, a failed check a Veto report.
4#
5# scripts/sepolia/airlock.sh # live Sepolia (target "sepolia", cre/veto/config.sepolia.json)
6# FORK=19631 scripts/sepolia/airlock.sh # local fork (target "sepolia-fork", a generated config in the state dir)
7# scripts/sepolia/airlock.sh stop # stops a background airlock.sh started with the same FORK setting
8#
9# It runs: (cd cre && $CRE_SIM workflow simulate ./veto -T <target> --non-interactive --trigger-index 0 --listen --broadcast)
10# with the attester key (the EIP-712 verdict secret, env name from cre/secrets.yaml) and CRE_ETH_PRIVATE_KEY from
11# sender.key (pays the writeReport transaction to the MockKeystoneForwarder). The workflow is compiled once per
12# source version into the state dir and shared with sweep.sh (WASM_CACHE=0 compiles inside the simulator instead).
13# Proposals it misses (the listener was down, the log was too big to deliver) are picked up by sweep.sh.
14# Env: CRE_SIM (default `cre`, the official CLI, which needs a logged-in session; a login-free build of the same
15# CLI also works), KEY_DIR, SEPOLIA_RPC (only for the pre-flight reads here; the workflow itself uses the RPCs
16# in cre/project.yaml and in the config), AIRLOCK_STATE, WORKFLOW_CONFIG (live: another config file).
17# Output: the simulator's output on stdout and in <state dir>/airlock-<time>.log (airlock.log points to the latest).
18# Runs in the foreground (systemd/airlock-listener.service runs it like this); stop it with Ctrl-C or `airlock.sh stop`.
19# When the simulator exits (it ends listen mode on any failed run), it is started again after 5 s.
20source "$(dirname "$0")/lib.sh"
21PIDF=$STATE/airlock.pid
22
23if [ "${1:-}" = stop ]; then
24 if [ -f "$PIDF" ] && kill -0 "$(cat "$PIDF")" 2>/dev/null; then
25 kill "$(cat "$PIDF")" && say "stopped airlock (pid $(cat "$PIDF"))"
26 else
27 say "no airlock running for $NET"
28 fi
29 rm -f "$PIDF"
30 exit 0
31fi
32[ $# = 0 ] || die "usage: [FORK=<port>] $0 [stop]"
33if [ -f "$PIDF" ] && kill -0 "$(cat "$PIDF")" 2>/dev/null; then
34 die "an airlock listener is already running for $NET (pid $(cat "$PIDF")); stop it first: ${FORK:+FORK=$FORK }$0 stop"
35fi
36
37sim_bin
38check_rpc
39GA=$(dep airlockAdmin)
40RX=$(dep receiver)
41workflow_config
42CONFIG_ARGS=(--config "$CONFIG")
43
44ATTESTER_PK=$(read_key attester)
45SENDER_PK=$(read_key sender)
46ATTESTER=$(cast wallet address --private-key "$ATTESTER_PK")
47SENDER=$(cast wallet address --private-key "$SENDER_PK")
48ONCHAIN_ATTESTER=$(cast call "$RX" 'attester()(address)' --rpc-url "$RPC")
49[ "$ONCHAIN_ATTESTER" = "$ATTESTER" ] || die "attester.key is $ATTESTER but AirlockReceiver.attester() is $ONCHAIN_ATTESTER"
50[ "$(lc "$(cast call "$GA" 'clearanceOracle()(address)' --rpc-url "$RPC")")" = "$(lc "$RX")" ] \
51 || die "the deployment's receiver is not AirlockAdmin's clearance oracle"
52SBAL=$(cast balance "$SENDER" --rpc-url "$RPC")
53GP=$(cast gas-price --rpc-url "$RPC")
54GL=$(jq -r .gasLimit "$CONFIG")
55PER_REPORT=$(bc <<<"$GL * $GP * 2")
56[ "$SBAL" != 0 ] || die "sender $SENDER has no ETH on $NET (run fund-sender.sh)"
57[ "$(bc <<<"$SBAL < $PER_REPORT")" = 0 ] || say "warning: sender balance $(cast from-wei "$SBAL") ETH may not cover a report ($GL gas at 2x $GP wei)"
58
59STAMP=$(date +%Y%m%d-%H%M%S)
60LOG=$STATE/airlock-$STAMP.log
61ln -sf "$(basename "$LOG")" "$STATE/airlock.log"
62{
63 say "== Upgrade Airlock listener: CRE log trigger, $NET, started $(date -u +%Y-%m-%dT%H:%M:%SZ) at block $(block_now)"
64 say " AirlockAdmin $GA receiver $RX"
65 say " attester $ATTESTER report sender $SENDER ($(cast from-wei "$SBAL") ETH)"
66 say " providers $(jq -r '[.rpcs[].label]|join(", ")' "$CONFIG"), quorum $(jq -r .minAgree "$CONFIG"); target $TARGET; simulator $(basename "$SIM")"
67} | tee "$LOG"
68wasm_build
69[ ${#WASM_ARGS[@]} = 0 ] || say " workflow binary $(basename "${WASM_ARGS[1]}")" | tee -a "$LOG"
70
71cleanup() {
72 if [ -n "${SPID:-}" ]; then kill "$SPID" 2>/dev/null || true; fi
73 rm -f "$PIDF"
74}
75trap cleanup EXIT
76trap 'exit 130' INT TERM
77cd "$ROOT/cre"
78echo $$ >"$PIDF"
79# The simulator in listen mode ends when one workflow run fails (an RPC timeout, a revert). Start it again after
80# RESTART_SECS (default 5); a proposal that arrives in the gap stays Pending without a clearance until sweep.sh
81# judges it. AIRLOCK_RESTART=0 exits instead (with the simulator's exit status).
82while :; do
83 # The keys go to the simulator's environment only (never to any process's arguments, or to the log): the subshell
84 # exports them with the shell builtin, then becomes the simulator (exec), so $! is the simulator's pid.
85 (
86 export "$(attester_env)=$ATTESTER_PK" CRE_ETH_PRIVATE_KEY="$SENDER_PK"
87 exec "$SIM" workflow simulate ./veto -T "$TARGET" "${CONFIG_ARGS[@]}" ${WASM_ARGS[@]+"${WASM_ARGS[@]}"} \
88 --non-interactive --trigger-index 0 --listen --broadcast
89 ) > >(tee -a "$LOG") 2>&1 &
90 SPID=$!
91 say "airlock listening (simulator pid $SPID); log $LOG"
92 set +e
93 wait "$SPID"
94 RC=$?
95 set -e
96 SPID=
97 [ "${AIRLOCK_RESTART:-1}" = 1 ] || exit "$RC"
98 say "== simulator exited ($RC) at block $(block_now 2>/dev/null || echo '?'); restarting in ${RESTART_SECS:-5} s (sweep.sh judges what arrives meanwhile)" | tee -a "$LOG"
99 sleep "${RESTART_SECS:-5}"
100done