diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c664f34..44dfe04 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,11 +7,37 @@ on: description: "git ref to checkout to" type: string default: "main" + nox-image: + description: "nox image tag" + type: string + default: "null" + fcli-version: + description: "fcli version to use" + type: string + # default: "unstable" + default: "main" + js-client-version: + description: "@fluencelabs/js-client version" + type: string + default: "null" + fluence-env: + description: "env to run tests against" + type: string + default: "local" jobs: tests: - name: "build" - runs-on: ubuntu-latest + name: "Run tests" + runs-on: builder + + env: + FLUENCE_USER_DIR: "${{ github.workspace }}/tmp/.fluence" + CI: true + # FORCE_COLOR: true + + permissions: + contents: read + id-token: write steps: - name: Checkout repository @@ -20,11 +46,86 @@ jobs: repository: fluencelabs/frpc ref: ${{ inputs.ref }} - - name: Install fluence-cli@unstable - run: npm i -g @fluencelabs/cli@0.11.1 + - name: Import secrets + uses: hashicorp/vault-action@v2.5.0 + with: + url: https://vault.fluence.dev + path: jwt/github + role: ci + method: jwt + jwtGithubAudience: "https://github.com/fluencelabs" + jwtTtl: 300 + secrets: | + kv/docker-registry/basicauth/ci username | DOCKER_USERNAME ; + kv/docker-registry/basicauth/ci password | DOCKER_PASSWORD ; + kv/npm-registry/basicauth/ci token | NODE_AUTH_TOKEN ; + kv/ci/frpc chain_urls | RPC_PROVIDERS ; + kv/ci/frpc private_key | FLUENCE_CHAIN_PRIVATE_KEY + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + registry: docker.fluence.dev + username: ${{ env.DOCKER_USERNAME }} + password: ${{ env.DOCKER_PASSWORD }} + + - name: Setup Rust toolchain + uses: dsherret/rust-toolchain-file@v1 + + - name: Setup fcli + uses: fluencelabs/setup-fluence@v1 + with: + artifact: fcli + version: ${{ inputs.fcli-version }} + + - name: Init local env with fcli + run: fluence local init --no-input + + - name: Replace nox image in docker-compose + if: inputs.nox-image != 'null' + working-directory: .fluence + run: | + sed -i'' -e '/nox-/!b;n;s|image: fluencelabs/nox:.*$|image: ${{ inputs.nox-image }}|' docker-compose.yaml + + - name: Run local env + run: fluence local up + + - name: Setup node with self-hosted registry + uses: actions/setup-node@v3 + with: + node-version: "18" + registry-url: "https://npm.fluence.dev" + cache: "npm" + + - name: Run npm i + run: npm i + + - name: Run npm i in gateway + working-directory: gateway + run: npm i + + - name: Set js-client version + if: inputs.js-client-version != 'null' + uses: fluencelabs/github-actions/npm-set-dependency@main + with: + package: "@fluencelabs/js-client" + version: "${{ inputs.js-client-version }}" + working-directory: gateway + + - name: Run npm run build + run: npm run build + + - name: Run tests + env: + FLUENCE_ENV: ${{ inputs.fluence-env }} + run: npm run test -- -t deploy - - name: Check if main.aqua compiles - run: fluence aqua --dry + - name: Dump container logs + if: always() + uses: jwalton/gh-docker-logs@v2 - - name: Check if services builds - run: fluence build + - name: Cleanup + if: always() + run: | + fluence local down + rm -rf tmp ${{ env.FLUENCE_USER_DIR }} diff --git a/.gitignore b/.gitignore index 2c76f1d..bfb83a0 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,11 @@ src/js/src/aqua aqua-compiled # recommended by Fluence Labs: -/.fluence/project-secrets.yaml -/.fluence/schemas -/.fluence/tmp +.fluence/project-secrets.yaml +.fluence/docker-compose.yaml +.fluence/schemas +.fluence/secrets +.fluence/tmp # to avoid accidental publish of RPC URLs gateway/configs/quickstart_config.json diff --git a/README.md b/README.md index cb8c594..e495d9f 100644 --- a/README.md +++ b/README.md @@ -507,6 +507,55 @@ Fluence CLI did a bunch of work for us behind the scenes and signing the transac Note that the deal's section in [fluence.yaml](./fluence.yaml) specifies the minimum and maximum workers that should be deployed under the *defaultWorker* namespace. Fluence CLI currently provides default values for min and max workers of one (1) and three (3), respectively. In the near future, you will be able to provide your spot price for service execution, hosting targets in the form of named capacity providers and more. +After successful deal deployment it is possible to retrieve logs for the deployed deal. + +**Command**: + +```bash +fluence deal logs +``` + +**Output**: + +```bash +Connecting to random stage relay: /dns4/0-stage.fluence.dev/tcp/9000/wss/p2p/12D3KooWDcpWuyrMTDinqNgmXAuRdfd2mTdY9VoXZSAet2pDzh6r +Connected +defaultWorker (host_id: 12D3KooWMMGdfVEJ1rWe1nH1nehYDzNEHhg5ogdfiGk88AupCMnf, worker_id: 12D3KooWGctQEUKcgWBetu9aiR3owMZcBGNcpDC5ZE3H6dL16uSP, spell_id: 679acf1c-57e2-4dd7-aa78-bb181df7a00a): + +2023-10-25 14:41:48 Installing worker for deal 0x02ab47b7b2737e16a516421c1b8ad36475e0f7ce +2023-10-25 14:41:48 parsed worker definition bafkreifp4gbp3emepswptldwlpbhpybt47uy2c3ksm3y7rut6cmcdnljwa { + "services": [ + { + "modules": [ + { + "config": "bafkreia2wftbxfd4blycnvlxw2yl7ibhan2g7vauexv7fspibodlu34que", + "wasm": "bafkreiarl3nin4jtauc52k76h4ze7yekvc5d2uno5fkgpotmcekwm7cnqa" + }, + { + "config": "bafkreiaclbxbmtydpwdcpoh2yggcd6uimicmbb6rxzab7bgp342w5vcz2m", + "wasm": "bafybeieeemeldllgokrkgybbrrjqeehyin3blv5cgehhdp3nlrfyj4eqoa" + } + ], + "name": "eth_rpc" + } + ], + "spells": [] +} +2023-10-25 14:41:50 Created service eth_rpc 2b5967ae-e5f9-4929-8668-d2039593af28 +2023-10-25 14:41:50 Installation finished +2023-10-25 14:41:50 Worker installation finished with status { + "message": "", + "state": "INSTALLATION_SUCCESSFUL", + "timestamp": 1698244910 +} + +defaultWorker (host_id: 12D3KooWJ4bTHirdTFNZpCS72TAzwtdmavTBkkEXtzo6wHL25CtE, worker_id: unknown, spell_id: unknown): Worker is not installed yet + +defaultWorker (host_id: 12D3KooWAKNos2KogexTXhrkMZzFYpLHuWJ4PgoAhurSAv7o5CWA, worker_id: unknown, spell_id: unknown): Worker is not installed yet +``` + +In the example output above, we see that the worker for the *defaultWorker* namespace was installed successfully. The worker is now ready to receive requests from the gateway. The other two workers are not installed yet. It should happen in a while and one can check the logs again to see the progress. + ### fRPC Aqua Code Now that we have our services deployed and ready for action, it's time to look at Aqua, which is utilized by the Gateway to bridge HTTP to/from libp2p. Let's have a look at the Aqua code and structure. diff --git a/fluence.yaml b/fluence.yaml index 724af0f..17d08de 100644 --- a/fluence.yaml +++ b/fluence.yaml @@ -4,16 +4,21 @@ # Documentation: https://github.com/fluencelabs/fluence-cli/tree/main/docs/configs/fluence.md -version: 2 +version: 4 + aquaInputPath: src/aqua/main.aqua + workers: defaultWorker: services: [ eth_rpc ] + deals: defaultWorker: minWorkers: 3 targetWorkers: 3 + relays: stage + services: eth_rpc: get: wasm-modules diff --git a/gateway/aqua/rpc.aqua b/gateway/aqua/rpc.aqua index 328f39b..aaf7561 100644 --- a/gateway/aqua/rpc.aqua +++ b/gateway/aqua/rpc.aqua @@ -42,7 +42,6 @@ func balancedEthCall{Logger, Balancer}(method: string, jsonArgs: []string) -> Js worker, provider <- Balancer.next() Logger.logWorker(worker) Logger.logCall(provider) - Op.noop() -- dirty hack for topology to converge rpc <- fromWorkerProvider(worker, provider) result <- rpcCall{rpc}(method, jsonArgs) <- result diff --git a/gateway/package-lock.json b/gateway/package-lock.json index 65818ab..c37a87d 100644 --- a/gateway/package-lock.json +++ b/gateway/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.18", "license": "Apache-2.0", "dependencies": { - "@fluencelabs/js-client": "0.3.0", + "@fluencelabs/js-client": "0.4.2", "@fluencelabs/marine-worker": "0.3.3", "body-parser": "1.20.2", "express": "4.18.2", @@ -625,15 +625,17 @@ } }, "node_modules/@fluencelabs/js-client": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@fluencelabs/js-client/-/js-client-0.3.0.tgz", - "integrity": "sha512-mRlEeoDEAsHK4GK3vIlNBkXgfJ01maQ4WVefob4QNEqpshipf6XQpU6R8dpUsjyhx53nus3ui6BSUV6gi5jg8A==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@fluencelabs/js-client/-/js-client-0.4.2.tgz", + "integrity": "sha512-Sho12XpyGcWAxOK0jUeO2Mh0ur2xJWTL7rCVAIT+QR7KlghlrFP1jdw4OrDCRlEJR4bhdPtI1PPkDo3Gk+axGQ==", "dependencies": { "@chainsafe/libp2p-noise": "13.0.0", "@chainsafe/libp2p-yamux": "5.0.0", - "@fluencelabs/avm": "0.52.0", + "@fluencelabs/avm": "0.54.0", "@fluencelabs/interfaces": "0.8.2", - "@fluencelabs/marine-worker": "0.4.0", + "@fluencelabs/js-client-isomorphic": "0.2.1", + "@fluencelabs/marine-worker": "0.4.1", + "@fluencelabs/threads": "^2.0.0", "@libp2p/crypto": "2.0.3", "@libp2p/interface": "0.1.2", "@libp2p/peer-id": "3.0.2", @@ -652,7 +654,6 @@ "libp2p": "0.46.6", "multiformats": "11.0.1", "rxjs": "7.5.5", - "threads": "github:fluencelabs/threads.js#b00a5342380b0278d3ae56dcfb170effb3cad7cd", "ts-pattern": "3.3.3", "uint8arrays": "4.0.3", "uuid": "8.3.2", @@ -663,19 +664,45 @@ "pnpm": ">=8" } }, + "node_modules/@fluencelabs/js-client-isomorphic": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fluencelabs/js-client-isomorphic/-/js-client-isomorphic-0.2.1.tgz", + "integrity": "sha512-o9uhjV2db7YvGHv38qXYecJmMlwnkiHqMX7dBAA8Z+89/J4lepvQydP9zU+qJHRGPcBjH4yPtnV4oEBXZbdhMg==", + "dependencies": { + "@fluencelabs/avm": "0.54.0", + "@fluencelabs/marine-js": "0.7.2", + "@fluencelabs/marine-worker": "0.4.1", + "@fluencelabs/threads": "^2.0.0" + } + }, + "node_modules/@fluencelabs/js-client-isomorphic/node_modules/@fluencelabs/avm": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@fluencelabs/avm/-/avm-0.54.0.tgz", + "integrity": "sha512-5GgROVly/vC7gasltr6/3TIY8vfV6b+SPfWUAGWnyXdbWt4jJANLO2YtXdaUsdNk9PiwOep7TMjLnypljdyMjQ==" + }, + "node_modules/@fluencelabs/js-client-isomorphic/node_modules/@fluencelabs/marine-worker": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@fluencelabs/marine-worker/-/marine-worker-0.4.1.tgz", + "integrity": "sha512-BnCOaAnzi3koFFHGy3955uYllI3TmQN++eI/0s5mou4pMZdF2H6qc40mXzgQb8dADpuE9cqRwQ2/0Ecwn5B3dg==", + "dependencies": { + "@fluencelabs/marine-js": "0.7.2", + "@fluencelabs/threads": "^2.0.0", + "observable-fns": "0.6.1" + } + }, "node_modules/@fluencelabs/js-client/node_modules/@fluencelabs/avm": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@fluencelabs/avm/-/avm-0.52.0.tgz", - "integrity": "sha512-T+/Hv/ZPfwWZAC4tH6wEDIRmtN6cTBebqbCaHfaq2PDLSMG0BgssdFF2BAaEXrvOvJbI5Bu/9bQhHv4ga7bYlA==" + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@fluencelabs/avm/-/avm-0.54.0.tgz", + "integrity": "sha512-5GgROVly/vC7gasltr6/3TIY8vfV6b+SPfWUAGWnyXdbWt4jJANLO2YtXdaUsdNk9PiwOep7TMjLnypljdyMjQ==" }, "node_modules/@fluencelabs/js-client/node_modules/@fluencelabs/marine-worker": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@fluencelabs/marine-worker/-/marine-worker-0.4.0.tgz", - "integrity": "sha512-nWri+j8Ey4UXoB32NPKsmVYzUKj6mwD7vh/5MjzCxrnVthnWnFdnkETF2BnZwjZWc701xeVhF3L5ZSjiQzKywQ==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@fluencelabs/marine-worker/-/marine-worker-0.4.1.tgz", + "integrity": "sha512-BnCOaAnzi3koFFHGy3955uYllI3TmQN++eI/0s5mou4pMZdF2H6qc40mXzgQb8dADpuE9cqRwQ2/0Ecwn5B3dg==", "dependencies": { "@fluencelabs/marine-js": "0.7.2", - "observable-fns": "0.6.1", - "threads": "github:fluencelabs/threads.js#b00a5342380b0278d3ae56dcfb170effb3cad7cd" + "@fluencelabs/threads": "^2.0.0", + "observable-fns": "0.6.1" } }, "node_modules/@fluencelabs/marine-js": { @@ -699,6 +726,23 @@ "threads": "1.7.0" } }, + "node_modules/@fluencelabs/threads": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@fluencelabs/threads/-/threads-2.0.0.tgz", + "integrity": "sha512-dgYpZg55OcEmop1U3G2bFKEJXg2avjXWYfWsdPlkSbHOHguaRifvr5bgwIYTg1wxoPGcn0jegcjKKwrY0qrV+g==", + "dependencies": { + "callsites": "^3.1.0", + "debug": "^4.2.0", + "is-observable": "^2.1.0", + "observable-fns": "^0.6.1" + }, + "funding": { + "url": "https://github.com/andywer/threads.js?sponsor=1" + }, + "optionalDependencies": { + "tiny-worker": ">= 2" + } + }, "node_modules/@libp2p/crypto": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-2.0.3.tgz", @@ -2517,17 +2561,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "dependencies": { - "get-intrinsic": "^1.2.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-property-descriptors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", diff --git a/gateway/package.json b/gateway/package.json index 03a9a05..53b5d87 100644 --- a/gateway/package.json +++ b/gateway/package.json @@ -5,9 +5,8 @@ "main": "src/index.js", "type": "module", "scripts": { - "run:example": "fluence aqua -i aqua/ -o aqua-compiled/ --js && node src/index.js \"config.json\"", - "run": "fluence aqua -i aqua/ -o aqua-compiled/ --js && node src/index.js", - "compile": "fluence aqua -i aqua/ -o aqua-compiled/ --js", + "run": "npm run compile && node src/index.js", + "compile": "fluence aqua -i aqua/ -o aqua-compiled/ --js --no-input", "req": "node web3run.js" }, "bin": { @@ -21,7 +20,7 @@ "author": "Fluence Labs", "license": "Apache-2.0", "dependencies": { - "@fluencelabs/js-client": "0.3.0", + "@fluencelabs/js-client": "0.4.2", "@fluencelabs/marine-worker": "0.3.3", "body-parser": "1.20.2", "express": "4.18.2", diff --git a/test/fRPC.integration-test.ts b/test/fRPC.integration-test.ts index 8240cf6..e8d9747 100644 --- a/test/fRPC.integration-test.ts +++ b/test/fRPC.integration-test.ts @@ -143,7 +143,7 @@ describe("fRPC", () => { "provider", "add-peer", ...providerPeers.flatMap((id) => ["--peer-id", id]), - "--units", + "--compute-units", "1", ); @@ -166,7 +166,7 @@ describe("fRPC", () => { const workersMatch = stderr.match(/(\d+)\s*workers/); const workers = - workersMatch?.[1] ?? throwError("Failed to parse workers"); + workersMatch?.[1] ?? throwError(`Failed to parse workers.`); const workersNum = parseInt(workers); expect(workersNum).toBeGreaterThanOrEqual(3); @@ -186,7 +186,10 @@ describe("fRPC", () => { } if (Date.now() > deadline) { throw new Error( - `Deployment timeout: ${workersNum} workers expected, ${deployed.length} deployed`, + `Deployment timeout: ${workersNum} workers expected, + ${deployed.length} deployed. + workers: ${JSON.stringify(workers)} + `, ); } } diff --git a/test/utils.ts b/test/utils.ts index 59e0861..6c5ae19 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -16,7 +16,7 @@ import { relative } from "path"; import { promises as fs } from "fs"; -import { execFile, ChildProcess } from "child_process"; +import { ChildProcess, execFile } from "child_process"; import treeKill from "tree-kill"; @@ -76,6 +76,7 @@ export async function subnet(env: string): Promise { export class Gateway { private stdout: string = ""; + private stderr: string = ""; constructor( private readonly gateway: ChildProcess, @@ -84,6 +85,9 @@ export class Gateway { gateway.stdout?.on("data", (data: any) => { this.stdout += data; }); + gateway.stderr?.on("data", (data: any) => { + this.stderr += data; + }); } public async stop(): Promise { @@ -109,7 +113,7 @@ export class Gateway { } else { resolve(); } - }), + }) ); } return this.gateway.kill(); @@ -131,6 +135,9 @@ export class Gateway { public getStdout(): string { return this.stdout; } + public getStderr(): string { + return this.stderr; + } } export async function startGateway(mode?: string): Promise {