From 5954e43942f026da05e8f711c3762fe7d3444446 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Wed, 3 Dec 2025 10:48:51 +0100 Subject: [PATCH 1/4] Add Zombienet battery station upgrade tests and update runtime upgrade logic --- .github/workflows/integration-tests.yml | 56 +++++++++++++++++++ integration-tests/pnpmfile.cjs | 35 ++++++++++++ .../test-zombienet-runtime-upgrade.ts | 53 +++++++++++++++--- 3 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 integration-tests/pnpmfile.cjs diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index f3c58ea0a..dc780c268 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -159,6 +159,62 @@ jobs: cd integration-tests pnpm exec moonwall test zombienet_zeitgeist_upgrade --runInBand + zombienet_battery_station_upgrade: + name: Battery Station Zombienet Post-Upgrade Tests + runs-on: ubuntu-22.04 + needs: ["build_parachain"] + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install build tools + run: ./scripts/init.sh + + - uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/setup-node@v3 + with: + node-version: 20.x + cache: "pnpm" + cache-dependency-path: "./integration-tests/pnpm-lock.yaml" + + - name: Install pnpm packages + run: | + cd integration-tests + pnpm install + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: Create local folders + run: | + mkdir -p target/release/wbuild/battery-station-runtime/ + mkdir -p integration-tests/tmp + + - name: Download runtime + uses: actions/download-artifact@v4.1.8 + with: + name: runtimes + path: target/release/wbuild/battery-station-runtime/ + + - name: Download binary + uses: actions/download-artifact@v4.1.8 + with: + name: binaries + path: target/release + + - name: Display structure of downloaded files + run: ls -R + working-directory: target/ + + - name: Test battery-station runtime upgrade using Zombienet + run: | + chmod uog+x target/release/zeitgeist + + cd integration-tests + pnpm exec moonwall test zombienet_battery_station_upgrade --runInBand + chopsticks_battery_station_upgrade: name: Battery Station Chopsticks Post-Upgrade Tests runs-on: ubuntu-22.04 diff --git a/integration-tests/pnpmfile.cjs b/integration-tests/pnpmfile.cjs new file mode 100644 index 000000000..7d6fa206d --- /dev/null +++ b/integration-tests/pnpmfile.cjs @@ -0,0 +1,35 @@ +/** + * Force polkadot-api ws-provider imports to use the Node build instead of the web build. + * + * Moonwall pulls in `polkadot-api/ws-provider/web`, which expects a global WebSocket. + * Here we rewrite the web reexports to point at the node entry so CI can run under Node 20 + * without shimming globals. + */ +const redirectToNode = (pkg) => { + const swap = (value) => + typeof value === "string" ? value.replace("ws-provider_web", "ws-provider_node") : value; + pkg.module = swap(pkg.module); + pkg.import = swap(pkg.import); + pkg.browser = swap(pkg.browser); + pkg.require = swap(pkg.require); + pkg.default = swap(pkg.default); +}; + +module.exports = { + hooks: { + readPackage(pkg) { + // polkadot-api reexports used by Moonwall + if (pkg.name === "polkadot-api_ws-provider_web") { + redirectToNode(pkg); + } + if (pkg.name === "polkadot-api_ws-provider") { + redirectToNode(pkg); + } + // direct package: keep web subpath aligned with node build + if (pkg.name === "@polkadot-api/ws-provider" && pkg.exports?.["./web"]) { + pkg.exports["./web"] = pkg.exports["./node"]; + } + return pkg; + }, + }, +}; diff --git a/integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts b/integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts index b93ac0e7e..d9acc81f8 100644 --- a/integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts +++ b/integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts @@ -117,32 +117,67 @@ describeSuite({ ); } - const txStatus = async (tx: any, label: string) => + const txStatus = async (tx: any, label: string, timeoutMs = 120_000) => new Promise((resolve, reject) => { let unsubscribe: (() => void) | undefined; + const timeout = setTimeout(() => { + const err = new Error(`${label} timed out waiting for inclusion`); + log(err.message); + unsubscribe?.(); + reject(err); + }, timeoutMs); + + const finish = (fn: (value?: any) => void, msg?: string, err?: any) => { + clearTimeout(timeout); + if (msg) { + log(msg); + } + unsubscribe?.(); + fn(err); + }; + tx.signAndSend(alice, (result: any) => { + const status = result.status; + if (result.dispatchError) { // Dispatch errors won't throw, so surface them explicitly. const errText = result.dispatchError.toString(); - log(`${label} dispatchError=${errText}`); - reject(new Error(`${label} failed: ${errText}`)); - unsubscribe?.(); + finish(reject, `${label} dispatchError=${errText}`, new Error(errText)); return; } + log( - `${label} status=${result.status?.type ?? "unknown"}, events=${result.events + `${label} status=${status?.type ?? "unknown"}, events=${result.events ?.map((ev: any) => `${ev.event.section}.${ev.event.method}`) .join(",")}` ); - if (result.status?.isInBlock || result.status?.isFinalized) { - unsubscribe?.(); - resolve(); + + if ( + status?.isDropped || + status?.isInvalid || + status?.isUsurped || + status?.isRetracted || + status?.isFinalityTimeout + ) { + finish( + reject, + `${label} failed with status=${status?.type ?? "unknown"}`, + new Error(`${label} failed with status=${status?.type ?? "unknown"}`) + ); + return; + } + + if (status?.isInBlock || status?.isFinalized) { + finish(resolve); } }) .then((unsub: () => void) => { unsubscribe = unsub; }) - .catch(reject); + .catch((err: any) => { + clearTimeout(timeout); + reject(err); + }); }); const findCall = (callName: string) => { From 494a12f36e3e6abc80c996618513260d34116a52 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Wed, 3 Dec 2025 12:47:57 +0100 Subject: [PATCH 2/4] Add .npmrc configuration and enhance pnpmfile for ws-provider handling --- integration-tests/.npmrc | 1 + integration-tests/pnpmfile.cjs | 32 ++++++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 integration-tests/.npmrc diff --git a/integration-tests/.npmrc b/integration-tests/.npmrc new file mode 100644 index 000000000..c15311917 --- /dev/null +++ b/integration-tests/.npmrc @@ -0,0 +1 @@ +use-pnpmfile=true diff --git a/integration-tests/pnpmfile.cjs b/integration-tests/pnpmfile.cjs index 7d6fa206d..755d8bfd6 100644 --- a/integration-tests/pnpmfile.cjs +++ b/integration-tests/pnpmfile.cjs @@ -2,7 +2,7 @@ * Force polkadot-api ws-provider imports to use the Node build instead of the web build. * * Moonwall pulls in `polkadot-api/ws-provider/web`, which expects a global WebSocket. - * Here we rewrite the web reexports to point at the node entry so CI can run under Node 20 + * We rewrite the "web" entry points to the Node build so CI can run under Node 20 * without shimming globals. */ const redirectToNode = (pkg) => { @@ -15,20 +15,44 @@ const redirectToNode = (pkg) => { pkg.default = swap(pkg.default); }; +const deepSwapToNode = (value) => { + if (typeof value === "string") { + return value + .replace(/ws-provider_web/g, "ws-provider_node") + .replace(/ws-provider(\b|$)/g, "ws-provider_node"); + } + if (value && typeof value === "object") { + return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, deepSwapToNode(v)])); + } + return value; +}; + module.exports = { hooks: { readPackage(pkg) { - // polkadot-api reexports used by Moonwall + // polkadot-api subpath packages that pnpm generates internally if (pkg.name === "polkadot-api_ws-provider_web") { redirectToNode(pkg); } if (pkg.name === "polkadot-api_ws-provider") { redirectToNode(pkg); } + // direct package: keep web subpath aligned with node build - if (pkg.name === "@polkadot-api/ws-provider" && pkg.exports?.["./web"]) { - pkg.exports["./web"] = pkg.exports["./node"]; + if (pkg.name === "@polkadot-api/ws-provider") { + const nodeExport = pkg.exports?.["./node"]; + if (nodeExport) { + pkg.exports["./web"] = nodeExport; + } } + + // top-level reexports: point web entry at the node build as well + if (pkg.name === "polkadot-api") { + if (pkg.exports?.["./ws-provider"]) { + pkg.exports["./ws-provider/web"] = deepSwapToNode(pkg.exports["./ws-provider"]); + } + } + return pkg; }, }, From 49250378fcc39484e8260541fef6f0854ab88d22 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Wed, 3 Dec 2025 13:45:18 +0100 Subject: [PATCH 3/4] Add WebSocket shim for Node environment and remove obsolete pnpmfile --- .github/workflows/integration-tests.yml | 4 ++ integration-tests/.npmrc | 1 - integration-tests/pnpmfile.cjs | 59 ------------------------- integration-tests/scripts/ws-shim.cjs | 4 ++ 4 files changed, 8 insertions(+), 60 deletions(-) delete mode 100644 integration-tests/.npmrc delete mode 100644 integration-tests/pnpmfile.cjs create mode 100644 integration-tests/scripts/ws-shim.cjs diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index dc780c268..2ba05a6ec 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -157,6 +157,7 @@ jobs: chmod uog+x target/release/zeitgeist cd integration-tests + export NODE_OPTIONS="--require ./scripts/ws-shim.cjs" pnpm exec moonwall test zombienet_zeitgeist_upgrade --runInBand zombienet_battery_station_upgrade: @@ -213,6 +214,7 @@ jobs: chmod uog+x target/release/zeitgeist cd integration-tests + export NODE_OPTIONS="--require ./scripts/ws-shim.cjs" pnpm exec moonwall test zombienet_battery_station_upgrade --runInBand chopsticks_battery_station_upgrade: @@ -261,6 +263,7 @@ jobs: - name: Battery Station post-upgrade tests using Chopsticks run: | cd integration-tests + export NODE_OPTIONS="--require ./scripts/ws-shim.cjs" pnpm exec moonwall test chopsticks_battery_station_upgrade --runInBand - name: Show chopsticks logs @@ -316,6 +319,7 @@ jobs: - name: Zeitgeist post-upgrade tests using Chopsticks run: | cd integration-tests + export NODE_OPTIONS="--require ./scripts/ws-shim.cjs" pnpm exec moonwall test chopsticks_zeitgeist_upgrade --runInBand - name: Show chopsticks logs diff --git a/integration-tests/.npmrc b/integration-tests/.npmrc deleted file mode 100644 index c15311917..000000000 --- a/integration-tests/.npmrc +++ /dev/null @@ -1 +0,0 @@ -use-pnpmfile=true diff --git a/integration-tests/pnpmfile.cjs b/integration-tests/pnpmfile.cjs deleted file mode 100644 index 755d8bfd6..000000000 --- a/integration-tests/pnpmfile.cjs +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Force polkadot-api ws-provider imports to use the Node build instead of the web build. - * - * Moonwall pulls in `polkadot-api/ws-provider/web`, which expects a global WebSocket. - * We rewrite the "web" entry points to the Node build so CI can run under Node 20 - * without shimming globals. - */ -const redirectToNode = (pkg) => { - const swap = (value) => - typeof value === "string" ? value.replace("ws-provider_web", "ws-provider_node") : value; - pkg.module = swap(pkg.module); - pkg.import = swap(pkg.import); - pkg.browser = swap(pkg.browser); - pkg.require = swap(pkg.require); - pkg.default = swap(pkg.default); -}; - -const deepSwapToNode = (value) => { - if (typeof value === "string") { - return value - .replace(/ws-provider_web/g, "ws-provider_node") - .replace(/ws-provider(\b|$)/g, "ws-provider_node"); - } - if (value && typeof value === "object") { - return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, deepSwapToNode(v)])); - } - return value; -}; - -module.exports = { - hooks: { - readPackage(pkg) { - // polkadot-api subpath packages that pnpm generates internally - if (pkg.name === "polkadot-api_ws-provider_web") { - redirectToNode(pkg); - } - if (pkg.name === "polkadot-api_ws-provider") { - redirectToNode(pkg); - } - - // direct package: keep web subpath aligned with node build - if (pkg.name === "@polkadot-api/ws-provider") { - const nodeExport = pkg.exports?.["./node"]; - if (nodeExport) { - pkg.exports["./web"] = nodeExport; - } - } - - // top-level reexports: point web entry at the node build as well - if (pkg.name === "polkadot-api") { - if (pkg.exports?.["./ws-provider"]) { - pkg.exports["./ws-provider/web"] = deepSwapToNode(pkg.exports["./ws-provider"]); - } - } - - return pkg; - }, - }, -}; diff --git a/integration-tests/scripts/ws-shim.cjs b/integration-tests/scripts/ws-shim.cjs new file mode 100644 index 000000000..5ec53eaa3 --- /dev/null +++ b/integration-tests/scripts/ws-shim.cjs @@ -0,0 +1,4 @@ +// Ensure a global WebSocket exists when running under Node. +if (typeof global.WebSocket === "undefined") { + global.WebSocket = require("ws"); +} From 9e881561fcba978c58eb6d178ab24b0d15124b34 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Wed, 3 Dec 2025 15:14:24 +0100 Subject: [PATCH 4/4] Add WebSocket setup for Node environment in integration tests --- integration-tests/tests/common-tests.ts | 1 + .../test-battery-station-chopsticks-runtime-upgrade.ts | 1 + .../test-zeitgeist-chopsticks-runtime-upgrade.ts | 1 + .../rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts | 1 + integration-tests/tests/setup-websocket.ts | 7 +++++++ 5 files changed, 11 insertions(+) create mode 100644 integration-tests/tests/setup-websocket.ts diff --git a/integration-tests/tests/common-tests.ts b/integration-tests/tests/common-tests.ts index 29983ffc0..def155d3f 100644 --- a/integration-tests/tests/common-tests.ts +++ b/integration-tests/tests/common-tests.ts @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +import "./setup-websocket"; import { expect, ChopsticksContext } from "@moonwall/cli"; import { generateKeyringPair } from "@moonwall/util"; import { ApiPromise, Keyring } from "@polkadot/api"; diff --git a/integration-tests/tests/rt-upgrade-battery-station-chopsticks/test-battery-station-chopsticks-runtime-upgrade.ts b/integration-tests/tests/rt-upgrade-battery-station-chopsticks/test-battery-station-chopsticks-runtime-upgrade.ts index f33973c74..9443caefe 100644 --- a/integration-tests/tests/rt-upgrade-battery-station-chopsticks/test-battery-station-chopsticks-runtime-upgrade.ts +++ b/integration-tests/tests/rt-upgrade-battery-station-chopsticks/test-battery-station-chopsticks-runtime-upgrade.ts @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +import "../setup-websocket"; import { MoonwallContext, beforeAll, diff --git a/integration-tests/tests/rt-upgrade-zeitgeist-chopsticks/test-zeitgeist-chopsticks-runtime-upgrade.ts b/integration-tests/tests/rt-upgrade-zeitgeist-chopsticks/test-zeitgeist-chopsticks-runtime-upgrade.ts index c73a0862b..1e97492d7 100644 --- a/integration-tests/tests/rt-upgrade-zeitgeist-chopsticks/test-zeitgeist-chopsticks-runtime-upgrade.ts +++ b/integration-tests/tests/rt-upgrade-zeitgeist-chopsticks/test-zeitgeist-chopsticks-runtime-upgrade.ts @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +import "../setup-websocket"; import { MoonwallContext, beforeAll, diff --git a/integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts b/integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts index d9acc81f8..51fd76763 100644 --- a/integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts +++ b/integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +import "../setup-websocket"; import { MoonwallContext, beforeAll, diff --git a/integration-tests/tests/setup-websocket.ts b/integration-tests/tests/setup-websocket.ts new file mode 100644 index 000000000..1edab3a5b --- /dev/null +++ b/integration-tests/tests/setup-websocket.ts @@ -0,0 +1,7 @@ +// Provide a global WebSocket for Node environments. +import WebSocket from "ws"; + +const g = globalThis as Record; +if (!g.WebSocket) { + g.WebSocket = WebSocket; +}