diff --git a/.github/workflows/deploy-vps.yml b/.github/workflows/deploy-vps.yml index 76919cc..f999815 100644 --- a/.github/workflows/deploy-vps.yml +++ b/.github/workflows/deploy-vps.yml @@ -2,6 +2,11 @@ name: Deploy to VPS on: push: branches: [main] + # The Zagros history indexer runs on the explorer host, not here. + paths-ignore: + - zagros-assethub.yaml + - docker-compose.zagros.yml + - ops/zagros/** concurrency: group: deploy-vps diff --git a/.gitignore b/.gitignore index 564b7a1..f95497f 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ secrets/ # SubQuery .data/ project.yaml +.env.zagros diff --git a/docker-compose.zagros.yml b/docker-compose.zagros.yml new file mode 100644 index 0000000..fe5d70d --- /dev/null +++ b/docker-compose.zagros.yml @@ -0,0 +1,82 @@ +# Zagros Asset Hub history for explorer.pezkuwichain.io. +# +# Runs on the explorer host next to its own archive node (systemd unit +# zagros-ah-archive, RPC on 127.0.0.1:9955), never on the mainnet indexer host: +# deploy-vps ignores this file and zagros-assethub.yaml. +# +# Everything uses the host network. Postgres binds 127.0.0.1; subql-node (3101) +# and subql-query (3100) cannot be told to, so the host firewall (default deny, +# only 22/80/443 open) keeps them local. Docker would bypass that firewall for +# published ports, so nothing is published. +# nginx exposes only POST /history/assethub (same origin as the explorer). +# +# printf 'ZAGROS_DB_PASS=%s\n' "$(openssl rand -hex 24)" > .env.zagros && chmod 600 .env.zagros +# docker compose -f docker-compose.zagros.yml --env-file .env.zagros up -d +# +# The mapping in dist/ must be built first (see ops/zagros/README.md). +name: zagros-history + +services: + postgres: + image: postgres:16-alpine + network_mode: host + command: ["postgres", "-c", "listen_addresses=127.0.0.1", "-c", "port=5433"] + volumes: + - zagros-pgdata:/var/lib/postgresql/data + - ./docker/init:/docker-entrypoint-initdb.d + environment: + POSTGRES_PASSWORD: ${ZAGROS_DB_PASS:?set ZAGROS_DB_PASS in .env.zagros} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -h 127.0.0.1 -p 5433"] + interval: 5s + timeout: 5s + retries: 10 + mem_limit: 1g + restart: unless-stopped + + subquery-node-assethub: + build: + context: . + dockerfile: docker/Dockerfile.node + network_mode: host + depends_on: + postgres: { condition: service_healthy } + environment: + TZ: UTC + DB_USER: postgres + DB_PASS: ${ZAGROS_DB_PASS:?set ZAGROS_DB_PASS in .env.zagros} + DB_DATABASE: postgres + DB_HOST: 127.0.0.1 + DB_PORT: 5433 + volumes: + - ./:/app/project:ro + command: + - -f=/app/project/zagros-assethub.yaml + - --db-schema=subquery-zagros-assethub + - --disable-historical=true + - --batch-size=30 + - --port=3101 + mem_limit: 1g + restart: unless-stopped + + graphql-engine-assethub: + image: onfinality/subql-query:v1.5.0 + network_mode: host + depends_on: + - subquery-node-assethub + environment: + DB_USER: postgres + DB_PASS: ${ZAGROS_DB_PASS:?set ZAGROS_DB_PASS in .env.zagros} + DB_DATABASE: postgres + DB_HOST: 127.0.0.1 + DB_PORT: 5433 + command: + - --name=subquery-zagros-assethub + - --indexer=http://127.0.0.1:3101 + - --port=3100 + - --query-limit=100 + mem_limit: 512m + restart: unless-stopped + +volumes: + zagros-pgdata: diff --git a/ops/zagros/README.md b/ops/zagros/README.md new file mode 100644 index 0000000..dfe9f5e --- /dev/null +++ b/ops/zagros/README.md @@ -0,0 +1,38 @@ +# Zagros Asset Hub history (explorer host) + +Serves `HistoryElement` for Zagros Asset Hub to explorer.pezkuwichain.io. It +runs on the explorer host next to its own archive node, never on the mainnet +indexer host (`deploy-vps` ignores `zagros-assethub.yaml`, +`docker-compose.zagros.yml` and this directory). + +## Pieces + +| piece | where | listens | +|---|---|---| +| archive node `zagros-ah-archive` (systemd, `--state-pruning archive --blocks-pruning archive`, relay followed over RPC) | host | RPC 127.0.0.1:9955 | +| postgres 16 | compose | 127.0.0.1:5433 | +| subql-node, `zagros-assethub.yaml` | compose | 3101 (firewalled) | +| subql-query | compose | 3100 (firewalled); nginx exposes `POST /history/assethub` only | + +## Build and start + +```sh +git clone https://github.com/pezkuwichain/pezkuwi-subquery /opt/zagros-history +cd /opt/zagros-history +# Build the mapping (dist/) without installing Node on the host. +docker run --rm -v "$PWD":/w -w /w node:24-alpine sh -c \ + 'corepack enable && yarn install --immutable && yarn subql codegen zagros-assethub.yaml && yarn subql build zagros-assethub.yaml' +printf 'ZAGROS_DB_PASS=%s\n' "$(openssl rand -hex 24)" > .env.zagros && chmod 600 .env.zagros +docker compose -f docker-compose.zagros.yml --env-file .env.zagros up -d --build +``` + +## Check + +```sh +curl -s -X POST 127.0.0.1:3100 -H 'content-type: application/json' \ + -d '{"query":"{_metadata{lastProcessedHeight targetHeight}}"}' +``` + +`targetHeight - lastProcessedHeight` above 100 for ten minutes means the +indexer has stalled; the explorer then shows "on its way" for payments it has +not seen yet. diff --git a/zagros-assethub.yaml b/zagros-assethub.yaml new file mode 100644 index 0000000..24f6a66 --- /dev/null +++ b/zagros-assethub.yaml @@ -0,0 +1,99 @@ +specVersion: 1.0.0 +name: subquery-zagros-assethub +version: 1.0.0 +runner: + node: + name: "@subql/node" + version: ">=4.6.6" + query: + name: "@subql/query" + version: "*" +description: Zagros Asset Hub SubQuery - history for explorer.pezkuwichain.io (same handlers as pezkuwi-assethub.yaml) +repository: https://github.com/pezkuwichain/pezkuwi-subquery +schema: + file: ./schema.graphql +network: + chainId: "0x063f739077881df9f291548de301b2e11d6a45cc545c24e7bf18d8a40f0ce182" + endpoint: + - ws://127.0.0.1:9955 +dataSources: + - name: main + kind: substrate/Runtime + startBlock: 1 + mapping: + file: ./dist/index.js + handlers: + # Block handler: initialize active pool stakers from chain state + - handler: handleBlock + kind: substrate/BlockHandler + # Signed extrinsics for history + - handler: handleHistoryElement + kind: substrate/CallHandler + filter: + isSigned: true + # Nomination Pools rewards + - handler: handlePoolReward + kind: substrate/EventHandler + filter: + module: nominationPools + method: PaidOut + # Pool member bonded (track active stakers) + - handler: handlePoolBonded + kind: substrate/EventHandler + filter: + module: nominationPools + method: Bonded + # Pool member unbonded (cleanup active stakers) + - handler: handlePoolUnbonded + kind: substrate/EventHandler + filter: + module: nominationPools + method: Unbonded + # Pool bonded slash + - handler: handlePoolBondedSlash + kind: substrate/EventHandler + filter: + module: nominationPools + method: PoolSlashed + # Pool unbonding slash + - handler: handlePoolUnbondingSlash + kind: substrate/EventHandler + filter: + module: nominationPools + method: UnbondingPoolSlashed + # Staking rewards on AH (validators + direct nominators) + - handler: handleAHRewarded + kind: substrate/EventHandler + filter: + module: staking + method: Rewarded + # Staking slashes on AH + - handler: handleAHSlashed + kind: substrate/EventHandler + filter: + module: staking + method: Slashed + # Era changes on AH (staking pallet lives here) - old format + - handler: handleAHNewEra + kind: substrate/EventHandler + filter: + module: staking + method: StakingElection + # Era changes on AH - new format (Polkadot 2.0) + - handler: handleAHStakersElected + kind: substrate/EventHandler + filter: + module: staking + method: StakersElected + # Native transfers + - handler: handleTransfer + kind: substrate/EventHandler + filter: + module: balances + method: Transfer + # Asset transfers + - handler: handleAssetTransfer + kind: substrate/EventHandler + filter: + module: assets + method: Transferred