From c2bfad7f794eb24dc7325bfc08bfdc82a953e333 Mon Sep 17 00:00:00 2001 From: Pavel Date: Mon, 18 Apr 2022 15:38:19 +0300 Subject: [PATCH] Marine-js stage 2: move avm-related helpers to appropriate package (#239) --- avm/client/.gitignore | 5 -- avm/client/package-lock.json | 16 ---- avm/client/package.json | 8 +- avm/client/src/avmHelpers.ts | 169 +++++++++++++++++++++++++++++++++++ avm/client/src/copyAvm.ts | 33 ------- avm/client/src/index.ts | 18 ++++ avm/client/src/types.ts | 122 +++++++++++++++++++++++++ 7 files changed, 311 insertions(+), 60 deletions(-) create mode 100644 avm/client/src/avmHelpers.ts delete mode 100644 avm/client/src/copyAvm.ts create mode 100644 avm/client/src/index.ts create mode 100644 avm/client/src/types.ts diff --git a/avm/client/.gitignore b/avm/client/.gitignore index a4a7b44c..b24d4f55 100644 --- a/avm/client/.gitignore +++ b/avm/client/.gitignore @@ -2,8 +2,3 @@ dist node_modules wasm *.tgz - -# this file is auto-generated -src/wasm.js -src/importObject.ts -src/avm.wasm diff --git a/avm/client/package-lock.json b/avm/client/package-lock.json index e74431ac..83cd5cff 100644 --- a/avm/client/package-lock.json +++ b/avm/client/package-lock.json @@ -8,20 +8,10 @@ "name": "@fluencelabs/avm", "version": "0.0.0", "license": "Apache 2.0", - "bin": { - "copy-avm": "dist/copyAvm.js" - }, "devDependencies": { - "@types/node": "^14.0.0", "typescript": "^4.0.0" } }, - "node_modules/@types/node": { - "version": "14.18.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.9.tgz", - "integrity": "sha512-j11XSuRuAlft6vLDEX4RvhqC0KxNxx6QIyMXNb0vHHSNPXTPeiy3algESWmOOIzEtiEL0qiowPU3ewW9hHVa7Q==", - "dev": true - }, "node_modules/typescript": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", @@ -37,12 +27,6 @@ } }, "dependencies": { - "@types/node": { - "version": "14.18.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.9.tgz", - "integrity": "sha512-j11XSuRuAlft6vLDEX4RvhqC0KxNxx6QIyMXNb0vHHSNPXTPeiy3algESWmOOIzEtiEL0qiowPU3ewW9hHVa7Q==", - "dev": true - }, "typescript": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", diff --git a/avm/client/package.json b/avm/client/package.json index ee18dd67..0110f215 100644 --- a/avm/client/package.json +++ b/avm/client/package.json @@ -2,23 +2,19 @@ "name": "@fluencelabs/avm", "description": "Aquamarine VM", "version": "0.0.0", - "main": "./dist/avm.wasm", + "main": "./dist/index.js", "repository": "https://github.com/fluencelabs/air", "author": "Fluence Labs", "license": "Apache 2.0", "files": [ "dist/*" ], - "bin": { - "copy-avm": "./dist/copyAvm.js" - }, "scripts": { "build": "tsc && ./build_wasm.sh" }, "private": false, "dependencies": {}, "devDependencies": { - "typescript": "^4.0.0", - "@types/node": "^14.0.0" + "typescript": "^4.0.0" } } diff --git a/avm/client/src/avmHelpers.ts b/avm/client/src/avmHelpers.ts new file mode 100644 index 00000000..c2630c03 --- /dev/null +++ b/avm/client/src/avmHelpers.ts @@ -0,0 +1,169 @@ +/* + * Copyright 2022 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CallResultsArray, InterpreterResult, CallRequest } from './types'; + +const decoder = new TextDecoder(); +const encoder = new TextEncoder(); + +/** + * Serializes AVM arguments in JSON string which can be passed into marine-js + * @param initPeerId - peer ID which initialized particle + * @param currentPeerId - peer ID which is currently executing the particle + * @param air - particle's air script as string + * @param prevData - particle's prev data as raw byte array + * @param data - particle's data as raw byte array + * @param callResults - array of tuples [callResultKey, callResult] + * @returns AVM call arguments as serialized JSON string + */ +export function serializeAvmArgs( + initPeerId: string, + currentPeerId: string, + air: string, + prevData: Uint8Array, + data: Uint8Array, + callResults: CallResultsArray, +): string { + const callResultsToPass: any = {}; + for (let [key, callResult] of callResults) { + callResultsToPass[key] = { + ret_code: callResult.retCode, + result: callResult.result, + }; + } + + const paramsToPass = { + init_peer_id: initPeerId, + current_peer_id: currentPeerId, + }; + + const encoded = encoder.encode(JSON.stringify(callResultsToPass)); + + const avmArg = JSON.stringify([ + // force new line + air, + Array.from(prevData), + Array.from(data), + paramsToPass, + Array.from(encoded), + ]); + + return avmArg; +} + +/** + * Deserializes raw result of AVM call obtained from marine-js into structured form + * @param rawResult - string containing raw result of AVM call + * @returns structured InterpreterResult + */ +export function deserializeAvmResult(rawResult: string): InterpreterResult { + let result: any; + try { + result = JSON.parse(rawResult); + } catch (ex) { + throw 'call_module result parsing error: ' + ex + ', original text: ' + rawResult; + } + + if (result.error !== '') { + throw 'call_module returned error: ' + result.error; + } + + result = result.result; + + const callRequestsStr = decoder.decode(new Uint8Array(result.call_requests)); + let parsedCallRequests; + try { + if (callRequestsStr.length === 0) { + parsedCallRequests = {}; + } else { + parsedCallRequests = JSON.parse(callRequestsStr); + } + } catch (e) { + throw "Couldn't parse call requests: " + e + '. Original string is: ' + callRequestsStr; + } + + let resultCallRequests: Array<[key: number, callRequest: CallRequest]> = []; + for (const key in parsedCallRequests) { + const callRequest = parsedCallRequests[key]; + + let arguments_; + let tetraplets; + try { + arguments_ = JSON.parse(callRequest.arguments); + } catch (e) { + throw "Couldn't parse arguments: " + e + '. Original string is: ' + arguments_; + } + + try { + tetraplets = JSON.parse(callRequest.tetraplets); + } catch (e) { + throw "Couldn't parse tetraplets: " + e + '. Original string is: ' + tetraplets; + } + + resultCallRequests.push([ + key as any, + { + serviceId: callRequest.service_id, + functionName: callRequest.function_name, + arguments: arguments_, + tetraplets: tetraplets, + }, + ]); + } + return { + retCode: result.ret_code, + errorMessage: result.error_message, + data: result.data, + nextPeerPks: result.next_peer_pks, + callRequests: resultCallRequests, + }; +} + +type CallToAvm = ((args: string) => Promise) | ((args: string) => string); + +/** + * Utility function which serializes AVM args and passed them into AVM returning interpreter result. + * Call to AVM is delegated to a function which must be provided by user. + * It might be either synchronous or asynchronous (returning a promise) + * @param fn - delegated call to AVM + * @param initPeerId - peer ID which initialized particle + * @param currentPeerId - peer ID which is currently executing the particle + * @param air - particle's air script as string + * @param prevData - particle's prev data as raw byte array + * @param data - particle's data as raw byte array + * @param callResults - array of tuples [callResultKey, callResult] + * @returns structured InterpreterResult + */ +export async function callAvm( + fn: CallToAvm, + initPeerId: string, + currentPeerId: string, + air: string, + prevData: Uint8Array, + data: Uint8Array, + callResults: CallResultsArray, +): Promise { + try { + const avmArg = serializeAvmArgs(initPeerId, currentPeerId, air, prevData, data, callResults); + const rawResult = await fn(avmArg); + return deserializeAvmResult(rawResult); + } catch (e) { + return { + retCode: -1, + errorMessage: 'marine-js call failed, ' + e, + } as any; + } +} diff --git a/avm/client/src/copyAvm.ts b/avm/client/src/copyAvm.ts deleted file mode 100644 index 3418d28d..00000000 --- a/avm/client/src/copyAvm.ts +++ /dev/null @@ -1,33 +0,0 @@ -#! /usr/bin/env node - -import fs from 'fs'; -import path from 'path'; - -const firstArgument = process.argv[2]; - -if (!firstArgument) { - console.log(`Expected exactly 1 argument, got 0. Usage: ${path.basename(process.argv[1])} `); - process.exit(1); -} - -let destPath = firstArgument; -if (!path.isAbsolute(destPath)) { - destPath = path.join(process.cwd(), destPath); -} - -const wasmName = 'avm.wasm'; -const packageName = '@fluencelabs/avm'; - -const modulePath = require.resolve(packageName); -const source = path.join(path.dirname(modulePath), wasmName); -const dest = path.join(destPath, wasmName); - -console.log('ensure directory exists: ', destPath); -fs.mkdirSync(destPath, { recursive: true }); - -console.log('copying AVM wasm'); -console.log('from: ', source); -console.log('to: ', dest); -fs.copyFileSync(source, dest); - -console.log('done!'); diff --git a/avm/client/src/index.ts b/avm/client/src/index.ts new file mode 100644 index 00000000..97576c0b --- /dev/null +++ b/avm/client/src/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2022 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './types'; +export * from './avmHelpers'; diff --git a/avm/client/src/types.ts b/avm/client/src/types.ts new file mode 100644 index 00000000..1563f313 --- /dev/null +++ b/avm/client/src/types.ts @@ -0,0 +1,122 @@ +/* + * Copyright 2022 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type LogLevel = 'info' | 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'off'; + +/** + * Represents an executed host function result. + */ +export interface CallServiceResult { + /** + * A error code service or builtin returned, where 0 represents success. + */ + retCode: number; + + /** + * Serialized return value from the service. + */ + result: string; +} + +/** + * Contains arguments of a call instruction and all other necessary information required for calling a service. + */ +export interface CallRequest { + /** + * Id of a service that should be called. + */ + serviceId: string; + + /** + * Name of a function from service identified by service_id that should be called. + */ + functionName: string; + + /** + * Arguments that should be passed to the service. + */ + arguments: any[]; + + /** + * Security tetraplets that should be passed to the service. + */ + tetraplets: SecurityTetraplet[][]; +} + +export type CallRequestsArray = Array<[key: number, callRequest: CallRequest]>; + +export type CallResultsArray = Array<[key: number, callServiceResult: CallServiceResult]>; + +/** + * Describes a result returned at the end of the interpreter execution_step. + */ +export interface InterpreterResult { + /** + * A return code, where 0 means success. + */ + retCode: number; + + /** + * Contains error message if ret_code != 0 + */ + errorMessage: string; + + /** + * Contains script data that should be preserved in an executor of this interpreter regardless of ret_code value. + */ + data: Uint8Array; + + /** + * Public keys of peers that should receive data. + */ + nextPeerPks: Array; + + /** + * Collected parameters of all met call instructions that could be executed on a current peer. + */ + callRequests: CallRequestsArray; +} + +/** + * ResolvedTriplet represents peer network location with all variables, literals and etc resolved into final string. + * This structure contains a subset of values that SecurityTetraplet consists of. + */ +export interface ResolvedTriplet { + /** + * Id of a peer where corresponding value was set. + */ + peer_pk: string; + + /** + * Id of a service that set corresponding value. + */ + service_id: string; + + /** + * Name of a function that returned corresponding value. + */ + function_name: string; +} + +/** + * Describes an origin that set corresponding value. + */ +export interface SecurityTetraplet extends ResolvedTriplet { + /** + * Value was produced by applying this `json_path` to the output from `call_service`. + */ + json_path: string; +}