mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2025-03-15 07:20:49 +00:00
Update air (#47)
* Migrate to the new version of air-interpreter package * Add pipeline with integration tests * Fix issues which prevented tests to finish normally
This commit is contained in:
parent
2d46fd47f1
commit
9aa077eb4b
55
.github/workflows/js_sdk_ci.yml
vendored
55
.github/workflows/js_sdk_ci.yml
vendored
@ -1,39 +1,40 @@
|
|||||||
name: Node.js CI
|
name: Run tests
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: .
|
working-directory: .
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [14.x, 15.x]
|
||||||
|
node-env: ['', ':node']
|
||||||
|
|
||||||
strategy:
|
steps:
|
||||||
matrix:
|
- uses: actions/checkout@v2
|
||||||
node-version: [14.x, 15.x]
|
|
||||||
|
|
||||||
steps:
|
- uses: actions/cache@v2
|
||||||
- uses: actions/checkout@v2
|
with:
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-v1-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-v1-node-${{ matrix.node-version }}
|
||||||
|
|
||||||
- uses: actions/cache@v2
|
- name: Setup Node.js ${{ matrix.node-version }}
|
||||||
with:
|
uses: actions/setup-node@v1
|
||||||
path: ~/.npm
|
with:
|
||||||
key: ${{ runner.os }}-v1-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
node-version: ${{ matrix.node-version }}
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-v1-node-${{ matrix.node-version }}
|
|
||||||
|
|
||||||
- name: Setup Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
|
|
||||||
- run: npm install
|
|
||||||
- run: npm run test:unit
|
|
||||||
env:
|
|
||||||
CI: true
|
|
||||||
|
|
||||||
|
- name: Run container with Fluence node
|
||||||
|
run: docker run -d --rm -e RUST_LOG="info" -p 1210:1210 -p 4310:4310 fluencelabs/fluence -t 1210 -w 4310 -k gKdiCSUr1TFGFEgu2t8Ch1XEUsrN5A2UfBLjSZvfci9SPR3NvZpACfcpPGC3eY4zma1pk7UvYv5zb1VjvPHwCjj --local
|
||||||
|
|
||||||
|
- run: npm install
|
||||||
|
- run: npm run test${{ matrix.node-env }}:all
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
14
jest-patched-jsdom.js
Normal file
14
jest-patched-jsdom.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
const Environment = require('jest-environment-jsdom');
|
||||||
|
|
||||||
|
module.exports = class CustomTestEnvironment extends Environment {
|
||||||
|
async setup() {
|
||||||
|
await super.setup();
|
||||||
|
if (typeof this.global.TextEncoder === 'undefined') {
|
||||||
|
const { TextEncoder, TextDecoder } = require('util');
|
||||||
|
this.global.TextEncoder = TextEncoder;
|
||||||
|
this.global.TextDecoder = TextDecoder;
|
||||||
|
this.global.Uint8Array = Uint8Array;
|
||||||
|
this.global.ArrayBuffer = ArrayBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
preset: 'ts-jest',
|
preset: 'ts-jest',
|
||||||
testEnvironment: 'node',
|
testEnvironment: './jest-patched-jsdom.js',
|
||||||
};
|
};
|
||||||
|
18
package-lock.json
generated
18
package-lock.json
generated
@ -434,10 +434,20 @@
|
|||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@fluencelabs/aquamarine-interpreter": {
|
"@fluencelabs/air-interpreter": {
|
||||||
"version": "0.8.2",
|
"version": "0.9.7",
|
||||||
"resolved": "https://registry.npmjs.org/@fluencelabs/aquamarine-interpreter/-/aquamarine-interpreter-0.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/@fluencelabs/air-interpreter/-/air-interpreter-0.9.7.tgz",
|
||||||
"integrity": "sha512-WYn3fO3dy/ZTS2DnjpEv59RAFTuPosO65jejb/2QRAbZId4KiaCxma0yvFC6P0j2qoAxCDTRdbKrNZGXzjddYQ=="
|
"integrity": "sha512-9aJwfyfJ18BvSSFhTnq8SJZmIAgbyGXUf3vilsMhU6Iql35ZDd8E+5dF2hRkRrqUj2409w7qOtgLbB8JzdG/fw==",
|
||||||
|
"requires": {
|
||||||
|
"base64-js": "1.5.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"@istanbuljs/load-nyc-config": {
|
"@istanbuljs/load-nyc-config": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
|
14
package.json
14
package.json
@ -5,17 +5,21 @@
|
|||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"typings": "./dist/index.d.ts",
|
"typings": "./dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest --env=jsdom --watch",
|
"test": "jest --watch",
|
||||||
"test:all": "jest --env=jsdom",
|
"test:all": "jest",
|
||||||
"test:unit": "jest --env=jsdom --testPathPattern=src/__test__/unit",
|
"test:unit": "jest --testPathPattern=src/__test__/unit",
|
||||||
"test:integration": "jest --env=jsdom --testPathPattern=src/__test__/integration",
|
"test:integration": "jest --testPathPattern=src/__test__/integration",
|
||||||
|
"test:node": "jest --env=node --watch",
|
||||||
|
"test:node:all": "jest --env=node",
|
||||||
|
"test:node:unit": "jest --env=node --testPathPattern=src/__test__/unit",
|
||||||
|
"test:node:integration": "jest --env=node --testPathPattern=src/__test__/integration",
|
||||||
"build": "tsc"
|
"build": "tsc"
|
||||||
},
|
},
|
||||||
"repository": "https://github.com/fluencelabs/fluence-js",
|
"repository": "https://github.com/fluencelabs/fluence-js",
|
||||||
"author": "Fluence Labs",
|
"author": "Fluence Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fluencelabs/aquamarine-interpreter": "0.8.2",
|
"@fluencelabs/air-interpreter": "0.9.7",
|
||||||
"async": "3.2.0",
|
"async": "3.2.0",
|
||||||
"base64-js": "1.3.1",
|
"base64-js": "1.3.1",
|
||||||
"bs58": "4.0.1",
|
"bs58": "4.0.1",
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
/*
|
/*
|
||||||
* start docker container to run integration tests locally:
|
* start docker container to run integration tests locally:
|
||||||
|
|
||||||
docker run --rm -e RUST_LOG="info" -p 1210:1210 -p 4310:4310 fluencelabs/fluence -t 1210 -w 4310 -k gKdiCSUr1TFGFEgu2t8Ch1XEUsrN5A2UfBLjSZvfci9SPR3NvZpACfcpPGC3eY4zma1pk7UvYv5zb1VjvPHwCjj
|
docker run --rm -e RUST_LOG="info" -p 1210:1210 -p 4310:4310 fluencelabs/fluence -t 1210 -w 4310 -k gKdiCSUr1TFGFEgu2t8Ch1XEUsrN5A2UfBLjSZvfci9SPR3NvZpACfcpPGC3eY4zma1pk7UvYv5zb1VjvPHwCjj --local
|
||||||
|
|
||||||
*/
|
*/
|
||||||
export const nodes = [
|
export const nodes = [
|
||||||
|
@ -5,6 +5,12 @@ import { nodes } from '../connection';
|
|||||||
let client: FluenceClient;
|
let client: FluenceClient;
|
||||||
|
|
||||||
describe('Legacy api suite', () => {
|
describe('Legacy api suite', () => {
|
||||||
|
afterEach(async () => {
|
||||||
|
if (client) {
|
||||||
|
await client.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('sendParticle', async () => {
|
it('sendParticle', async () => {
|
||||||
client = await createClient(nodes[0]);
|
client = await createClient(nodes[0]);
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ describe('== AIR suite', () => {
|
|||||||
await client.initiateFlow(request);
|
await client.initiateFlow(request);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
await expect(error).rejects.toContain("aqua script can't be parsed");
|
await expect(error).rejects.toContain("air can't be parsed");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('call script without ttl', async function () {
|
it('call script without ttl', async function () {
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
import { AquamarineInterpreter } from '../../internal/aqua/interpreter';
|
import { AirInterpreter } from '@fluencelabs/air-interpreter';
|
||||||
|
import { genUUID } from '../../internal/particle';
|
||||||
|
|
||||||
describe('== AST parsing suite', () => {
|
describe('== AST parsing suite', () => {
|
||||||
it('parse simple script and return ast', async function () {
|
it('parse simple script and return ast', async function () {
|
||||||
const interpreter = await AquamarineInterpreter.create({} as any);
|
const interpreter = await AirInterpreter.create(
|
||||||
|
undefined as any,
|
||||||
|
undefined as any,
|
||||||
|
undefined as any,
|
||||||
|
undefined as any,
|
||||||
|
);
|
||||||
let ast = interpreter.parseAir(`
|
let ast = interpreter.parseAir(`
|
||||||
(call "node" ("service" "function") [1 2 3] output)
|
(call "node" ("service" "function") [1 2 3] output)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
console.log(ast);
|
|
||||||
ast = JSON.parse(ast);
|
ast = JSON.parse(ast);
|
||||||
|
|
||||||
expect(ast).toEqual({
|
expect(ast).toEqual({
|
||||||
|
@ -25,7 +25,7 @@ import { AquaCallHandler, errorHandler, fnHandler } from './AquaHandler';
|
|||||||
import { loadRelayFn, loadVariablesService } from './RequestFlowBuilder';
|
import { loadRelayFn, loadVariablesService } from './RequestFlowBuilder';
|
||||||
import { logParticle, Particle } from './particle';
|
import { logParticle, Particle } from './particle';
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
import { AquamarineInterpreter } from './aqua/interpreter';
|
import { AirInterpreter } from '@fluencelabs/air-interpreter';
|
||||||
|
|
||||||
const makeDefaultClientHandler = (): AquaCallHandler => {
|
const makeDefaultClientHandler = (): AquaCallHandler => {
|
||||||
const res = new AquaCallHandler();
|
const res = new AquaCallHandler();
|
||||||
@ -54,7 +54,7 @@ export class ClientImpl implements FluenceClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private connection: FluenceConnection;
|
private connection: FluenceConnection;
|
||||||
private interpreter: AquamarineInterpreter;
|
private interpreter: AirInterpreter;
|
||||||
|
|
||||||
constructor(selfPeerIdFull: PeerId) {
|
constructor(selfPeerIdFull: PeerId) {
|
||||||
this.selfPeerIdFull = selfPeerIdFull;
|
this.selfPeerIdFull = selfPeerIdFull;
|
||||||
@ -68,13 +68,18 @@ export class ClientImpl implements FluenceClient {
|
|||||||
await this.connection.disconnect();
|
await this.connection.disconnect();
|
||||||
}
|
}
|
||||||
this.clearWathcDog();
|
this.clearWathcDog();
|
||||||
|
this.requests.forEach((r) => {
|
||||||
|
r.cancel();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async initAquamarineRuntime(): Promise<void> {
|
async initAquamarineRuntime(): Promise<void> {
|
||||||
this.interpreter = await AquamarineInterpreter.create({
|
this.interpreter = await AirInterpreter.create(
|
||||||
particleHandler: this.interpreterCallback.bind(this),
|
this.interpreterCallback.bind(this),
|
||||||
peerId: this.selfPeerIdFull,
|
this.selfPeerId,
|
||||||
});
|
'trace',
|
||||||
|
log.log,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(multiaddr: string | Multiaddr, options?: FluenceConnectionOptions): Promise<void> {
|
async connect(multiaddr: string | Multiaddr, options?: FluenceConnectionOptions): Promise<void> {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import log, { trace } from 'loglevel';
|
import log, { trace } from 'loglevel';
|
||||||
import PeerId from 'peer-id';
|
import PeerId from 'peer-id';
|
||||||
import { AquamarineInterpreter } from './aqua/interpreter';
|
import { AirInterpreter } from '@fluencelabs/air-interpreter';
|
||||||
import { AquaCallHandler } from './AquaHandler';
|
import { AquaCallHandler } from './AquaHandler';
|
||||||
import { InterpreterOutcome, PeerIdB58 } from './commonTypes';
|
import { InterpreterOutcome, PeerIdB58 } from './commonTypes';
|
||||||
import { FluenceConnection } from './FluenceConnection';
|
import { FluenceConnection } from './FluenceConnection';
|
||||||
@ -20,6 +20,7 @@ export class RequestFlow {
|
|||||||
private prevData: Uint8Array = Buffer.from([]);
|
private prevData: Uint8Array = Buffer.from([]);
|
||||||
private onTimeoutHandlers = [];
|
private onTimeoutHandlers = [];
|
||||||
private onErrorHandlers = [];
|
private onErrorHandlers = [];
|
||||||
|
private timeoutHandle?: NodeJS.Timeout;
|
||||||
|
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly isExternal: boolean;
|
readonly isExternal: boolean;
|
||||||
@ -33,7 +34,7 @@ export class RequestFlow {
|
|||||||
const res = new RequestFlow(true, particle.id, particle.script);
|
const res = new RequestFlow(true, particle.id, particle.script);
|
||||||
res.ttl = particle.ttl;
|
res.ttl = particle.ttl;
|
||||||
res.state = particle;
|
res.state = particle;
|
||||||
setTimeout(res.raiseTimeout.bind(res), particle.ttl);
|
res.timeoutHandle = setTimeout(res.raiseTimeout.bind(res), particle.ttl);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +58,7 @@ export class RequestFlow {
|
|||||||
this.onErrorHandlers.push(handler);
|
this.onErrorHandlers.push(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(interpreter: AquamarineInterpreter, connection: FluenceConnection, relayPeerId?: PeerIdB58) {
|
async execute(interpreter: AirInterpreter, connection: FluenceConnection, relayPeerId?: PeerIdB58) {
|
||||||
if (this.hasExpired()) {
|
if (this.hasExpired()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -97,11 +98,18 @@ export class RequestFlow {
|
|||||||
|
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
this.raiseError('Cannot send particle: non connected');
|
this.raiseError('Cannot send particle: non connected');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sendIntoConnection(connection);
|
this.sendIntoConnection(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public cancel() {
|
||||||
|
if (this.timeoutHandle) {
|
||||||
|
clearTimeout(this.timeoutHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private throwIncorrectNextPeerPks(nextPeers: PeerIdB58[]) {
|
private throwIncorrectNextPeerPks(nextPeers: PeerIdB58[]) {
|
||||||
this.raiseError(
|
this.raiseError(
|
||||||
`Particle is expected to be sent to only the single peer (relay which client is connected to).
|
`Particle is expected to be sent to only the single peer (relay which client is connected to).
|
||||||
@ -127,7 +135,7 @@ relay peer id: ${this.relayPeerId}
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.state = particle;
|
this.state = particle;
|
||||||
setTimeout(this.raiseTimeout.bind(this), particle.ttl);
|
this.timeoutHandle = setTimeout(this.raiseTimeout.bind(this), particle.ttl);
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveUpdate(particle: Particle) {
|
receiveUpdate(particle: Particle) {
|
||||||
@ -145,7 +153,7 @@ relay peer id: ${this.relayPeerId}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runInterpreter(interpreter: AquamarineInterpreter) {
|
runInterpreter(interpreter: AirInterpreter) {
|
||||||
const interpreterOutcomeStr = interpreter.invoke(
|
const interpreterOutcomeStr = interpreter.invoke(
|
||||||
this.state.init_peer_id,
|
this.state.init_peer_id,
|
||||||
this.state.script,
|
this.state.script,
|
||||||
|
@ -1,162 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* This is generated and patched code. All functions are using local wasm as an argument for now, not a global wasm file.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function main(wasm) {
|
|
||||||
wasm.main();
|
|
||||||
}
|
|
||||||
|
|
||||||
export let WASM_VECTOR_LEN = 0;
|
|
||||||
|
|
||||||
let cachegetUint8Memory0 = null;
|
|
||||||
export function getUint8Memory0(wasm) {
|
|
||||||
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
|
|
||||||
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
|
|
||||||
}
|
|
||||||
return cachegetUint8Memory0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lTextEncoder = typeof TextEncoder === 'undefined' ? module.require('util').TextEncoder : TextEncoder;
|
|
||||||
|
|
||||||
let cachedTextEncoder = new lTextEncoder('utf-8');
|
|
||||||
|
|
||||||
const encodeString =
|
|
||||||
typeof cachedTextEncoder.encodeInto === 'function'
|
|
||||||
? function (arg, view) {
|
|
||||||
return cachedTextEncoder.encodeInto(arg, view);
|
|
||||||
}
|
|
||||||
: function (arg, view) {
|
|
||||||
const buf = cachedTextEncoder.encode(arg);
|
|
||||||
view.set(buf);
|
|
||||||
return {
|
|
||||||
read: arg.length,
|
|
||||||
written: buf.length,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function passStringToWasm0(wasm, arg, malloc, realloc) {
|
|
||||||
if (realloc === undefined) {
|
|
||||||
const buf = cachedTextEncoder.encode(arg);
|
|
||||||
const ptr = malloc(buf.length);
|
|
||||||
getUint8Memory0(wasm)
|
|
||||||
.subarray(ptr, ptr + buf.length)
|
|
||||||
.set(buf);
|
|
||||||
WASM_VECTOR_LEN = buf.length;
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
let len = arg.length;
|
|
||||||
let ptr = malloc(len);
|
|
||||||
|
|
||||||
const mem = getUint8Memory0(wasm);
|
|
||||||
|
|
||||||
let offset = 0;
|
|
||||||
|
|
||||||
for (; offset < len; offset++) {
|
|
||||||
const code = arg.charCodeAt(offset);
|
|
||||||
if (code > 0x7f) break;
|
|
||||||
mem[ptr + offset] = code;
|
|
||||||
}
|
|
||||||
if (offset !== len) {
|
|
||||||
if (offset !== 0) {
|
|
||||||
arg = arg.slice(offset);
|
|
||||||
}
|
|
||||||
ptr = realloc(ptr, len, (len = offset + arg.length * 3));
|
|
||||||
const view = getUint8Memory0(wasm).subarray(ptr + offset, ptr + len);
|
|
||||||
const ret = encodeString(arg, view);
|
|
||||||
|
|
||||||
offset += ret.written;
|
|
||||||
}
|
|
||||||
|
|
||||||
WASM_VECTOR_LEN = offset;
|
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cachegetInt32Memory0 = null;
|
|
||||||
export function getInt32Memory0(wasm) {
|
|
||||||
if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
|
|
||||||
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
|
|
||||||
}
|
|
||||||
return cachegetInt32Memory0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lTextDecoder = typeof TextDecoder === 'undefined' ? module.require('util').TextDecoder : TextDecoder;
|
|
||||||
|
|
||||||
let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
|
||||||
|
|
||||||
cachedTextDecoder.decode();
|
|
||||||
|
|
||||||
export function getStringFromWasm0(wasm, ptr, len) {
|
|
||||||
return cachedTextDecoder.decode(getUint8Memory0(wasm).subarray(ptr, ptr + len));
|
|
||||||
}
|
|
||||||
|
|
||||||
function passArray8ToWasm0(wasm, arg, malloc) {
|
|
||||||
const ptr = malloc(arg.length * 1);
|
|
||||||
getUint8Memory0(wasm).set(arg, ptr / 1);
|
|
||||||
WASM_VECTOR_LEN = arg.length;
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any} wasm
|
|
||||||
* @param {string} init_peer_id
|
|
||||||
* @param {string} aqua
|
|
||||||
* @param {string} prev_data
|
|
||||||
* @param {string} data
|
|
||||||
* @param {string} log_level
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export function invoke(wasm, init_peer_id, aqua, prev_data, data, log_level) {
|
|
||||||
try {
|
|
||||||
var ptr0 = passStringToWasm0(wasm, init_peer_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
||||||
var len0 = WASM_VECTOR_LEN;
|
|
||||||
var ptr1 = passStringToWasm0(wasm, aqua, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
||||||
var len1 = WASM_VECTOR_LEN;
|
|
||||||
var ptr2 = passArray8ToWasm0(wasm, prev_data, wasm.__wbindgen_malloc);
|
|
||||||
var len2 = WASM_VECTOR_LEN;
|
|
||||||
var ptr3 = passArray8ToWasm0(wasm, data, wasm.__wbindgen_malloc);
|
|
||||||
var len3 = WASM_VECTOR_LEN;
|
|
||||||
var ptr4 = passStringToWasm0(wasm, log_level, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
||||||
var len4 = WASM_VECTOR_LEN;
|
|
||||||
wasm.invoke(8, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4);
|
|
||||||
var r0 = getInt32Memory0(wasm)[8 / 4 + 0];
|
|
||||||
var r1 = getInt32Memory0(wasm)[8 / 4 + 1];
|
|
||||||
return getStringFromWasm0(wasm, r0, r1);
|
|
||||||
} finally {
|
|
||||||
wasm.__wbindgen_free(r0, r1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ast(wasm, script) {
|
|
||||||
try {
|
|
||||||
var ptr0 = passStringToWasm0(wasm, script, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
||||||
var len0 = WASM_VECTOR_LEN;
|
|
||||||
wasm.ast(8, ptr0, len0);
|
|
||||||
var r0 = getInt32Memory0(wasm)[8 / 4 + 0];
|
|
||||||
var r1 = getInt32Memory0(wasm)[8 / 4 + 1];
|
|
||||||
return getStringFromWasm0(wasm, r0, r1);
|
|
||||||
} finally {
|
|
||||||
wasm.__wbindgen_free(r0, r1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function return_current_peer_id(wasm, peerId, arg0) {
|
|
||||||
var ptr0 = passStringToWasm0(wasm, peerId, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
||||||
var len0 = WASM_VECTOR_LEN;
|
|
||||||
getInt32Memory0(wasm)[arg0 / 4 + 1] = len0;
|
|
||||||
getInt32Memory0(wasm)[arg0 / 4 + 0] = ptr0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function return_call_service_result(wasm, ret, arg0) {
|
|
||||||
var ptr1 = passStringToWasm0(wasm, ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
||||||
var len1 = WASM_VECTOR_LEN;
|
|
||||||
getInt32Memory0(wasm)[arg0 / 4 + 1] = len1;
|
|
||||||
getInt32Memory0(wasm)[arg0 / 4 + 0] = ptr1;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function free(wasm, ptr, len) {
|
|
||||||
wasm.__wbindgen_free(ptr, len);
|
|
||||||
}
|
|
@ -1,296 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 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 { toByteArray } from 'base64-js';
|
|
||||||
import * as aqua from '.';
|
|
||||||
import { return_current_peer_id, return_call_service_result, getStringFromWasm0, free } from '.';
|
|
||||||
import { ParticleHandler, CallServiceResult, SecurityTetraplet } from '../commonTypes';
|
|
||||||
|
|
||||||
import PeerId from 'peer-id';
|
|
||||||
import log from 'loglevel';
|
|
||||||
import wasmBs64 from '@fluencelabs/aquamarine-interpreter';
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
type InterpreterInvoke = (
|
|
||||||
init_peer_id: string,
|
|
||||||
script: string,
|
|
||||||
prev_data: Uint8Array,
|
|
||||||
data: Uint8Array,
|
|
||||||
) => string;
|
|
||||||
|
|
||||||
type ImportObject = {
|
|
||||||
'./aquamarine_client_bg.js': {
|
|
||||||
// fn call_service_impl(service_id: String, fn_name: String, args: String, security_tetraplets: String) -> String;
|
|
||||||
// prettier-ignore
|
|
||||||
__wbg_callserviceimpl_84d8278762e4c639: (arg0: any, arg1: any, arg2: any, arg3: any, arg4: any, arg5: any, arg6: any, arg7: any, arg8: any, ) => void;
|
|
||||||
__wbg_getcurrentpeeridimpl_4aca996e28cb8f44: (arg0: any) => void;
|
|
||||||
__wbindgen_throw: (arg: any) => void;
|
|
||||||
};
|
|
||||||
host: LogImport;
|
|
||||||
};
|
|
||||||
|
|
||||||
type LogImport = {
|
|
||||||
log_utf8_string: (level: any, target: any, offset: any, size: any) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Exports = any;
|
|
||||||
type Instance = any;
|
|
||||||
type ExportValue = any;
|
|
||||||
|
|
||||||
class HostImportsConfig {
|
|
||||||
exports: Exports | undefined;
|
|
||||||
newImportObject: () => ImportObject;
|
|
||||||
|
|
||||||
constructor(create: (cfg: HostImportsConfig) => ImportObject) {
|
|
||||||
this.exports = undefined;
|
|
||||||
this.newImportObject = () => create(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
setExports(exports: Exports) {
|
|
||||||
this.exports = exports;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const interpreter_wasm = toByteArray(wasmBs64);
|
|
||||||
|
|
||||||
/// Instantiates WebAssembly runtime with AIR interpreter module
|
|
||||||
async function interpreterInstance(cfg: HostImportsConfig): Promise<Instance> {
|
|
||||||
/// Create host imports that use module exports internally
|
|
||||||
let imports = cfg.newImportObject();
|
|
||||||
|
|
||||||
/// Instantiate interpreter
|
|
||||||
let interpreter_module = await WebAssembly.compile(interpreter_wasm);
|
|
||||||
let instance: Instance = await WebAssembly.instantiate(interpreter_module, imports);
|
|
||||||
|
|
||||||
/// Set exports, so host imports can use them
|
|
||||||
cfg.setExports(instance.exports);
|
|
||||||
|
|
||||||
/// Trigger interpreter initialization (i.e., call main function)
|
|
||||||
call_export(instance.exports.main);
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If export is a function, call it. Otherwise log a warning.
|
|
||||||
/// NOTE: any here is unavoidable, see Function interface definition
|
|
||||||
function call_export(f: ExportValue, ...argArray: any[]): any {
|
|
||||||
if (typeof f === 'function') {
|
|
||||||
return f();
|
|
||||||
} else {
|
|
||||||
log.warn(`can't call export ${f}: it is not a function, but ${typeof f}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function log_import(cfg: HostImportsConfig): LogImport {
|
|
||||||
return {
|
|
||||||
log_utf8_string: (level: any, target: any, offset: any, size: any) => {
|
|
||||||
let wasm = cfg.exports;
|
|
||||||
try {
|
|
||||||
let str = getStringFromWasm0(wasm, offset, size);
|
|
||||||
|
|
||||||
switch (level) {
|
|
||||||
case 1:
|
|
||||||
log.error(str);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
log.warn(str);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
log.info(str);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
log.debug(str);
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
// we don't want a trace in trace logs
|
|
||||||
log.debug(str);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const theParticleHandler = (
|
|
||||||
callback: ParticleHandler,
|
|
||||||
service_id: string,
|
|
||||||
fn_name: string,
|
|
||||||
args: string,
|
|
||||||
tetraplets: string,
|
|
||||||
): CallServiceResult => {
|
|
||||||
let argsObject;
|
|
||||||
let tetrapletsObject: SecurityTetraplet[][];
|
|
||||||
try {
|
|
||||||
argsObject = JSON.parse(args);
|
|
||||||
if (!Array.isArray(argsObject)) {
|
|
||||||
throw new Error('args is not an array');
|
|
||||||
}
|
|
||||||
|
|
||||||
tetrapletsObject = JSON.parse(tetraplets);
|
|
||||||
} catch (err) {
|
|
||||||
log.error('Cannot parse arguments: ' + JSON.stringify(err));
|
|
||||||
return {
|
|
||||||
result: JSON.stringify('Cannot parse arguments: ' + JSON.stringify(err)),
|
|
||||||
ret_code: 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(service_id, fn_name, argsObject, tetrapletsObject);
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Returns import object that describes host functions called by AIR interpreter
|
|
||||||
function newImportObject(particleHandler: ParticleHandler, cfg: HostImportsConfig, peerId: PeerId): ImportObject {
|
|
||||||
return {
|
|
||||||
// __wbg_callserviceimpl_c0ca292e3c8c0c97 this is a function generated by bindgen. Could be changed.
|
|
||||||
// If so, an error with a new name will be occurred after wasm initialization.
|
|
||||||
'./aquamarine_client_bg.js': {
|
|
||||||
// prettier-ignore
|
|
||||||
__wbg_callserviceimpl_84d8278762e4c639: (arg0: any, arg1: any, arg2: any, arg3: any, arg4: any, arg5: any, arg6: any, arg7: any, arg8: any) => {
|
|
||||||
let wasm = cfg.exports;
|
|
||||||
try {
|
|
||||||
let serviceId = getStringFromWasm0(wasm, arg1, arg2);
|
|
||||||
let fnName = getStringFromWasm0(wasm, arg3, arg4);
|
|
||||||
let args = getStringFromWasm0(wasm, arg5, arg6);
|
|
||||||
let tetraplets = getStringFromWasm0(wasm, arg7, arg8);
|
|
||||||
/*
|
|
||||||
TODO:: parse and pack arguments into structure like the following
|
|
||||||
class Argument<T> {
|
|
||||||
value: T,
|
|
||||||
SecurityTetraplet: tetraplet
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
let serviceResult = theParticleHandler(particleHandler, serviceId, fnName, args, tetraplets);
|
|
||||||
let resultStr = JSON.stringify(serviceResult);
|
|
||||||
return_call_service_result(wasm, resultStr, arg0);
|
|
||||||
} finally {
|
|
||||||
free(wasm, arg1, arg2);
|
|
||||||
free(wasm, arg3, arg4);
|
|
||||||
free(wasm, arg5, arg6);
|
|
||||||
free(wasm, arg7, arg8);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
__wbg_getcurrentpeeridimpl_4aca996e28cb8f44: (arg0: any) => {
|
|
||||||
let peerIdStr = peerId.toB58String();
|
|
||||||
let wasm = cfg.exports;
|
|
||||||
return_current_peer_id(wasm, peerIdStr, arg0);
|
|
||||||
},
|
|
||||||
__wbindgen_throw: (arg: any) => {
|
|
||||||
log.error(`wbindgen throw: ${JSON.stringify(arg)}`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
host: log_import(cfg),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function newLogImport(cfg: HostImportsConfig): ImportObject {
|
|
||||||
return {
|
|
||||||
host: log_import(cfg),
|
|
||||||
'./aquamarine_client_bg.js': {
|
|
||||||
__wbg_callserviceimpl_84d8278762e4c639: (_) => {},
|
|
||||||
__wbg_getcurrentpeeridimpl_4aca996e28cb8f44: (_) => {},
|
|
||||||
__wbindgen_throw: (_) => {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Instantiates AIR interpreter, and returns its `invoke` function as closure
|
|
||||||
async function instantiateInterpreter(particleHandler: ParticleHandler, peerId: PeerId): Promise<InterpreterInvoke> {
|
|
||||||
let cfg = new HostImportsConfig((cfg) => {
|
|
||||||
return newImportObject(particleHandler, cfg, peerId);
|
|
||||||
});
|
|
||||||
let instance = await interpreterInstance(cfg);
|
|
||||||
|
|
||||||
return (init_peer_id: string, script: string, prev_data: Uint8Array, data: Uint8Array) => {
|
|
||||||
let logLevel = log.getLevel();
|
|
||||||
let logLevelStr = 'info';
|
|
||||||
if (logLevel === 0) {
|
|
||||||
logLevelStr = 'trace';
|
|
||||||
} else if (logLevel === 1) {
|
|
||||||
logLevelStr = 'debug';
|
|
||||||
} else if (logLevel === 2) {
|
|
||||||
logLevelStr = 'info';
|
|
||||||
} else if (logLevel === 3) {
|
|
||||||
logLevelStr = 'warn';
|
|
||||||
} else if (logLevel === 4) {
|
|
||||||
logLevelStr = 'error';
|
|
||||||
} else if (logLevel === 5) {
|
|
||||||
logLevelStr = 'off';
|
|
||||||
}
|
|
||||||
|
|
||||||
return aqua.invoke(instance.exports, init_peer_id, script, prev_data, data, logLevelStr);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Instantiate AIR interpreter with host imports containing only logger, but not call_service
|
|
||||||
/// peerId isn't actually required for AST parsing, but host imports require it, and I don't see any workaround
|
|
||||||
async function parseAstClosure(): Promise<(script: string) => string> {
|
|
||||||
let cfg = new HostImportsConfig((cfg) => newLogImport(cfg));
|
|
||||||
let instance = await interpreterInstance(cfg);
|
|
||||||
|
|
||||||
return (script: string) => {
|
|
||||||
return aqua.ast(instance.exports, script);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses script and returns AST in JSON format
|
|
||||||
/// NOTE & TODO: interpreter is instantiated every time, make it a lazy constant?
|
|
||||||
async function parseAIR(script: string): Promise<string> {
|
|
||||||
let closure = await parseAstClosure();
|
|
||||||
return closure(script);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AquamarineInterpreter {
|
|
||||||
private wasmWrapper;
|
|
||||||
|
|
||||||
constructor(wasmWrapper) {
|
|
||||||
this.wasmWrapper = wasmWrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async create(config: { particleHandler: ParticleHandler; peerId: PeerId }) {
|
|
||||||
const cfg = new HostImportsConfig((cfg) => {
|
|
||||||
return newImportObject(config.particleHandler, cfg, config.peerId);
|
|
||||||
});
|
|
||||||
|
|
||||||
const instance = await interpreterInstance(cfg);
|
|
||||||
const res = new AquamarineInterpreter(instance);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
invoke(init_peer_id: string, script: string, prev_data: Uint8Array, data: Uint8Array): string {
|
|
||||||
let logLevel = log.getLevel();
|
|
||||||
let logLevelStr = 'info';
|
|
||||||
if (logLevel === 0) {
|
|
||||||
logLevelStr = 'trace';
|
|
||||||
} else if (logLevel === 1) {
|
|
||||||
logLevelStr = 'debug';
|
|
||||||
} else if (logLevel === 2) {
|
|
||||||
logLevelStr = 'info';
|
|
||||||
} else if (logLevel === 3) {
|
|
||||||
logLevelStr = 'warn';
|
|
||||||
} else if (logLevel === 4) {
|
|
||||||
logLevelStr = 'error';
|
|
||||||
} else if (logLevel === 5) {
|
|
||||||
logLevelStr = 'off';
|
|
||||||
}
|
|
||||||
|
|
||||||
return aqua.invoke(this.wasmWrapper.exports, init_peer_id, script, prev_data, data, logLevelStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
parseAir(script: string): string {
|
|
||||||
return aqua.ast(this.wasmWrapper.exports, script);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user