From 4306269c2b5a007b3847ed2a2622e8a330d8848b Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Tue, 10 Jan 2023 17:48:55 -0500 Subject: [PATCH] work on zombienet provider --- codegen/codecs.ts | 4 +- examples/xcm_teleport_assets.ts | 6 +- server/provider/PolkadotDev.test.ts | 4 +- server/provider/PolkadotDev.ts | 34 +++++---- server/provider/Wss.test.ts | 4 +- server/provider/Wss.ts | 43 ++++++----- server/provider/Zombienet.test.ts | 13 ++++ server/provider/Zombienet.ts | 107 ++++++++++++++++++++++------ server/provider/common/Frame.ts | 21 +++--- 9 files changed, 162 insertions(+), 74 deletions(-) create mode 100644 server/provider/Zombienet.test.ts diff --git a/codegen/codecs.ts b/codegen/codecs.ts index 3ebff3a3a..2501cead3 100644 --- a/codegen/codecs.ts +++ b/codegen/codecs.ts @@ -139,9 +139,7 @@ export function codecs(ctx: CodegenCtx) { return file function addCodecDecl(ty: Ty, value: string) { - if (ty.path.length > 1) { - namespaceImports.add(ty.path[0]!) - } + if (ty.path.length > 1) namespaceImports.add(ty.path[0]!) file.code += `export const $${ty.id}: $.Codec<${ctx.typeVisitor.visit(ty)}> = ${value}\n\n` return `$${ty.id}` } diff --git a/examples/xcm_teleport_assets.ts b/examples/xcm_teleport_assets.ts index bdd21b6ef..219ba1cbf 100644 --- a/examples/xcm_teleport_assets.ts +++ b/examples/xcm_teleport_assets.ts @@ -1,9 +1,7 @@ -// This example requires zombienet-macos/zombienet-linux, polkadot and polkadot-parachain binaries in the PATH - import * as C from "capi/mod.ts" -import { client as relayChainClient } from "http://localhost:8000/zombienet/examples/xcm_teleport_assets.toml#alice/_/client.ts" -import { client as parachainClient } from "http://localhost:8000/zombienet/examples/xcm_teleport_assets.toml#collator01/_/client.ts" +import { client as relayChainClient } from "http://localhost:8000/zombienet/examples/xcm_teleport_assets.toml@v9.36.0#alice/_/client.ts" +import { client as parachainClient } from "http://localhost:8000/zombienet/examples/xcm_teleport_assets.toml@v9.36.0#collator01/_/client.ts" const teleportAssetsTx = C.extrinsic(relayChainClient)({ sender: C.alice.address, diff --git a/server/provider/PolkadotDev.test.ts b/server/provider/PolkadotDev.test.ts index 04939d526..7a4e921ea 100644 --- a/server/provider/PolkadotDev.test.ts +++ b/server/provider/PolkadotDev.test.ts @@ -3,10 +3,10 @@ import { parsePolkadotDevPathInfo } from "./PolkadotDev.ts" Deno.test("Polkadot Dev Path Info Parsing", () => { assertEquals(parsePolkadotDevPathInfo("polkadot@version/mod.ts"), { - discoveryValue: "polkadot", + chainKey: "polkadot@version", + runtimeName: "polkadot", version: "version", filePath: "mod.ts", - key: "polkadot@version", ext: ".ts", }) }) diff --git a/server/provider/PolkadotDev.ts b/server/provider/PolkadotDev.ts index 4702b1296..e5b193454 100644 --- a/server/provider/PolkadotDev.ts +++ b/server/provider/PolkadotDev.ts @@ -3,19 +3,21 @@ import { outdent } from "../../deps/outdent.ts" import { extname } from "../../deps/std/path.ts" import { Client, proxyProvider } from "../../rpc/mod.ts" import * as port from "../../util/port.ts" -import { FrameProvider, FrameProviderPathInfo } from "./common/mod.ts" +import { FramePathInfo, FrameProvider } from "./common/mod.ts" -export type PolkadotDevPathInfo = FrameProviderPathInfo +export interface PolkadotDevPathInfo extends FramePathInfo { + runtimeName: DevRuntimeName +} export interface PolkadotDevProviderProps { polkadotPath?: string additional?: string[] } -export class PolkadotDevProvider extends FrameProvider { - #devNets: Partial> = {} +export class PolkadotDevProvider extends FrameProvider { + devNets: Partial> = {} - constructor(readonly props: PolkadotDevProviderProps = {}) { + constructor(readonly props?: PolkadotDevProviderProps) { super() } @@ -52,8 +54,8 @@ export class PolkadotDevProvider extends FrameProvider { return `ws://localhost:${port_}` } - devNet({ discoveryValue }: PolkadotDevPathInfo) { - let port_ = this.#devNets[discoveryValue] + devNet({ runtimeName }: PolkadotDevPathInfo) { + let port_ = this.devNets[runtimeName] if (!port_) { port_ = port.getAvailable() const polkadotPath_ = this.props?.polkadotPath ?? "polkadot" @@ -64,7 +66,8 @@ export class PolkadotDevProvider extends FrameProvider { port_.toString(), ...this.props?.additional ?? [], ] - if (discoveryValue !== "polkadot") cmd.push(`--force-${discoveryValue}`) + if (runtimeName !== "polkadot") cmd.push(`--force-${runtimeName}`) + if (this.props?.additional) cmd.push(...this.props.additional) try { const process = Deno.run({ cmd, @@ -76,7 +79,7 @@ export class PolkadotDevProvider extends FrameProvider { await process.status() process.close() }) - this.#devNets[discoveryValue] = port_ + this.devNets[runtimeName] = port_ } catch (_e) { console.log(POLKADOT_PATH_NOT_FOUND) Deno.exit(1) @@ -86,17 +89,18 @@ export class PolkadotDevProvider extends FrameProvider { } } +// TODO: auto installation prompt? const POLKADOT_PATH_NOT_FOUND = "The Polkadot CLI was not found. Please ensure Polkadot is installed and PATH is set for `polkadot`." - + `For more information, visit the following link: "https://github.com/paritytech/polkadot".` + + ` For more information, visit the following link: "https://github.com/paritytech/polkadot".` export function parsePolkadotDevPathInfo(path: string): PolkadotDevPathInfo { const atI = path.search("@") if (atI == -1) throw new Error(`Could not find "@" char in path`) - const discoveryValue = path.slice(0, atI) - if (!isDevRuntimeName(discoveryValue)) { + const runtimeName = path.slice(0, atI) + if (!isDevRuntimeName(runtimeName)) { throw new Error( - `"${discoveryValue}" is not a valid dev runtime name. Please specify one of the following: "${ + `"${runtimeName}" is not a valid dev runtime name. Please specify one of the following: "${ DEV_RUNTIME_NAMES.join(`", "`) }"`, ) @@ -105,10 +109,10 @@ export function parsePolkadotDevPathInfo(path: string): PolkadotDevPathInfo { const slashI0 = atTrailing.search("/") if (slashI0 == -1) throw new Error("Could not extract chain path") const version = atTrailing.slice(0, slashI0) - const key = path.slice(0, atI + 1 + slashI0) + const chainKey = path.slice(0, atI + 1 + slashI0) const filePath = atTrailing.slice(slashI0 + 1) const ext = extname(filePath) as Ext - return { key, discoveryValue, version, filePath, ext } + return { chainKey, runtimeName, version, filePath, ext } } export const DEV_RUNTIME_NAMES = ["polkadot", "kusama", "westend", "rococo"] as const diff --git a/server/provider/Wss.test.ts b/server/provider/Wss.test.ts index 4eadb0ef6..474619e01 100644 --- a/server/provider/Wss.test.ts +++ b/server/provider/Wss.test.ts @@ -3,10 +3,10 @@ import { parseWssPathInfo } from "./Wss.ts" Deno.test("Wss Path Info Parsing", () => { assertEquals(parseWssPathInfo("rpc.polkadot.io@version/mod.ts"), { - discoveryValue: "rpc.polkadot.io", + chainKey: "rpc.polkadot.io@version", + protocolTrailing: "rpc.polkadot.io", version: "version", filePath: "mod.ts", - key: "rpc.polkadot.io@version", ext: ".ts", }) }) diff --git a/server/provider/Wss.ts b/server/provider/Wss.ts index 3b0c7aa8e..b059e1419 100644 --- a/server/provider/Wss.ts +++ b/server/provider/Wss.ts @@ -1,33 +1,44 @@ import { Ext, File } from "../../codegen/mod.ts" -import { extname } from "../../deps/std/path.ts" +import { outdent } from "../../deps/outdent.ts" +import * as path from "../../deps/std/path.ts" import { Client, proxyProvider } from "../../rpc/mod.ts" -import { FrameProvider, FrameProviderPathInfo } from "./common/mod.ts" +import { FramePathInfo, FrameProvider } from "./common/mod.ts" -export type WssPathInfo = FrameProviderPathInfo +export interface WssPathInfo extends FramePathInfo { + protocolTrailing: string +} -export class WssProvider extends FrameProvider { +export class WssProvider extends FrameProvider { parsePathInfo = parseWssPathInfo - client({ discoveryValue }: WssPathInfo) { - return new Client(proxyProvider, `wss://${discoveryValue}`) + url({ protocolTrailing }: WssPathInfo) { + return `wss://${protocolTrailing}` + } + + client(pathInfo: WssPathInfo) { + return new Client(proxyProvider, this.url(pathInfo)) } - clientFile() { + clientFile(pathInfo: WssPathInfo) { const clientFile = new File() - clientFile.code = `export const client = null!` + clientFile.code = outdent` + import * as C from "../capi.ts" + + export const client = new C.Client(C.rpc.proxyProvider, "${this.url(pathInfo)}") + ` return clientFile } } -export function parseWssPathInfo(path: string): FrameProviderPathInfo { - const atI = path.search("@") - if (atI == -1) throw new Error(`Expected "@" character and version to appear in URL`) - const discoveryValue = path.slice(0, atI) - const atTrailing = path.slice(atI + 1) +export function parseWssPathInfo(path_: string): WssPathInfo { + const atI = path_.search("@") + if (atI == -1) throw new Error(`Expected "@" character to appear in URL`) + const protocolTrailing = path_.slice(0, atI) + const atTrailing = path_.slice(atI + 1) const slashI = atTrailing.search("/") const version = atTrailing.slice(0, slashI) const filePath = atTrailing.slice(slashI + 1) - const key = path.slice(0, atI + 1 + slashI) - const ext = extname(filePath) as Ext - return { key, discoveryValue, version, filePath, ext } + const chainKey = path_.slice(0, atI + 1 + slashI) + const ext = path.extname(filePath) as Ext + return { chainKey, protocolTrailing, version, filePath, ext } } diff --git a/server/provider/Zombienet.test.ts b/server/provider/Zombienet.test.ts new file mode 100644 index 000000000..8cb46dce3 --- /dev/null +++ b/server/provider/Zombienet.test.ts @@ -0,0 +1,13 @@ +import { assertEquals } from "../../deps/std/testing/asserts.ts" +import { parseZombienetPathInfo } from "./Zombienet.ts" + +Deno.test("Zombienet Path Info Parsing", () => { + assertEquals(parseZombienetPathInfo("examples/xcm_teleport_assets.toml#alice@version/mod.ts"), { + chainKey: "examples/xcm_teleport_assets.toml#alice@version", + configPath: "examples/xcm_teleport_assets.toml", + chainId: "alice", + version: "version", + filePath: "mod.ts", + ext: ".ts", + }) +}) diff --git a/server/provider/Zombienet.ts b/server/provider/Zombienet.ts index dd56fd3fd..241197abe 100644 --- a/server/provider/Zombienet.ts +++ b/server/provider/Zombienet.ts @@ -1,5 +1,89 @@ -// TODO -export {} +import { Ext } from "../../codegen/mod.ts" +import * as path from "../../deps/std/path.ts" +import { FramePathInfo, FrameProvider } from "./common/mod.ts" + +export interface ZombienetPathInfo extends FramePathInfo { + configPath: string + chainId: string +} + +export interface ZombienetProviderProps { + zombienetPath?: string + additional?: string[] +} + +export class ZombienetProvider extends FrameProvider { + zombienetTmp = Deno.makeTempDirSync({ prefix: "capi_zombienet_" }) + zombienets: Record = {} + + constructor(readonly props?: ZombienetProviderProps) { + super() + } + + parsePathInfo = parseZombienetPathInfo + + client(pathInfo: ZombienetPathInfo) {} + + clientFile(pathInfo: ZombienetPathInfo) {} + + url(pathInfo: ZombienetPathInfo) {} + + zombienet(pathInfo: ZombienetPathInfo) { + const cmd: string[] = [ + this.props?.zombienetPath ?? "zombienet", + "-d", + this.zombienetTmp, + "--provider", + "native", + "--force", + "spawn", + pathInfo.configPath, + ] + if (this.props?.additional) cmd.push(...this.props.additional) + try { + const process = Deno.run({ + cmd, + stdout: "piped", + stderr: "piped", + }) + this.ctx.signal.addEventListener("abort", async () => { + process.kill("SIGINT") + await process.status() + process.close() + Deno.remove(this.zombienetTmp, { recursive: true }) + }) + } catch (_e) { + console.log(ZOMBIENET_PATH_NOT_FOUND) + Deno.exit(1) + } + } +} + +export function parseZombienetPathInfo(path_: string): ZombienetPathInfo { + const atI = path_.search("@") + if (atI == -1) throw new Error(`Expected "@" character to appear in URL`) + const leading = path_.slice(0, atI) + const hashI = leading.search("#") + if (hashI == -1) throw new Error(`Expected "#" character to appear in URL`) + const configPath = leading.slice(0, hashI) + const chainId = leading.slice(hashI + 1) + const trailing = path_.slice(atI + 1) + const slashI = trailing.search("/") + if (slashI == -1) throw new Error() + const version = trailing.slice(0, slashI) + const filePath = trailing.slice(slashI + 1) + const chainKey = path_.slice(0, atI + 1 + slashI) + const ext = path.extname(filePath) as Ext + return { chainId, chainKey, configPath, ext, filePath, version } +} + +// TODO: auto installation prompt? +const ZOMBIENET_PATH_NOT_FOUND = + "The Zombienet CLI was not found. Please ensure Zombienet is installed and PATH is set for `zombienet`." + + ` For more information, visit the following link: "https://github.com/paritytech/zombienet".` + +// import { client as relayChainClient } from "http://localhost:8000/zombienet/examples/xcm_teleport_assets.toml#alice/_/client.ts" +// import { client as parachainClient } from "http://localhost:8000/zombienet/examples/xcm_teleport_assets.toml#collator01/_/client.ts" // import * as Z from "../../deps/zones.ts" // import * as rpc from "../../rpc/mod.ts" @@ -71,22 +155,3 @@ export {} // } // return { close, config, clients } // } - -// export class NodeClientEffect extends Z.Effect, Error> { -// constructor(readonly url: string) { -// super({ -// kind: "Client", -// impl: Z -// .call(() => { -// try { -// return new rpc.Client(rpc.proxyProvider, url) -// } catch (e) { -// return e -// } -// }) -// .impl, -// items: [url], -// memoize: true, -// }) -// } -// } diff --git a/server/provider/common/Frame.ts b/server/provider/common/Frame.ts index b97cc2e98..5501145a6 100644 --- a/server/provider/common/Frame.ts +++ b/server/provider/common/Frame.ts @@ -4,20 +4,19 @@ import { Client } from "../../../rpc/mod.ts" import * as U from "../../../util/mod.ts" import { Provider } from "./Base.ts" -export interface FrameProviderPathInfo { - key: string - discoveryValue: DiscoveryValue +export interface FramePathInfo { + chainKey: string version: string filePath: string ext: Ext } -export abstract class FrameProvider extends Provider { +export abstract class FrameProvider extends Provider { codegenCtxPendings: Record> = {} - abstract parsePathInfo(path: string): FrameProviderPathInfo - abstract client(pathInfo: FrameProviderPathInfo): U.PromiseOr - abstract clientFile(pathInfo: FrameProviderPathInfo): File + abstract parsePathInfo(path: string): FramePathInfo + abstract client(pathInfo: FramePathInfo): U.PromiseOr + abstract clientFile(pathInfo: FramePathInfo): File async run(req: Request, path: string) { const pathInfo = this.parsePathInfo(path) @@ -27,8 +26,8 @@ export abstract class FrameProvider extends Provider { return this.ctx.code(req, pathInfo.filePath, file.code) } - codegenCtx(pathInfo: FrameProviderPathInfo) { - let codegenCtxPending = this.codegenCtxPendings[pathInfo.key] + codegenCtx(pathInfo: FramePathInfo) { + let codegenCtxPending = this.codegenCtxPendings[pathInfo.chainKey] if (!codegenCtxPending) { codegenCtxPending = (async () => { const client = await this.client(pathInfo) @@ -50,10 +49,10 @@ export abstract class FrameProvider extends Provider { this.postInitCodegenCtx(codegenCtx, pathInfo) return codegenCtx })() - this.codegenCtxPendings[pathInfo.key] = codegenCtxPending + this.codegenCtxPendings[pathInfo.chainKey] = codegenCtxPending } return codegenCtxPending } - postInitCodegenCtx(_codegenCtx: CodegenCtx, _pathInfo: FrameProviderPathInfo) {} + postInitCodegenCtx(_codegenCtx: CodegenCtx, _pathInfo: FramePathInfo) {} }