lantern-cron #937
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }} |