Skip to content

Commit 48c3853

Browse files
Merge pull request #116 from paritytech/ch/cdm-e2e
update rust-cdm contract tests
2 parents e45af78 + 82f38ad commit 48c3853

29 files changed

Lines changed: 1282 additions & 62 deletions

.changeset/bump-bulletin-deploy.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"playground-cli": patch
3+
---
4+
5+
Update `bulletin-deploy` to 0.7.10 for the latest DotNS/deploy fixes.

.github/workflows/e2e.yml

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,23 +94,41 @@ jobs:
9494
max-parallel: 1 # serial — share SIGNER + registry domains
9595
matrix:
9696
include:
97+
- cell: pr-deploy-cdm
98+
# source: e2e/cli/deploy.test.ts → describe("dot deploy — cdm …")
99+
pattern: "deploy — cdm"
97100
- cell: pr-deploy-frontend
98101
# source: e2e/cli/deploy.test.ts → describe("dot deploy --playground — full pipeline …")
99102
pattern: "full pipeline"
100103
- cell: pr-deploy-foundry
101104
# source: e2e/cli/deploy.test.ts → describe("dot deploy — foundry …")
102105
pattern: "deploy — foundry"
103-
# pr-deploy-cdm dropped pending fixture upgrade — see the
104-
# describe.skip block in e2e/cli/deploy.test.ts. Phase 5
105-
# follow-up will land a real flipper fixture and re-add
106-
# this cell.
107106
steps:
108107
# checkout must run before the local composite action can be loaded.
109108
- uses: actions/checkout@v4
110109

111110
- id: setup
112111
uses: ./.github/actions/setup-e2e
113112

113+
- name: Install Rust/CDM toolchain
114+
if: matrix.cell == 'pr-deploy-cdm'
115+
shell: bash
116+
run: |
117+
sudo apt-get update -q
118+
sudo apt-get install -y -q --no-install-recommends build-essential pkg-config
119+
if ! command -v rustup >/dev/null 2>&1; then
120+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
121+
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
122+
source "$HOME/.cargo/env"
123+
fi
124+
rustup toolchain install nightly --profile minimal --component rust-src
125+
rustup default nightly
126+
curl -fsSL https://raw.githubusercontent.com/paritytech/contract-dependency-manager/main/install.sh | bash
127+
echo "$HOME/.cdm/bin" >> "$GITHUB_PATH"
128+
export PATH="$HOME/.cdm/bin:$PATH"
129+
command -v cdm
130+
cargo pvm-contract --help
131+
114132
- name: Run E2E cell (one retry on transient testnet failures)
115133
uses: nick-fields/retry@v3
116134
with:

CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ These are things that aren't self-evident from reading the code and have bitten
1818
- **Signer mode selection lives in one file** (`src/utils/deploy/signerMode.ts`). The mainnet rewrite is a single-file swap; keep that boundary clean.
1919
- **`src/utils/deploy/*` and `src/utils/build/*` must not import React or Ink.** They form the SDK surface that RevX consumes from a WebContainer. TUI code lives in `src/commands/*/`.
2020
- **Bun compiled-binary stdin quirk** — Ink's `useInput` silently drops every keystroke (arrows, Enter, Ctrl+C) in `bun build --compile` binaries unless `process.stdin.on('readable', …)` is touched before Ink's `render()`. We install a no-op `readable` listener at the top of `src/index.ts` as a warm-up. Do NOT remove it until Bun's compiled-binary TTY stdin behaves like Node's. Symptom if this breaks: TUI renders but nothing responds, including Ctrl+C.
21-
- **`bulletin-deploy` 0.7.4+ pulls in a transitive dep with a broken publish manifest** that pnpm refuses to install. `@parity/dotns-cli@0.5.6`'s published `package.json` declares `"@polkadot-api/descriptors": "file:.papi/descriptors"` — a workspace-only path that doesn't exist in the published tarball. npm tolerates the dangling `file:` reference (creates a broken symlink and continues); pnpm's strict resolver fails with `ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND`. We work around it with a `pnpm.overrides` entry in `package.json` pointing the offending sub-dep at a tiny stub package (`stubs/papi-descriptors-stub/`) so resolution succeeds. The dep is functionally vestigial — dotns-cli's `dist/cli.js` is fully-bundled (Bun build, no externals) and never imports `@polkadot-api/descriptors` at runtime, so the stub exporting `{}` is correct. **Remove the override + stub once `@parity/dotns-cli` republishes a clean manifest.** Tracked upstream against `paritytech/dotns-sdk`.
22-
- **`bulletin-deploy` is pinned to an explicit version, not `latest`.** We're on `0.7.6` stable today. The `latest` npm dist-tag is a moving target and previously pointed at 0.6.8, which has a WebSocket heartbeat bug (default 40s < chunk timeout 60s) that tears down uploads mid-flight as `WS halt (3)`. Keep the pin explicit so we never silently slide onto a broken `latest`. When upgrading, read the release notes for any public-API changes to `deploy()`, `DotNS` methods, or the `DeployOptions` we rely on (`jsMerkle`, `signer`, `signerAddress`, `mnemonic`, `rpc`, `attributes`). Note: 0.7.0 removed the `playground?: boolean` `DeployOption` (registry publishing now lives here in `src/utils/deploy/playground.ts`), which is a no-op for us since we never passed that flag. 0.7.1 made the memory-report teardown Bun-safe upstream. 0.7.2 bumped the default `CHUNK_TIMEOUT_MS` 60s → 180s to match Bulletin's new 24s Aura slot duration; `BULLETIN_CHUNK_TIMEOUT_MS` override still works. 0.7.4 extracted the dotns logic into a separate `@parity/dotns-cli` subprocess (forked via `_require.resolve("@parity/dotns-cli")`); see the publish-bug workaround note above. 0.7.4 also moved label classification off the `DotNS` instance — the previously-instance method `dotns.classifyName(label)` is now the top-level pure function `classifyDotnsLabel(label)`, and the result field renamed `requiredStatus` → `status`. The function isn't re-exported from the package root, so `src/utils/deploy/availability.ts` mirrors the (small, stable) logic locally as `classifyLabel` — same precedent as `simulateUserStatus`. 0.7.6 added ambient Sentry mode for host apps; keep the CLI-owned privacy gate in `src/bootstrap.ts`.
21+
- **`bulletin-deploy` 0.7.4+ pulls in a transitive dep with a broken publish manifest** that pnpm refuses to install. `@parity/dotns-cli@0.6.0`'s published `package.json` declares `"@polkadot-api/descriptors": "file:.papi/descriptors"` — a workspace-only path that doesn't exist in the published tarball. npm tolerates the dangling `file:` reference (creates a broken symlink and continues); pnpm's strict resolver fails with `ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND`. We work around it with a `pnpm.overrides` entry in `package.json` pointing the offending sub-dep at a tiny stub package (`stubs/papi-descriptors-stub/`) so resolution succeeds. The dep is functionally vestigial — dotns-cli's `dist/cli.js` is fully-bundled (Bun build, no externals) and never imports `@polkadot-api/descriptors` at runtime, so the stub exporting `{}` is correct. **Remove the override + stub once `@parity/dotns-cli` republishes a clean manifest.** Tracked upstream against `paritytech/dotns-sdk`.
22+
- **`bulletin-deploy` is pinned to an explicit version, not `latest`.** We're on `0.7.9` stable today. The `latest` npm dist-tag is a moving target and previously pointed at 0.6.8, which has a WebSocket heartbeat bug (default 40s < chunk timeout 60s) that tears down uploads mid-flight as `WS halt (3)`. Keep the pin explicit so we never silently slide onto a broken `latest`. When upgrading, read the release notes for any public-API changes to `deploy()`, `DotNS` methods, or the `DeployOptions` we rely on (`jsMerkle`, `signer`, `signerAddress`, `mnemonic`, `rpc`, `attributes`). Note: 0.7.0 removed the `playground?: boolean` `DeployOption` (registry publishing now lives here in `src/utils/deploy/playground.ts`), which is a no-op for us since we never passed that flag. 0.7.1 made the memory-report teardown Bun-safe upstream. 0.7.2 bumped the default `CHUNK_TIMEOUT_MS` 60s → 180s to match Bulletin's new 24s Aura slot duration; `BULLETIN_CHUNK_TIMEOUT_MS` override still works. 0.7.4 extracted the dotns logic into a separate `@parity/dotns-cli` subprocess (forked via `_require.resolve("@parity/dotns-cli")`); see the publish-bug workaround note above. 0.7.4 also moved label classification off the `DotNS` instance — the previously-instance method `dotns.classifyName(label)` is now the top-level pure function `classifyDotnsLabel(label)`, and the result field renamed `requiredStatus` → `status`. The function isn't re-exported from the package root, so `src/utils/deploy/availability.ts` mirrors the (small, stable) logic locally as `classifyLabel` — same precedent as `simulateUserStatus`. 0.7.6 added ambient Sentry mode for host apps; keep the CLI-owned privacy gate in `src/bootstrap.ts`. 0.7.9 includes the DotNS/deploy fixes needed by the CDM E2E path.
2323
- **Throttle TUI info updates** — bulletin-deploy logs per-chunk and builds (vite/next) stream thousands of lines/sec. Calling `setState` on every log event floods React's reconciler with so much backpressure the process can balloon past 20 GB and freeze the OS. `RunningStage` coalesces "latest info" updates to ≤10/sec via a ref + timer and caps line length at 160 chars. Any new hot-path event sink should do the same; don't hook raw per-line streams directly into Ink state.
24-
- **Process-guard safety net** (`src/utils/process-guard.ts`) — deploy pipelines open several long-lived WebSockets + child processes and any one of them can keep the event loop alive after the TUI visibly finishes, turning `dot` into a zombie that accumulates retry buffers indefinitely (seen climbing past 25 GB). We defend in depth: (1) `installSignalHandlers()` catches SIGINT/TERM/HUP + `unhandledRejection` and forces cleanup + exit within 3 s; (2) `scheduleHardExit()` installs an `unref`'d timer that kills the process if the event loop doesn't drain within a grace period; (3) `startMemoryWatchdog()` aborts if RSS exceeds 4 GB — a generous cap because legit deploys on Bun SEA binaries routinely touch 1–1.5 GB from runtime-metadata decoding + Bun's JSC heap + Ink yoga. Do NOT re-add a per-window growth detector: we tried 300 MB / 3 s and it false-positived on the single-burst metadata-loading spike, aborting deploys that would have succeeded. Set `DOT_MEMORY_TRACE=1` to stream per-sample RSS/heap/external stats — useful when diagnosing a real leak report. **Telemetry bootstrap** (`src/bootstrap.ts`) is the FIRST import in `src/index.ts`. It sets `BULLETIN_DEPLOY_USE_AMBIENT_SENTRY=1` and `BULLETIN_DEPLOY_HOST_APP=playground-cli` before `bulletin-deploy` can evaluate, then maps `DOT_TELEMETRY`/internal-context detection to `BULLETIN_DEPLOY_TELEMETRY`. Do not leave `BULLETIN_DEPLOY_TELEMETRY` unset while setting the host app: `bulletin-deploy@0.7.6` treats `playground-cli` as an internal host, which would enable deploy telemetry for external users. `BULLETIN_DEPLOY_MEM_REPORT` is not forced off by default anymore because upstream guards the Bun-incompatible memory-report path. Any new long-running command should register a cleanup hook via `onProcessShutdown()`.
24+
- **Process-guard safety net** (`src/utils/process-guard.ts`) — deploy pipelines open several long-lived WebSockets + child processes and any one of them can keep the event loop alive after the TUI visibly finishes, turning `dot` into a zombie that accumulates retry buffers indefinitely (seen climbing past 25 GB). We defend in depth: (1) `installSignalHandlers()` catches SIGINT/TERM/HUP + `unhandledRejection` and forces cleanup + exit within 3 s; (2) `scheduleHardExit()` installs an `unref`'d timer that kills the process if the event loop doesn't drain within a grace period; (3) `startMemoryWatchdog()` aborts if RSS exceeds 4 GB — a generous cap because legit deploys on Bun SEA binaries routinely touch 1–1.5 GB from runtime-metadata decoding + Bun's JSC heap + Ink yoga. Do NOT re-add a per-window growth detector: we tried 300 MB / 3 s and it false-positived on the single-burst metadata-loading spike, aborting deploys that would have succeeded. Set `DOT_MEMORY_TRACE=1` to stream per-sample RSS/heap/external stats — useful when diagnosing a real leak report. **Telemetry bootstrap** (`src/bootstrap.ts`) is the FIRST import in `src/index.ts`. It sets `BULLETIN_DEPLOY_USE_AMBIENT_SENTRY=1` and `BULLETIN_DEPLOY_HOST_APP=playground-cli` before `bulletin-deploy` can evaluate, then maps `DOT_TELEMETRY`/internal-context detection to `BULLETIN_DEPLOY_TELEMETRY`. Do not leave `BULLETIN_DEPLOY_TELEMETRY` unset while setting the host app: `bulletin-deploy` treats `playground-cli` as an internal host, which would enable deploy telemetry for external users. `BULLETIN_DEPLOY_MEM_REPORT` is not forced off by default anymore because upstream guards the Bun-incompatible memory-report path. Any new long-running command should register a cleanup hook via `onProcessShutdown()`.
2525
- **Parser MUST NOT emit an event per log line.** `DeployLogParser.feed()` is called for every console line bulletin-deploy prints — hundreds per deploy on the happy path, thousands if retries fire. We intentionally emit events ONLY for phase-banner matches and `[N/M]` chunk progress. Everything else returns `null`. Adding a catch-all `info` emit turns the parser into a firehose that allocates ~200 bytes × thousands of lines, and was a measurable contributor to chunk-upload memory pressure.
2626
- **`dot mod` is GitHub-tarball-only and must stay that way.** `src/utils/mod/source.ts` downloads from `codeload.github.com` (no auth, no `git`/`gh` required for the public-repo case) and extracts via `node:zlib` + the pure-JS `tar` package. Do NOT re-introduce `git clone` or `gh repo fork` paths — both would re-add a hard tooling requirement and the fork path was specifically removed because GitHub caps you to one fork per source-repo per account, which broke "mod the same starter twice." A non-modable app (no `metadata.repository`) returns a hard error from `dot mod`; the interactive picker filters those out so the user never sees an unmoddable option. The picker does NOT pre-probe each app's repo visibility, because that would burn the 60 req/hr anonymous GitHub API quota on every `dot mod`. Instead, `runModCommand` lazy-probes the picked app once via `assertPublicGitHubRepo()` between picker dismount and `SetupScreen` mount; `dot deploy --modable` already rejects private repos at deploy time, so this fires only when a publisher has flipped visibility post-publish.
2727
- **`ensureGhAuthed()` does NOT shell out to `gh auth login`.** Even from the interactive deploy, Ink owns stdout + raw-mode stdin and a `stdio: "inherit"` child would race Ink's `useInput` for keystrokes — producing garbled output and dropped key events. Both interactive and non-interactive paths that need GitHub repo creation fail with the same actionable message: run `gh auth login` once outside `dot`, then retry. Existing `origin` URLs do not require `gh` auth. Properly suspending Ink to hand off stdio is possible but requires unmounting + re-rendering with state preservation, and we deemed that complexity not worth it for a one-time-per-machine speedbump. If you ever do implement it, the wiring point is `ModablePreflightStage` in `src/commands/deploy/DeployScreen.tsx`.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ pnpm format:check # check only
197197

198198
- `@polkadot-apps/*` are pinned to `latest` intentionally — they are our own packages and we want the lockfile to track head.
199199
- `@polkadot-api/sdk-ink` is pinned to `^0.6.2` and `polkadot-api` to `^1.23.3` because `chain-client` currently embeds an internal `PolkadotClient` shape that breaks with newer versions. Bump together with `chain-client` only.
200-
- `bulletin-deploy` is pinned to an explicit version — not `latest`. Currently `0.7.6`. Previously `latest` pointed at 0.6.8 which had a WebSocket heartbeat bug (40s default < 60s chunk timeout) that tore chunk uploads down as `WS halt (3)`; keeping the pin explicit avoids ever sliding back onto that. When bumping, check the release notes for any changes to `deploy()` / `DotNS` APIs we rely on.
200+
- `bulletin-deploy` is pinned to an explicit version — not `latest`. Currently `0.7.10`. Previously `latest` pointed at 0.6.8 which had a WebSocket heartbeat bug (40s default < 60s chunk timeout) that tore chunk uploads down as `WS halt (3)`; keeping the pin explicit avoids ever sliding back onto that. When bumping, check the release notes for any changes to `deploy()` / `DotNS` APIs we rely on.
201201

202202
## Architecture Highlights
203203

e2e/cli/deploy.test.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -426,17 +426,9 @@ describe("dot deploy — rejects --no-contract-build with no artefacts", () => {
426426
});
427427
});
428428

429-
// SKIPPED: the rust-cdm fixture's `target/flipper.contract` is a stub
430-
// (`{"source":{"hash":"0xabc"}}`) and there is no `target/<crate>.release.polkavm`
431-
// for the skip-build path to read. A working fixture needs:
432-
// 1. a real `src/lib.rs` so `cargo metadata` parses the manifest
433-
// (currently fails: "no targets specified in the manifest")
434-
// 2. a committed `target/<crate>.release.polkavm` produced by an
435-
// actual `cargo-contract build` of a minimal flipper contract
436-
// Tracked as Phase 5 follow-up. Until then, CDM detection is covered by
437-
// the preflight test in `dot deploy — preflight and validation` and the
438-
// skip-build path itself is unit-tested in `src/utils/deploy/contracts.test.ts`.
439-
describe.skip("dot deploy — CDM (requires Paseo + IPFS)", () => {
429+
// CDM follows the same CI shape as foundry/hardhat: deploy pre-built artifacts
430+
// committed with the fixture, without requiring the Rust/PVM toolchain on CI.
431+
describe("dot deploy — cdm (requires Paseo + IPFS)", () => {
440432
test("CDM deploy completes end-to-end", { timeout: 450_000 }, async () => {
441433
const domain = E2E_DOMAINS.cdm;
442434
const result = await dot([

e2e/cli/fixtures/accounts.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ export const BOB: TestAccount = devAccount("Bob");
7474
* publish path (storage / re-deploy / cross-owner collision); reuse a single
7575
* domain for the preflight / validation tests.
7676
*
77+
* DotNS classifies names with a 6-8 character base plus exactly two trailing
78+
* digits as PopLite. Keep these labels in that shape so E2E tests do not
79+
* accidentally require Full personhood on testnet.
80+
*
7781
* NOTE: do not assert on the registry state of `preflight` — it's shared by
7882
* six tests in the same file and the metadata at any moment reflects whichever
7983
* one ran last. Stdout assertions are fine. If you need to assert on registry
@@ -86,28 +90,28 @@ export const E2E_DOMAINS = {
8690
* tests do reach `registry.publish`. SIGNER ends up owning this domain
8791
* regardless; subsequent runs are same-owner re-publishes.
8892
*/
89-
preflight: "e2e-cli-preflight",
93+
preflight: "e2epre00",
9094
/** Used by the storage-phase happy path. */
91-
storage: "e2e-cli-storage",
95+
storage: "e2estr00",
9296
/** Used by the same-owner re-deploy test. */
93-
redeploy: "e2e-cli-redeploy",
97+
redeploy: "e2ered00",
9498
/** Used by the cross-owner collision test (BOB tries to take SIGNER's). */
95-
collision: "e2e-cli-collision",
99+
collision: "e2ecol00",
96100
/**
97101
* Phase 3 cell domains — registered by `tools/register-e2e-fixtures.ts`.
98102
* Owned by SIGNER; subsequent runs are same-owner re-publishes.
99103
* Not yet wired to any test — see Phase 4 of docs-internal/2026-05-02-e2e-test-suite-design.md.
100104
*/
101-
foundry: "e2e-cli-foundry",
102-
cdm: "e2e-cli-cdm",
103-
hardhat: "e2e-cli-hardhat",
104-
multi: "e2e-cli-multi",
105+
foundry: "e2efnd00",
106+
cdm: "e2ecdm00",
107+
hardhat: "e2ehat00",
108+
multi: "e2emul00",
105109
/**
106110
* Used by the nightly-chaos-sigint cell only. The deploy is interrupted by
107111
* SIGINT before it completes, so this domain is never actually registered.
108112
* It is kept separate from `storage` to avoid any race with the happy-path
109113
* storage test in test-publish when both run in a nightly that triggers all
110114
* matrices.
111115
*/
112-
chaos: "e2e-cli-chaos",
116+
chaos: "e2echs00",
113117
} as const;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[build]
2+
target = ".cargo/riscv64emac-unknown-none-polkavm.json"
3+
4+
[unstable]
5+
build-std = ["core", "alloc"]
6+
json-target-spec = true
7+
8+
[env]
9+
RUSTC_BOOTSTRAP = "1"

0 commit comments

Comments
 (0)