Files
pezkuwi-subquery/.github/workflows/ci.yml
T
SatoshiQaziMuhammed 1e8d5e3675 Stop one lost extrinsic from silencing the noter bot (#30)
Every report waits behind the previous one, and a submission only settled
when it reached a block. One the pool dropped, rejected or replaced never
settled, so every report queued after it waited forever. The watchdog meant
to catch a silent bot was armed only after the first scan returned, so when
that first scan was the one that hung there was no watchdog at all: the
production container logged its last line one minute after starting on
2026-10-13 and stayed up, silent, for more than a day.

- submit.js: a submission settles on a terminal status (Dropped, Invalid,
  Usurped, FinalityTimeout) or after SUBMIT_TIMEOUT_MS, and releases its
  status subscription.
- The watchdog is armed before connecting and counts from the process start
  until a scan completes, so a heartbeat left by an earlier process in the
  same container does not count for this one.
- The noter's own account is skipped: the chain refuses a noter's report
  about themselves, so it failed in every scan.
- Unit tests for both rules, run inside the noter image in CI.
2026-10-15 06:03:40 +04:00

127 lines
4.7 KiB
YAML

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20'
- name: Enable Corepack
run: corepack enable
- name: Install dependencies
run: yarn install --immutable
- name: Generate types
run: ./node_modules/.bin/subql codegen pezkuwi.yaml
- name: Build
run: ./node_modules/.bin/subql build pezkuwi.yaml
# Dependency Review allows the @subql packages because the mapping imports
# only their types; this is the check that keeps it true. A type-only
# import is erased on emit, a value import survives as a require.
- name: The mapping imports no @subql code
run: |
out=$(mktemp -d)
./node_modules/.bin/tsc -p tsconfig.json --outDir "$out" --declaration false
! grep -rn 'require("@subql/' "$out" || {
echo "the mapping imports @subql code; import its types with 'import type' or drop it"; exit 1; }
# The noter and payout bots ship as their own images (docker-compose.prod.yml)
# and nothing else here builds or runs them. Build each image as production
# does, then run inside it: the crypto stack must be the locked one and sign
# with every key type, and the bot must load its whole module graph and stop
# only where it reads the mnemonic that CI does not have.
bots:
name: bots (${{ matrix.bot }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- bot: noter
mode: esm
expect: 'No noter mnemonic found'
- bot: payout-bot
mode: cjs
expect: "ENOENT: no such file or directory, open '/run/secrets/payout_mnemonic'"
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- name: Build the image
run: docker build -t bot ${{ matrix.bot }}
# Dependency Review allows these GPL-3.0 packages in the lockfiles only
# because no image installs them; this is the check that keeps it true.
- name: No light client in the image
run: |
docker run --rm bot sh -c '! [ -e node_modules/@bizinikiwi ] && ! [ -e node_modules/smoldot ]' || {
echo "the image installs the light client; it must be built with --omit=optional"; exit 1; }
- name: Crypto stack
run: docker run --rm -v "$PWD/scripts/bot-smoke.mjs:/app/bot-smoke.mjs:ro" bot node bot-smoke.mjs ${{ matrix.mode }}
# Run inside the image, so the modules under test are the ones the image ships.
- name: Unit tests
if: matrix.bot == 'noter'
run: docker run --rm -v "$PWD/noter/test:/app/test:ro" bot node --test test/submit.test.mjs
- name: The bot loads and stops at its missing mnemonic
env:
EXPECT: ${{ matrix.expect }}
run: |
set +e
out=$(timeout 60 docker run --rm bot 2>&1)
code=$?
set -e
echo "$out"
[ "$code" -ne 0 ] && [ "$code" -ne 124 ] || { echo "expected the bot to exit with an error, got $code"; exit 1; }
grep -qF "$EXPECT" <<<"$out" || { echo "expected: $EXPECT"; exit 1; }
# The indexers run docker/Dockerfile.node (docker-compose.prod.yml). Build it
# as production does: the patch step fails the build if a pattern no longer
# matches. Then the aliased crypto stack must be the locked one and sign with
# every key type, and subql-node must load.
indexer:
name: Indexer image
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- name: Build the image
run: docker build -f docker/Dockerfile.node -t indexer .
- name: Crypto stack
run: docker run --rm --entrypoint node -v "$PWD/scripts/bot-smoke.mjs:/app/bot-smoke.mjs:ro" indexer /app/bot-smoke.mjs indexer
- name: subql-node loads
run: docker run --rm indexer --help >/dev/null
# The scripts the deploy key is pinned to are the whole of what CI can do on
# the host, so they are checked with every change.
host-scripts:
name: Host deploy scripts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- run: |
shellcheck -S warning ops/host/subquery-deploy ops/host/subquery-deploy-ssh ops/host/test-deploy.sh
bash ops/host/test-deploy.sh