Skip to content

lantern-cron

lantern-cron #937

Workflow file for this run

name: lantern-cron
on:
schedule:
# Restart cadence only. GitHub throttles schedules (fires hours apart, see
# the Lantern staleness incidents), so the in-run self-loop below is what
# actually keeps the on-chain root fresh; the cron just (re)starts it.
- cron: '*/30 * * * *'
workflow_dispatch:
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# A fresh cron/dispatch cancels the previous long-lived loop (mirrors
# services pyth-keeper), so runs never stack and coverage restarts cleanly.
cancel-in-progress: true
jobs:
publish:
runs-on: ubuntu-latest
# Long-lived: the publish step loops ~5.3h. GH free-tier job cap is 6h.
timeout-minutes: 350
permissions:
contents: read
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: '20'
cache: pnpm
- run: pnpm install --frozen-lockfile
# The hardened signer (Audit C-20/F-7) loads an ENCRYPTED keystore from
# disk; it does not accept a raw key. Materialize the keystore envelope
# from a secret into the runner temp dir (outside the repo tree, so the
# signer's path-traversal guard passes) and point LANTERN_KEY_PATH at it.
- name: Materialize Lantern keystore
run: |
printf '%s' "$LANTERN_KEY_ENVELOPE" > "$RUNNER_TEMP/lantern.key"
echo "LANTERN_KEY_PATH=$RUNNER_TEMP/lantern.key" >> "$GITHUB_ENV"
env:
LANTERN_KEY_ENVELOPE: ${{ secrets.LANTERN_KEY_ENVELOPE }}
- name: Trigger Lantern publish (in-run self-loop)
run: |
# GitHub throttles the schedule (fires hours apart), so a single
# publish per run lets the on-chain root drift past the ~130min
# staleness threshold the /reserves UI checks. Mirror the proven
# services pyth-keeper pattern: loop in-run for ~5.3h, publishing every
# ~45min - well inside the staleness window - so the proof stays fresh
# for the whole run. Unlike pyth (price push every ~50s), each lantern
# publish is an on-chain attestation that costs gas, so the cadence is
# minutes. The ~130min freshness buffer after the final publish covers
# the gap until the throttled cron restarts a fresh loop.
end=$((SECONDS + 19200)) # ~5.33h
n=0
while [ "$SECONDS" -lt "$end" ]; do
pnpm --filter @atrium/lantern-attestor publish-now || echo "lantern publish #$n failed; retrying next tick"
n=$((n + 1))
[ "$SECONDS" -lt "$end" ] && sleep 2700 # ~45min
done
echo "lantern loop done after $n publishes; cron will restart a fresh loop"
env:
SCRIBE_URL: ${{ secrets.SCRIBE_URL }}
ARBITRUM_SEPOLIA_RPC: ${{ secrets.ARBITRUM_SEPOLIA_RPC }}
# Addresses resolve from the canonical registry (self-correcting across
# redeploys); the *_ADDRESS env vars are the fallback. The names MUST
# match what publish-once.ts reads (were LANTERN_CONTRACT_ADDR /
# COFFER_ADDR, which the code never read, so every tick threw and the
# on-chain root went stale).
DEPLOYMENT_REGISTRY_URL: https://useatrium.me/deployments/arbitrum_sepolia.json
LANTERN_ATTESTOR_ADDRESS: ${{ secrets.LANTERN_CONTRACT_ADDR }}
COFFER_ADDRESS: ${{ secrets.COFFER_ADDR }}
USDC_ADDR: ${{ secrets.USDC_ADDR }}
PINATA_JWT: ${{ secrets.PINATA_JWT }}
LANTERN_KEY_PASSPHRASE: ${{ secrets.LANTERN_KEY_PASSPHRASE }}
- name: Notify ops on failure
if: failure()
run: |
# Guard the webhook: when DISCORD_OPS_WEBHOOK is unset the bare curl
# exits 2 ("no URL specified"), which masks the real failure and adds
# noise. Only post when the secret is actually configured.
if [ -z "$DISCORD_OPS_WEBHOOK" ]; then
echo "DISCORD_OPS_WEBHOOK not set; skipping failure notification."
exit 0
fi
curl -X POST -H 'Content-Type: application/json' \
-d "{\"content\":\"⚠️ ${{ github.workflow }} failed on ${{ github.ref_name }}: ${{ github.run_id }}\"}" \
"$DISCORD_OPS_WEBHOOK"
env:
DISCORD_OPS_WEBHOOK: ${{ secrets.DISCORD_OPS_WEBHOOK }}