mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2024-12-12 13:35:32 +00:00
feat: Cleaning up technical debts (#295)
This commit is contained in:
parent
00b62f1459
commit
0b2f12d8ac
@ -8,9 +8,6 @@
|
||||
"node": ">=10",
|
||||
"pnpm": ">=3"
|
||||
},
|
||||
"scripts": {
|
||||
"simulate-cdn": "http-server -p 8765 ./packages/client/js-client.web.standalone/dist"
|
||||
},
|
||||
"author": "Fluence Labs",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { fromByteArray } from 'base64-js';
|
||||
import { Fluence } from '@fluencelabs/js-client.api';
|
||||
import type { ClientConfig } from '@fluencelabs/js-client.api';
|
||||
import { kras, randomKras } from '@fluencelabs/fluence-network-environment';
|
||||
import { registerHelloWorld, helloTest, marineTest, resourceTest } from './_aqua/smoke_test.js';
|
||||
import { wasm } from './wasmb64.js';
|
||||
@ -11,7 +12,9 @@ import { wasm } from './wasmb64.js';
|
||||
// };
|
||||
|
||||
// Currently the tests executes some calls to registry. And they fail for a single local node setup. So we use kras instead.
|
||||
const relay = randomKras();
|
||||
// TODO DXJ-356: use local peers instead of kras
|
||||
// const relay = randomKras();
|
||||
const relay = kras[4];
|
||||
|
||||
function generateRandomUint8Array() {
|
||||
const uint8Array = new Uint8Array(32);
|
||||
@ -21,7 +24,7 @@ function generateRandomUint8Array() {
|
||||
return uint8Array;
|
||||
}
|
||||
|
||||
const optsWithRandomKeyPair = () => {
|
||||
const optsWithRandomKeyPair = (): ClientConfig => {
|
||||
return {
|
||||
keyPair: {
|
||||
type: 'Ed25519',
|
||||
@ -37,6 +40,7 @@ export const runTest = async (): Promise<TestResult> => {
|
||||
Fluence.onConnectionStateChange((state) => console.info('connection state changed: ', state));
|
||||
|
||||
console.log('connecting to Fluence Network...');
|
||||
console.log('multiaddr: ', relay.multiaddr);
|
||||
await Fluence.connect(relay, optsWithRandomKeyPair());
|
||||
|
||||
console.log('connected');
|
||||
@ -68,10 +72,9 @@ export const runTest = async (): Promise<TestResult> => {
|
||||
const hello = await helloTest();
|
||||
console.log('hello test finished, result: ', hello);
|
||||
|
||||
// TODO: some wired error shit about SharedArrayBuffer
|
||||
// console.log('running marine test...');
|
||||
// const marine = await marineTest(wasm);
|
||||
// console.log('marine test finished, result: ', marine);
|
||||
console.log('running marine test...');
|
||||
const marine = await marineTest(wasm);
|
||||
console.log('marine test finished, result: ', marine);
|
||||
|
||||
const returnVal = {
|
||||
res,
|
||||
@ -79,8 +82,6 @@ export const runTest = async (): Promise<TestResult> => {
|
||||
// marine,
|
||||
};
|
||||
return { type: 'success', data: JSON.stringify(returnVal) };
|
||||
} catch (err: any) {
|
||||
return { type: 'failure', error: err.toString() };
|
||||
} finally {
|
||||
console.log('disconnecting from Fluence Network...');
|
||||
await Fluence.disconnect();
|
||||
|
19
packages/@tests/marine/node/.gitignore
vendored
19
packages/@tests/marine/node/.gitignore
vendored
@ -1,19 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
bundle/
|
||||
/dist/
|
||||
/worker/dist/
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
.idea
|
@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testPathIgnorePatterns: ['dist'],
|
||||
};
|
@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "@test/marine_node",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.11.59",
|
||||
"typescript": "^4.0.0",
|
||||
"@types/jest": "28.1.0",
|
||||
"jest": "28.1.0",
|
||||
"ts-jest": "28.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluencelabs/avm": "0.32.1",
|
||||
"@fluencelabs/marine.background-runner": "0.1.0",
|
||||
"@fluencelabs/marine.deps-loader.node": "0.1.0"
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
import { MarineBackgroundRunner } from '@fluencelabs/marine.background-runner';
|
||||
import { InlinedWorkerLoader, WasmNpmLoader } from '@fluencelabs/marine.deps-loader.node';
|
||||
import { callAvm, JSONArray, JSONObject } from '@fluencelabs/avm';
|
||||
|
||||
const vmPeerId = '12D3KooWNzutuy8WHXDKFqFsATvCR6j9cj2FijYbnd47geRKaQZS';
|
||||
|
||||
describe('Nodejs integration tests', () => {
|
||||
it('Smoke test', async () => {
|
||||
let runner: MarineBackgroundRunner | undefined = undefined;
|
||||
try {
|
||||
// arrange
|
||||
const avm = new WasmNpmLoader('@fluencelabs/avm', 'avm.wasm');
|
||||
const control = new WasmNpmLoader('@fluencelabs/marine-js', 'marine-js.wasm');
|
||||
const worker = new InlinedWorkerLoader();
|
||||
runner = new MarineBackgroundRunner(worker, control, () => {});
|
||||
|
||||
await avm.start();
|
||||
|
||||
await runner.start();
|
||||
await runner.createService(avm.getValue(), 'avm');
|
||||
|
||||
const s = `(seq
|
||||
(par
|
||||
(call "${vmPeerId}" ("local_service_id" "local_fn_name") [] result_1)
|
||||
(call "remote_peer_id" ("service_id" "fn_name") [] g)
|
||||
)
|
||||
(call "${vmPeerId}" ("local_service_id" "local_fn_name") [] result_2)
|
||||
)`;
|
||||
|
||||
// act
|
||||
const res = await callAvm(
|
||||
(args: JSONArray | JSONObject) => runner!.callService('avm', 'invoke', args, undefined),
|
||||
{
|
||||
currentPeerId: vmPeerId,
|
||||
initPeerId: vmPeerId,
|
||||
timestamp: Date.now(),
|
||||
ttl: 10000,
|
||||
},
|
||||
s,
|
||||
Buffer.from(''),
|
||||
Buffer.from(''),
|
||||
[],
|
||||
);
|
||||
|
||||
// assert
|
||||
expect(res).toMatchObject({
|
||||
retCode: 0,
|
||||
errorMessage: '',
|
||||
});
|
||||
} finally {
|
||||
runner?.stop();
|
||||
}
|
||||
});
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"extends": "../../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
22
packages/@tests/marine/web/.gitignore
vendored
22
packages/@tests/marine/web/.gitignore
vendored
@ -1,22 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
build/
|
||||
public/*.*
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
bundle/
|
||||
/dist/
|
||||
/worker/dist/
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
.idea
|
@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Webpack App</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello world!</h1>
|
||||
<h2>Tip: Check your console</h2>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,9 +0,0 @@
|
||||
module.exports = {
|
||||
preset: 'jest-puppeteer',
|
||||
testMatch: ['**/?(*.)+(spec|test).[t]s'],
|
||||
testPathIgnorePatterns: ['/node_modules/', 'dist'],
|
||||
testMatch: ['**/test/*.spec.ts'],
|
||||
transform: {
|
||||
'^.+\\.ts?$': 'ts-jest',
|
||||
},
|
||||
};
|
@ -1,37 +0,0 @@
|
||||
{
|
||||
"name": "@test/marine_web",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"start": "webpack serve",
|
||||
"test": "jest",
|
||||
"build": "webpack --mode=production --node-env=production",
|
||||
"build:dev": "webpack --mode=development",
|
||||
"build:prod": "webpack --mode=production --node-env=production",
|
||||
"watch": "webpack --watch",
|
||||
"serve": "webpack serve"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@webpack-cli/generators": "^2.4.1",
|
||||
"css-loader": "^6.5.1",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"install-local": "^3.0.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-loader": "^8.3.0",
|
||||
"typescript": "^4.5.4",
|
||||
"util": "^0.12.4",
|
||||
"webpack": "^5.65.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"webpack-dev-server": "^4.6.0",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@types/jest-environment-puppeteer": "^4.4.1",
|
||||
"@types/puppeteer": "^5.4.4",
|
||||
"jest": "28.1.0",
|
||||
"jest-puppeteer": "^6.0.2",
|
||||
"ts-jest": "28.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluencelabs/avm": "0.34.4",
|
||||
"js-base64": "^3.7.2",
|
||||
"buffer": "6.0.3"
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
// @ts-ignore
|
||||
window.Buffer = Buffer;
|
||||
|
||||
import { MarineBackgroundRunner } from '@fluencelabs/marine.background-runner';
|
||||
import { InlinedWorkerLoader, WasmWebLoader } from '@fluencelabs/marine.deps-loader.web';
|
||||
import { callAvm, JSONArray, JSONObject } from '@fluencelabs/avm';
|
||||
import { toUint8Array } from 'js-base64';
|
||||
|
||||
const vmPeerId = '12D3KooWNzutuy8WHXDKFqFsATvCR6j9cj2FijYbnd47geRKaQZS';
|
||||
|
||||
const b = (s: string) => {
|
||||
return toUint8Array(s);
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
const avm = new WasmWebLoader('avm.wasm');
|
||||
const control = new WasmWebLoader('marine-js.wasm');
|
||||
const worker = new InlinedWorkerLoader();
|
||||
const runner = new MarineBackgroundRunner(worker, control, () => {});
|
||||
|
||||
await runner.start();
|
||||
await avm.start();
|
||||
const avmVal = await avm.getValue();
|
||||
await runner.createService(avmVal, 'avm');
|
||||
|
||||
const s = `(seq
|
||||
(par
|
||||
(call "${vmPeerId}" ("local_service_id" "local_fn_name") [] result_1)
|
||||
(call "remote_peer_id" ("service_id" "fn_name") [] g)
|
||||
)
|
||||
(call "${vmPeerId}" ("local_service_id" "local_fn_name") [] result_2)
|
||||
)`;
|
||||
|
||||
// act
|
||||
const res = await callAvm(
|
||||
(args: JSONArray | JSONObject) => runner.callService('avm', 'invoke', args, undefined),
|
||||
{
|
||||
currentPeerId: vmPeerId,
|
||||
initPeerId: vmPeerId,
|
||||
timestamp: Date.now(),
|
||||
ttl: 10000,
|
||||
},
|
||||
s,
|
||||
b(''),
|
||||
b(''),
|
||||
[],
|
||||
);
|
||||
await runner.stop();
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
window.MAIN = main;
|
@ -1,115 +0,0 @@
|
||||
import Webpack from 'webpack';
|
||||
import WebpackDevServer from 'webpack-dev-server';
|
||||
import webpackConfig from '../webpack.config.js';
|
||||
import process from 'process';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
// change directory to the location to the test-project.
|
||||
// run all the subsequent Webpack scripts in that directory
|
||||
process.chdir(path.join(__dirname, '..'));
|
||||
|
||||
let server;
|
||||
const port = 8080;
|
||||
|
||||
jest.setTimeout(10000);
|
||||
|
||||
const startServer = async (modifyConfig?) => {
|
||||
const loadInBrowserToDebug = false;
|
||||
// const loadInBrowserToDebug = true; // use this line to debug
|
||||
|
||||
modifyConfig = modifyConfig || ((_) => {});
|
||||
|
||||
const config: any = webpackConfig();
|
||||
modifyConfig(config);
|
||||
config.devServer.open = loadInBrowserToDebug;
|
||||
server = await makeServer(config);
|
||||
};
|
||||
|
||||
// https://stackoverflow.com/questions/42940550/wait-until-webpack-dev-server-is-ready
|
||||
function makeServer(config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const compiler = Webpack(config);
|
||||
|
||||
let compiled = false;
|
||||
let listening = false;
|
||||
|
||||
compiler.hooks.done.tap('tap_name', () => {
|
||||
// console.log('compiled');
|
||||
|
||||
if (listening) resolve(server);
|
||||
else compiled = true;
|
||||
});
|
||||
|
||||
const server = new WebpackDevServer(compiler, config.devServer);
|
||||
|
||||
server.listen(port, '0.0.0.0', (err) => {
|
||||
if (err) return reject(err);
|
||||
|
||||
// console.log('listening');
|
||||
|
||||
if (compiled) {
|
||||
resolve(server);
|
||||
} else {
|
||||
listening = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const stopServer = async () => {
|
||||
console.log('test: stopping server');
|
||||
await server.stop();
|
||||
};
|
||||
|
||||
const publicDir = 'public';
|
||||
|
||||
const copyFile = async (packageName: string, fileName: string) => {
|
||||
const modulePath = require.resolve(packageName);
|
||||
const source = path.join(path.dirname(modulePath), fileName);
|
||||
const dest = path.join(publicDir, fileName);
|
||||
|
||||
return fs.promises.copyFile(source, dest);
|
||||
};
|
||||
|
||||
const copyPublicDeps = async () => {
|
||||
await fs.promises.mkdir(publicDir, { recursive: true });
|
||||
return Promise.all([
|
||||
copyFile('@fluencelabs/marine-js', 'marine-js.wasm'),
|
||||
copyFile('@fluencelabs/avm', 'avm.wasm'),
|
||||
]);
|
||||
};
|
||||
|
||||
const cleanPublicDeps = () => {
|
||||
return fs.promises.rm(publicDir, { recursive: true, force: true });
|
||||
};
|
||||
|
||||
describe('Browser integration tests', () => {
|
||||
beforeEach(async () => {
|
||||
await copyPublicDeps();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await stopServer();
|
||||
await cleanPublicDeps();
|
||||
});
|
||||
|
||||
it('Some test', async () => {
|
||||
console.log('test: starting server...');
|
||||
await startServer();
|
||||
console.log('test: navigating to page...');
|
||||
await page.goto('http://localhost:8080/');
|
||||
|
||||
console.log('test: running script in browser...');
|
||||
const res = await page.evaluate(() => {
|
||||
// @ts-ignore
|
||||
return window.MAIN();
|
||||
});
|
||||
|
||||
console.log('test: checking expectations...');
|
||||
await expect(res).toMatchObject({
|
||||
retCode: 0,
|
||||
errorMessage: '',
|
||||
});
|
||||
});
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"extends": "../../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
// Generated using webpack-cli https://github.com/webpack/webpack-cli
|
||||
|
||||
const path = require('path');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
const isProduction = process.env.NODE_ENV == 'production';
|
||||
|
||||
const stylesHandler = 'style-loader';
|
||||
|
||||
const config = {
|
||||
entry: './src/index.ts',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
devServer: {
|
||||
open: true,
|
||||
host: 'localhost',
|
||||
static: {
|
||||
directory: path.join(__dirname, 'public'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'index.html',
|
||||
}),
|
||||
|
||||
// Add your plugins here
|
||||
// Learn more about plugins from https://webpack.js.org/configuration/plugins/
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|tsx)$/i,
|
||||
loader: 'ts-loader',
|
||||
exclude: ['/node_modules/'],
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: [stylesHandler, 'css-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
|
||||
type: 'asset',
|
||||
},
|
||||
|
||||
// Add your rules for custom modules here
|
||||
// Learn more about loaders from https://webpack.js.org/loaders/
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js'],
|
||||
fallback: {
|
||||
buffer: require.resolve('buffer/'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = () => {
|
||||
if (isProduction) {
|
||||
config.mode = 'production';
|
||||
} else {
|
||||
config.mode = 'development';
|
||||
}
|
||||
return config;
|
||||
};
|
@ -11,8 +11,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "node --loader ts-node/esm ./src/index.ts",
|
||||
"test_logs": "DEBUG=fluence:particle:* node --loader ts-node/esm ./src/index.ts"
|
||||
"test_commented_out": "node --loader ts-node/esm ./src/index.ts"
|
||||
},
|
||||
"repository": "https://github.com/fluencelabs/fluence-js",
|
||||
"author": "Fluence Labs",
|
||||
|
@ -24,6 +24,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"test_commented_out": "node --loader ts-node/esm ./test/index.ts",
|
||||
"simulate-cdn": "http-server -p 8766 ../../../client/js-client.web.standalone/dist",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"_test": "react-scripts test",
|
||||
|
@ -11,6 +11,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"simulate-cdn": "http-server -p 8765 ../../../client/js-client.web.standalone/dist",
|
||||
"test_commented_out": "node --loader ts-node/esm ./src/index.ts",
|
||||
"serve": "http-server public"
|
||||
},
|
||||
|
@ -29,13 +29,12 @@ import { getFluenceInterface } from '../util.js';
|
||||
* @param def - function definition generated by the Aqua compiler
|
||||
* @param script - air script with function execution logic generated by the Aqua compiler
|
||||
*/
|
||||
export const callFunction = async (rawFnArgs: Array<any>, def: FunctionCallDef, script: string): Promise<unknown> => {
|
||||
export const v5_callFunction = async (
|
||||
rawFnArgs: Array<any>,
|
||||
def: FunctionCallDef,
|
||||
script: string,
|
||||
): Promise<unknown> => {
|
||||
const { args, client: peer, config } = await extractFunctionArgs(rawFnArgs, def);
|
||||
if (peer.internals.getConnectionState() !== 'connected') {
|
||||
throw new Error(
|
||||
'Could not call the Aqua function because client is disconnected. Did you forget to call Fluence.connect()?',
|
||||
);
|
||||
}
|
||||
|
||||
const fluence = await getFluenceInterface();
|
||||
return fluence.callAquaFunction({
|
||||
@ -53,17 +52,9 @@ export const callFunction = async (rawFnArgs: Array<any>, def: FunctionCallDef,
|
||||
* @param args - raw arguments passed by user to the generated function
|
||||
* @param def - service definition generated by the Aqua compiler
|
||||
*/
|
||||
export const registerService = async (args: any[], def: ServiceDef): Promise<unknown> => {
|
||||
export const v5_registerService = async (args: any[], def: ServiceDef): Promise<unknown> => {
|
||||
const { peer, service, serviceId } = await extractServiceArgs(args, def.defaultServiceId);
|
||||
|
||||
// TODO: TBH service registration is just putting some stuff into a hashmap
|
||||
// there should not be such a check at all
|
||||
if (peer.internals.getConnectionState() !== 'connected') {
|
||||
throw new Error(
|
||||
'Could not register Aqua service because the client is disconnected. Did you forget to call Fluence.connect()?',
|
||||
);
|
||||
}
|
||||
|
||||
const fluence = await getFluenceInterface();
|
||||
return fluence.registerService({
|
||||
def,
|
||||
@ -104,6 +95,11 @@ const extractFunctionArgs = async (
|
||||
config = args[numberOfExpectedArgs + 1];
|
||||
} else {
|
||||
const fluence = await getFluenceInterface();
|
||||
if (!fluence.defaultClient) {
|
||||
throw new Error(
|
||||
'Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?',
|
||||
);
|
||||
}
|
||||
peer = fluence.defaultClient;
|
||||
structuredArgs = args.slice(0, numberOfExpectedArgs);
|
||||
config = args[numberOfExpectedArgs];
|
||||
@ -145,6 +141,11 @@ const extractServiceArgs = async (
|
||||
peer = args[0];
|
||||
} else {
|
||||
const fluence = await getFluenceInterface();
|
||||
if (!fluence.defaultClient) {
|
||||
throw new Error(
|
||||
'Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?',
|
||||
);
|
||||
}
|
||||
peer = fluence.defaultClient;
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,28 @@
|
||||
/*
|
||||
* Copyright 2023 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 { getFluenceInterface, getFluenceInterfaceFromGlobalThis } from './util.js';
|
||||
import {
|
||||
IFluenceClient,
|
||||
ClientOptions,
|
||||
ClientConfig,
|
||||
RelayOptions,
|
||||
ConnectionState,
|
||||
ConnectionStates,
|
||||
CallAquaFunctionType,
|
||||
RegisterServiceType,
|
||||
} from '@fluencelabs/interfaces';
|
||||
export type { IFluenceClient, ClientOptions, CallParams } from '@fluencelabs/interfaces';
|
||||
export type { IFluenceClient, ClientConfig, CallParams } from '@fluencelabs/interfaces';
|
||||
|
||||
export {
|
||||
ArrayType,
|
||||
@ -14,7 +30,6 @@ export {
|
||||
ArrowWithCallbacks,
|
||||
ArrowWithoutCallbacks,
|
||||
BottomType,
|
||||
FnConfig,
|
||||
FunctionCallConstants,
|
||||
FunctionCallDef,
|
||||
LabeledProductType,
|
||||
@ -28,12 +43,15 @@ export {
|
||||
StructType,
|
||||
TopType,
|
||||
UnlabeledProductType,
|
||||
CallAquaFunctionType,
|
||||
CallAquaFunctionArgs,
|
||||
PassedArgs,
|
||||
FnConfig,
|
||||
RegisterServiceType,
|
||||
RegisterServiceArgs,
|
||||
} from '@fluencelabs/interfaces';
|
||||
|
||||
export {
|
||||
callFunction as v5_callFunction,
|
||||
registerService as v5_registerService,
|
||||
} from './compilerSupport/implementation.js';
|
||||
export { v5_callFunction, v5_registerService } from './compilerSupport/implementation.js';
|
||||
|
||||
/**
|
||||
* Public interface to Fluence Network
|
||||
@ -42,11 +60,12 @@ export const Fluence = {
|
||||
/**
|
||||
* Connect to the Fluence network
|
||||
* @param relay - relay node to connect to
|
||||
* @param options - client options
|
||||
* @param config - client configuration
|
||||
*/
|
||||
connect: async (relay: RelayOptions, options?: ClientOptions): Promise<void> => {
|
||||
connect: async (relay: RelayOptions, config?: ClientConfig): Promise<void> => {
|
||||
const fluence = await getFluenceInterface();
|
||||
return fluence.defaultClient.connect(relay, options);
|
||||
const client = await fluence.clientFactory(relay, config);
|
||||
fluence.defaultClient = client;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -54,7 +73,8 @@ export const Fluence = {
|
||||
*/
|
||||
disconnect: async (): Promise<void> => {
|
||||
const fluence = await getFluenceInterface();
|
||||
return fluence.defaultClient.disconnect();
|
||||
await fluence.defaultClient?.disconnect();
|
||||
fluence.defaultClient = undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -62,11 +82,13 @@ export const Fluence = {
|
||||
*/
|
||||
onConnectionStateChange(handler: (state: ConnectionState) => void): ConnectionState {
|
||||
const optimisticResult = getFluenceInterfaceFromGlobalThis();
|
||||
if (optimisticResult) {
|
||||
if (optimisticResult && optimisticResult.defaultClient) {
|
||||
return optimisticResult.defaultClient.onConnectionStateChange(handler);
|
||||
}
|
||||
|
||||
getFluenceInterface().then((fluence) => fluence.defaultClient.onConnectionStateChange(handler));
|
||||
getFluenceInterface().then((fluence) => {
|
||||
fluence.defaultClient?.onConnectionStateChange(handler);
|
||||
});
|
||||
|
||||
return 'disconnected';
|
||||
},
|
||||
@ -77,6 +99,9 @@ export const Fluence = {
|
||||
*/
|
||||
getClient: async (): Promise<IFluenceClient> => {
|
||||
const fluence = await getFluenceInterface();
|
||||
if (!fluence.defaultClient) {
|
||||
throw new Error('Fluence client is not initialized. Call Fluence.connect() first');
|
||||
}
|
||||
return fluence.defaultClient;
|
||||
},
|
||||
};
|
||||
@ -85,7 +110,23 @@ export const Fluence = {
|
||||
* Low level API. Generally you need Fluence.connect() instead.
|
||||
* @returns IFluenceClient instance
|
||||
*/
|
||||
export const createClient = async (): Promise<IFluenceClient> => {
|
||||
export const createClient = async (relay: RelayOptions, config?: ClientConfig): Promise<IFluenceClient> => {
|
||||
const fluence = await getFluenceInterface();
|
||||
return fluence.clientFactory();
|
||||
return await fluence.clientFactory(relay, config);
|
||||
};
|
||||
|
||||
/**
|
||||
* Low level API. Generally you should use code generated by the Aqua compiler.
|
||||
*/
|
||||
export const callAquaFunction: CallAquaFunctionType = async (args) => {
|
||||
const fluence = await getFluenceInterface();
|
||||
return await fluence.callAquaFunction(args);
|
||||
};
|
||||
|
||||
/**
|
||||
* Low level API. Generally you should use code generated by the Aqua compiler.
|
||||
*/
|
||||
export const registerService: RegisterServiceType = async (args) => {
|
||||
const fluence = await getFluenceInterface();
|
||||
return await fluence.registerService(args);
|
||||
};
|
||||
|
@ -1,10 +1,31 @@
|
||||
import type { CallAquaFunction, IFluenceClient, RegisterService } from '@fluencelabs/interfaces';
|
||||
/*
|
||||
* Copyright 2023 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 type {
|
||||
CallAquaFunctionType,
|
||||
ClientConfig,
|
||||
IFluenceClient,
|
||||
RegisterServiceType,
|
||||
RelayOptions,
|
||||
} from '@fluencelabs/interfaces';
|
||||
|
||||
type PublicFluenceInterface = {
|
||||
clientFactory: () => IFluenceClient;
|
||||
defaultClient: IFluenceClient;
|
||||
callAquaFunction: CallAquaFunction;
|
||||
registerService: RegisterService;
|
||||
defaultClient: IFluenceClient | undefined;
|
||||
clientFactory: (relay: RelayOptions, config?: ClientConfig) => Promise<IFluenceClient>;
|
||||
callAquaFunction: CallAquaFunctionType;
|
||||
registerService: RegisterServiceType;
|
||||
};
|
||||
|
||||
export const getFluenceInterfaceFromGlobalThis = (): PublicFluenceInterface | undefined => {
|
||||
|
@ -1,33 +1,34 @@
|
||||
{
|
||||
"name": "@fluencelabs/js-client.node",
|
||||
"version": "0.6.7",
|
||||
"description": "TypeScript implementation of Fluence Peer",
|
||||
"main": "./dist/index.js",
|
||||
"typings": "./dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"pnpm": ">=3"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
"name": "@fluencelabs/js-client.node",
|
||||
"version": "0.6.7",
|
||||
"description": "TypeScript implementation of Fluence Peer",
|
||||
"main": "./dist/index.js",
|
||||
"typings": "./dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"pnpm": ">=3"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"repository": "https://github.com/fluencelabs/fluence-js",
|
||||
"author": "Fluence Labs",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@fluencelabs/js-peer": "0.8.6",
|
||||
"@fluencelabs/interfaces": "0.7.4",
|
||||
"@fluencelabs/avm": "0.35.4",
|
||||
"@fluencelabs/marine-js": "0.3.45",
|
||||
"platform": "1.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/platform": "1.3.4"
|
||||
}
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"repository": "https://github.com/fluencelabs/fluence-js",
|
||||
"author": "Fluence Labs",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@fluencelabs/js-peer": "0.8.6",
|
||||
"@fluencelabs/avm": "0.35.4",
|
||||
"@fluencelabs/marine-js": "0.3.45",
|
||||
"platform": "1.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/platform": "1.3.4"
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,29 @@
|
||||
/*
|
||||
* Copyright 2023 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 * as platform from 'platform';
|
||||
|
||||
import { FluencePeer } from '@fluencelabs/js-peer/dist/js-peer/FluencePeer.js';
|
||||
import type { RelayOptions, ClientConfig, IFluenceClient } from '@fluencelabs/interfaces';
|
||||
import { ClientPeer, makeClientPeerConfig } from '@fluencelabs/js-peer/dist/clientPeer/ClientPeer.js';
|
||||
import { callAquaFunction } from '@fluencelabs/js-peer/dist/compilerSupport/callFunction.js';
|
||||
import { registerService } from '@fluencelabs/js-peer/dist/compilerSupport/registerService.js';
|
||||
import { MarineBasedAvmRunner } from '@fluencelabs/js-peer/dist/js-peer/avm.js';
|
||||
import { MarineBasedAvmRunner } from '@fluencelabs/js-peer/dist/jsPeer/avm.js';
|
||||
import { MarineBackgroundRunner } from '@fluencelabs/js-peer/dist/marine/worker/index.js';
|
||||
import { WasmLoaderFromNpm } from '@fluencelabs/js-peer/dist/marine/deps-loader/node.js';
|
||||
import { WorkerLoader } from '@fluencelabs/js-peer/dist/marine/worker-script/workerLoader.js';
|
||||
import { doRegisterNodeUtils } from '@fluencelabs/js-peer/dist/services/NodeUtils.js';
|
||||
|
||||
throwIfNotSupported();
|
||||
|
||||
@ -21,19 +38,26 @@ export const defaultNames = {
|
||||
},
|
||||
};
|
||||
|
||||
export const createClient = () => {
|
||||
const createClient = async (relay: RelayOptions, config: ClientConfig): Promise<IFluenceClient> => {
|
||||
const workerLoader = new WorkerLoader();
|
||||
const controlModuleLoader = new WasmLoaderFromNpm(defaultNames.marine.package, defaultNames.marine.file);
|
||||
const avmModuleLoader = new WasmLoaderFromNpm(defaultNames.avm.package, defaultNames.avm.file);
|
||||
|
||||
const marine = new MarineBackgroundRunner(workerLoader, controlModuleLoader);
|
||||
const avm = new MarineBasedAvmRunner(marine, avmModuleLoader);
|
||||
return new FluencePeer(marine, avm);
|
||||
const { keyPair, peerConfig, relayConfig } = await makeClientPeerConfig(relay, config);
|
||||
const client: IFluenceClient = new ClientPeer(peerConfig, relayConfig, keyPair, marine, avm);
|
||||
registerNodeOnlyServices(client);
|
||||
await client.connect();
|
||||
return client;
|
||||
};
|
||||
|
||||
function registerNodeOnlyServices(client: IFluenceClient) {
|
||||
doRegisterNodeUtils(client);
|
||||
}
|
||||
|
||||
const publicFluenceInterface = {
|
||||
clientFactory: createClient,
|
||||
defaultClient: createClient(),
|
||||
callAquaFunction,
|
||||
registerService,
|
||||
};
|
||||
|
@ -1,36 +1,37 @@
|
||||
{
|
||||
"name": "@fluencelabs/js-client.web.standalone",
|
||||
"version": "0.13.6",
|
||||
"description": "TypeScript implementation of Fluence Peer",
|
||||
"main": "./dist/index.js",
|
||||
"typings": "./dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"pnpm": ">=3"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "node --loader ts-node/esm ./build.ts"
|
||||
},
|
||||
"repository": "https://github.com/fluencelabs/fluence-js",
|
||||
"author": "Fluence Labs",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@fluencelabs/js-peer": "0.8.6",
|
||||
"buffer": "6.0.3",
|
||||
"process": "0.11.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fluencelabs/avm": "0.35.4",
|
||||
"@fluencelabs/marine-js": "0.3.45",
|
||||
"@types/node": "16.11.59",
|
||||
"@types/jest": "28.1.0",
|
||||
"jest": "28.1.0",
|
||||
"ts-jest": "28.0.2",
|
||||
"js-base64": "3.7.5",
|
||||
"@rollup/plugin-inject": "5.0.3",
|
||||
"vite-plugin-replace": "0.1.1",
|
||||
"vite": "4.0.4",
|
||||
"vite-tsconfig-paths": "4.0.3"
|
||||
}
|
||||
"name": "@fluencelabs/js-client.web.standalone",
|
||||
"version": "0.13.6",
|
||||
"description": "TypeScript implementation of Fluence Peer",
|
||||
"main": "./dist/index.js",
|
||||
"typings": "./dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"pnpm": ">=3"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "node --loader ts-node/esm ./build.ts"
|
||||
},
|
||||
"repository": "https://github.com/fluencelabs/fluence-js",
|
||||
"author": "Fluence Labs",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@fluencelabs/js-peer": "0.8.6",
|
||||
"@fluencelabs/interfaces": "0.7.4",
|
||||
"buffer": "6.0.3",
|
||||
"process": "0.11.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fluencelabs/avm": "0.35.4",
|
||||
"@fluencelabs/marine-js": "0.3.45",
|
||||
"@types/node": "16.11.59",
|
||||
"@types/jest": "28.1.0",
|
||||
"jest": "28.1.0",
|
||||
"ts-jest": "28.0.2",
|
||||
"js-base64": "3.7.5",
|
||||
"@rollup/plugin-inject": "5.0.3",
|
||||
"vite-plugin-replace": "0.1.1",
|
||||
"vite": "4.0.4",
|
||||
"vite-tsconfig-paths": "4.0.3"
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,41 @@
|
||||
import { FluencePeer } from '@fluencelabs/js-peer/dist/js-peer/FluencePeer.js';
|
||||
/*
|
||||
* Copyright 2023 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 type { RelayOptions, ClientConfig, IFluenceClient } from '@fluencelabs/interfaces';
|
||||
import { ClientPeer, makeClientPeerConfig } from '@fluencelabs/js-peer/dist/clientPeer/ClientPeer.js';
|
||||
import { callAquaFunction } from '@fluencelabs/js-peer/dist/compilerSupport/callFunction.js';
|
||||
import { registerService } from '@fluencelabs/js-peer/dist/compilerSupport/registerService.js';
|
||||
import { MarineBasedAvmRunner } from '@fluencelabs/js-peer/dist/js-peer/avm.js';
|
||||
import { MarineBasedAvmRunner } from '@fluencelabs/js-peer/dist/jsPeer/avm.js';
|
||||
import { MarineBackgroundRunner } from '@fluencelabs/js-peer/dist/marine/worker';
|
||||
import { InlinedWorkerLoader, InlinedWasmLoader } from '@fluencelabs/js-peer/dist/marine/deps-loader/common.js';
|
||||
|
||||
const createClient = () => {
|
||||
const createClient = async (relay: RelayOptions, config: ClientConfig): Promise<IFluenceClient> => {
|
||||
const workerLoader = new InlinedWorkerLoader('___worker___');
|
||||
const controlModuleLoader = new InlinedWasmLoader('___marine___');
|
||||
const avmModuleLoader = new InlinedWasmLoader('___avm___');
|
||||
|
||||
const marine = new MarineBackgroundRunner(workerLoader, controlModuleLoader);
|
||||
const avm = new MarineBasedAvmRunner(marine, avmModuleLoader);
|
||||
return new FluencePeer(marine, avm);
|
||||
const { keyPair, peerConfig, relayConfig } = await makeClientPeerConfig(relay, config);
|
||||
const client: IFluenceClient = new ClientPeer(peerConfig, relayConfig, keyPair, marine, avm);
|
||||
await client.connect();
|
||||
return client;
|
||||
};
|
||||
|
||||
const publicFluenceInterface = {
|
||||
clientFactory: createClient,
|
||||
defaultClient: createClient(),
|
||||
callAquaFunction,
|
||||
registerService,
|
||||
};
|
||||
|
@ -8,6 +8,7 @@
|
||||
"node": ">=10",
|
||||
"pnpm": ">=3"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
@ -15,7 +16,8 @@
|
||||
"author": "Fluence Labs",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@fluencelabs/js-peer": "0.7.0"
|
||||
"@fluencelabs/js-peer": "0.8.6",
|
||||
"@fluencelabs/interfaces": "0.7.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.11.59",
|
@ -1,23 +1,50 @@
|
||||
import { MarineBackgroundRunner } from '@fluencelabs/marine.background-runner';
|
||||
import { MarineBasedAvmRunner } from '@fluencelabs/js-peer/dist/avm';
|
||||
import { marineLogFunction } from '@fluencelabs/js-peer/dist/utils';
|
||||
import { FluencePeer } from '@fluencelabs/js-peer/dist/FluencePeer';
|
||||
import { InlinedWorkerLoader, WasmWebLoader } from '@fluencelabs/marine.deps-loader.web';
|
||||
/*
|
||||
* Copyright 2023 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 type { RelayOptions, ClientConfig, IFluenceClient } from '@fluencelabs/interfaces';
|
||||
import { ClientPeer, makeClientPeerConfig } from '@fluencelabs/js-peer/dist/clientPeer/ClientPeer.js';
|
||||
import { callAquaFunction } from '@fluencelabs/js-peer/dist/compilerSupport/callFunction.js';
|
||||
import { registerService } from '@fluencelabs/js-peer/dist/compilerSupport/registerService.js';
|
||||
import { MarineBasedAvmRunner } from '@fluencelabs/js-peer/dist/jsPeer/avm.js';
|
||||
import { MarineBackgroundRunner } from '@fluencelabs/js-peer/dist/marine/worker';
|
||||
import { WasmLoaderFromUrl, WorkerLoaderFromUrl } from '@fluencelabs/js-peer/dist/marine/deps-loader/web.js';
|
||||
|
||||
export const defaultNames = {
|
||||
avm: 'avm.wasm',
|
||||
const defaultNames = {
|
||||
marine: 'marine-js.wasm',
|
||||
avm: 'avm.wasm',
|
||||
worker: 'worker-script.js',
|
||||
};
|
||||
|
||||
export const makeDefaultPeer = () => {
|
||||
const workerLoader = new InlinedWorkerLoader();
|
||||
const controlModuleLoader = new WasmWebLoader(defaultNames.marine);
|
||||
const avmModuleLoader = new WasmWebLoader(defaultNames.avm);
|
||||
const createClient = async (relay: RelayOptions, config: ClientConfig): Promise<IFluenceClient> => {
|
||||
const workerLoader = new WorkerLoaderFromUrl(defaultNames.worker);
|
||||
const controlModuleLoader = new WasmLoaderFromUrl(defaultNames.marine);
|
||||
const avmModuleLoader = new WasmLoaderFromUrl(defaultNames.avm);
|
||||
|
||||
const marine = new MarineBackgroundRunner(workerLoader, controlModuleLoader, marineLogFunction);
|
||||
const avm = new MarineBasedAvmRunner(marine, avmModuleLoader, undefined);
|
||||
return new FluencePeer(marine, avm);
|
||||
const marine = new MarineBackgroundRunner(workerLoader, controlModuleLoader);
|
||||
const avm = new MarineBasedAvmRunner(marine, avmModuleLoader);
|
||||
const { keyPair, peerConfig, relayConfig } = await makeClientPeerConfig(relay, config);
|
||||
const client: IFluenceClient = new ClientPeer(peerConfig, relayConfig, keyPair, marine, avm);
|
||||
await client.connect();
|
||||
return client;
|
||||
};
|
||||
|
||||
const publicFluenceInterface = {
|
||||
clientFactory: createClient,
|
||||
callAquaFunction,
|
||||
registerService,
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
globalThis.defaultPeer = makeDefaultPeer();
|
||||
globalThis.fluence = publicFluenceInterface;
|
||||
|
65
packages/core/interfaces/src/commonTypes.ts
Normal file
65
packages/core/interfaces/src/commonTypes.ts
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2023 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 type { SecurityTetraplet } from '@fluencelabs/avm';
|
||||
|
||||
/**
|
||||
* Peer ID's id as a base58 string (multihash/CIDv0).
|
||||
*/
|
||||
export type PeerIdB58 = string;
|
||||
|
||||
/**
|
||||
* Node of the Fluence network specified as a pair of node's multiaddr and it's peer id
|
||||
*/
|
||||
export type Node = {
|
||||
peerId: PeerIdB58;
|
||||
multiaddr: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Additional information about a service call
|
||||
* @typeparam ArgName
|
||||
*/
|
||||
export interface CallParams<ArgName extends string | null> {
|
||||
/**
|
||||
* The identifier of particle which triggered the call
|
||||
*/
|
||||
particleId: string;
|
||||
|
||||
/**
|
||||
* The peer id which created the particle
|
||||
*/
|
||||
initPeerId: PeerIdB58;
|
||||
|
||||
/**
|
||||
* Particle's timestamp when it was created
|
||||
*/
|
||||
timestamp: number;
|
||||
|
||||
/**
|
||||
* Time to live in milliseconds. The time after the particle should be expired
|
||||
*/
|
||||
ttl: number;
|
||||
|
||||
/**
|
||||
* Particle's signature
|
||||
*/
|
||||
signature?: string;
|
||||
|
||||
/**
|
||||
* Security tetraplets
|
||||
*/
|
||||
tetraplets: ArgName extends string ? Record<ArgName, SecurityTetraplet[]> : Record<string, never>;
|
||||
}
|
@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
type SomeNonArrowTypes = ScalarType | OptionType | ArrayType | StructType | TopType | BottomType;
|
||||
|
||||
export type NonArrowType = SomeNonArrowTypes | ProductType<SomeNonArrowTypes>;
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2023 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 { IFluenceInternalApi } from '../fluenceClient.js';
|
||||
import { FnConfig, FunctionCallDef, ServiceDef } from './aquaTypeDefinitions.js';
|
||||
|
||||
/**
|
||||
* Arguments passed to Aqua function
|
||||
*/
|
||||
export type PassedArgs = { [key: string]: any };
|
||||
|
||||
/**
|
||||
* Arguments for callAquaFunction function
|
||||
*/
|
||||
export interface CallAquaFunctionArgs {
|
||||
/**
|
||||
* Peer to call the function on
|
||||
*/
|
||||
peer: IFluenceInternalApi;
|
||||
|
||||
/**
|
||||
* Function definition
|
||||
*/
|
||||
def: FunctionCallDef;
|
||||
|
||||
/**
|
||||
* Air script used by the aqua function
|
||||
*/
|
||||
script: string;
|
||||
|
||||
/**
|
||||
* Function configuration
|
||||
*/
|
||||
config: FnConfig;
|
||||
|
||||
/**
|
||||
* Arguments to pass to the function
|
||||
*/
|
||||
args: PassedArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a function from Aqua script
|
||||
*/
|
||||
export type CallAquaFunctionType = (args: CallAquaFunctionArgs) => Promise<unknown>;
|
||||
|
||||
/**
|
||||
* Arguments for registerService function
|
||||
*/
|
||||
export interface RegisterServiceArgs {
|
||||
/**
|
||||
* Peer to register the service on
|
||||
*/
|
||||
peer: IFluenceInternalApi;
|
||||
|
||||
/**
|
||||
* Service definition
|
||||
*/
|
||||
def: ServiceDef;
|
||||
|
||||
/**
|
||||
* Service id
|
||||
*/
|
||||
serviceId: string | undefined;
|
||||
|
||||
/**
|
||||
* Service implementation
|
||||
*/
|
||||
service: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a service defined in Aqua on a Fluence peer
|
||||
*/
|
||||
export type RegisterServiceType = (args: RegisterServiceArgs) => void;
|
@ -1,70 +1,36 @@
|
||||
import type { SecurityTetraplet } from '@fluencelabs/avm';
|
||||
import type { LogLevel } from '@fluencelabs/marine-js/dist/types';
|
||||
// import type { MultiaddrInput } from '@multiformats/multiaddr';
|
||||
import type { FnConfig, FunctionCallDef, ServiceDef } from './compilerSupport.js';
|
||||
|
||||
/**
|
||||
* Peer ID's id as a base58 string (multihash/CIDv0).
|
||||
/*
|
||||
* Copyright 2023 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 PeerIdB58 = string;
|
||||
|
||||
/**
|
||||
* Additional information about a service call
|
||||
* @typeparam ArgName
|
||||
*/
|
||||
export interface CallParams<ArgName extends string | null> {
|
||||
/**
|
||||
* The identifier of particle which triggered the call
|
||||
*/
|
||||
particleId: string;
|
||||
|
||||
/**
|
||||
* The peer id which created the particle
|
||||
*/
|
||||
initPeerId: PeerIdB58;
|
||||
|
||||
/**
|
||||
* Particle's timestamp when it was created
|
||||
*/
|
||||
timestamp: number;
|
||||
|
||||
/**
|
||||
* Time to live in milliseconds. The time after the particle should be expired
|
||||
*/
|
||||
ttl: number;
|
||||
|
||||
/**
|
||||
* Particle's signature
|
||||
*/
|
||||
signature?: string;
|
||||
|
||||
/**
|
||||
* Security tetraplets
|
||||
*/
|
||||
tetraplets: ArgName extends string ? Record<ArgName, SecurityTetraplet[]> : Record<string, never>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Node of the Fluence network specified as a pair of node's multiaddr and it's peer id
|
||||
*/
|
||||
type Node = {
|
||||
peerId: PeerIdB58;
|
||||
multiaddr: string;
|
||||
};
|
||||
|
||||
// TODO: either drop support for this exact type or get it back
|
||||
import type { Node } from './commonTypes.js';
|
||||
|
||||
/**
|
||||
* A node in Fluence network a client can connect to.
|
||||
* Can be in the form of:
|
||||
* - string: multiaddr in string format
|
||||
* - Multiaddr: multiaddr object, @see https://github.com/multiformats/js-multiaddr
|
||||
* - Node: node structure, @see Node
|
||||
*/
|
||||
export type RelayOptions = string | /* MultiaddrInput | */ Node;
|
||||
export type RelayOptions = string | Node;
|
||||
|
||||
/**
|
||||
* Fluence Peer's key pair types
|
||||
*/
|
||||
export type KeyTypes = 'RSA' | 'Ed25519' | 'secp256k1';
|
||||
|
||||
/**
|
||||
* Options to specify key pair used in Fluence Peer
|
||||
*/
|
||||
export type KeyPairOptions = {
|
||||
type: 'Ed25519';
|
||||
source: 'random' | Uint8Array;
|
||||
@ -73,13 +39,16 @@ export type KeyPairOptions = {
|
||||
/**
|
||||
* Configuration used when initiating Fluence Client
|
||||
*/
|
||||
export interface ClientOptions {
|
||||
export interface ClientConfig {
|
||||
/**
|
||||
* Specify the KeyPair to be used to identify the Fluence Peer.
|
||||
* Will be generated randomly if not specified
|
||||
*/
|
||||
keyPair?: KeyPairOptions;
|
||||
|
||||
/**
|
||||
* Options to configure the connection to the Fluence network
|
||||
*/
|
||||
connectionOptions?: {
|
||||
/**
|
||||
* When the peer established the connection to the network it sends a ping-like message to check if it works correctly.
|
||||
@ -92,6 +61,18 @@ export interface ClientOptions {
|
||||
* The dialing timeout in milliseconds
|
||||
*/
|
||||
dialTimeoutMs?: number;
|
||||
|
||||
/**
|
||||
* The maximum number of inbound streams for the libp2p node.
|
||||
* Default: 1024
|
||||
*/
|
||||
maxInboundStreams?: number;
|
||||
|
||||
/**
|
||||
* The maximum number of outbound streams for the libp2p node.
|
||||
* Default: 1024
|
||||
*/
|
||||
maxOutboundStreams?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -113,18 +94,31 @@ export interface ClientOptions {
|
||||
};
|
||||
}
|
||||
|
||||
export type FluenceStartConfig = ClientOptions & { relay: RelayOptions };
|
||||
|
||||
/**
|
||||
* Fluence JS Client connection states as string literals
|
||||
*/
|
||||
export const ConnectionStates = ['disconnected', 'connecting', 'connected', 'disconnecting'] as const;
|
||||
export type ConnectionState = typeof ConnectionStates[number];
|
||||
|
||||
export interface IFluenceClient {
|
||||
/**
|
||||
* Fluence JS Client connection states
|
||||
*/
|
||||
export type ConnectionState = (typeof ConnectionStates)[number];
|
||||
|
||||
export interface IFluenceInternalApi {
|
||||
/**
|
||||
* Internal API
|
||||
*/
|
||||
internals: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public API of Fluence JS Client
|
||||
*/
|
||||
export interface IFluenceClient extends IFluenceInternalApi {
|
||||
/**
|
||||
* Connect to the Fluence network
|
||||
* @param relay - relay node to connect to
|
||||
* @param options - client options
|
||||
*/
|
||||
connect: (relay: RelayOptions, options?: ClientOptions) => Promise<void>;
|
||||
connect: () => Promise<void>;
|
||||
|
||||
/**
|
||||
* Disconnect from the Fluence network
|
||||
@ -150,36 +144,11 @@ export interface IFluenceClient {
|
||||
* Return relay's public key as a base58 string (multihash/CIDv0).
|
||||
*/
|
||||
getRelayPeerId(): string;
|
||||
|
||||
// TODO: come up with a working interface for
|
||||
// - particle creation
|
||||
// - particle initialization
|
||||
// - service registration
|
||||
/**
|
||||
* For internal use only. Do not call directly
|
||||
*/
|
||||
internals: any;
|
||||
}
|
||||
|
||||
export interface CallAquaFunctionArgs {
|
||||
def: FunctionCallDef;
|
||||
script: string;
|
||||
config: FnConfig;
|
||||
peer: IFluenceClient;
|
||||
args: { [key: string]: any };
|
||||
}
|
||||
|
||||
export type CallAquaFunction = (args: CallAquaFunctionArgs) => Promise<unknown>;
|
||||
|
||||
export interface RegisterServiceArgs {
|
||||
peer: IFluenceClient;
|
||||
def: ServiceDef;
|
||||
serviceId: string | undefined;
|
||||
service: any;
|
||||
}
|
||||
|
||||
export type RegisterService = (args: RegisterServiceArgs) => void;
|
||||
|
||||
/**
|
||||
* For internal use. Checks if the object is a Fluence Peer
|
||||
*/
|
||||
export const asFluencePeer = (fluencePeerCandidate: unknown): IFluenceClient => {
|
||||
if (isFluencePeer(fluencePeerCandidate)) {
|
||||
return fluencePeerCandidate;
|
||||
@ -188,6 +157,9 @@ export const asFluencePeer = (fluencePeerCandidate: unknown): IFluenceClient =>
|
||||
throw new Error(`Argument ${fluencePeerCandidate} is not a Fluence Peer`);
|
||||
};
|
||||
|
||||
/**
|
||||
* For internal use. Checks if the object is a Fluence Peer
|
||||
*/
|
||||
export const isFluencePeer = (fluencePeerCandidate: unknown): fluencePeerCandidate is IFluenceClient => {
|
||||
if (fluencePeerCandidate && (fluencePeerCandidate as any).__isFluenceAwesome) {
|
||||
return true;
|
||||
|
@ -1,2 +1,19 @@
|
||||
export * from './compilerSupport.js'
|
||||
export * from './fluenceClient.js'
|
||||
/*
|
||||
* Copyright 2023 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 './compilerSupport/aquaTypeDefinitions.js';
|
||||
export * from './compilerSupport/compilerSupportInterface.js';
|
||||
export * from './commonTypes.js';
|
||||
export * from './fluenceClient.js';
|
||||
|
@ -12,7 +12,7 @@
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"compile-aqua": "fluence aqua -i ./aqua/ -o ./aqua",
|
||||
"test": "node ./copy-worker-script-workaround.mjs && vitest run"
|
||||
"test": "node ./copy-worker-script-workaround.mjs && vitest --threads false run"
|
||||
},
|
||||
"repository": "https://github.com/fluencelabs/fluence-js",
|
||||
"author": "Fluence Labs",
|
||||
|
133
packages/core/js-peer/src/clientPeer/ClientPeer.ts
Normal file
133
packages/core/js-peer/src/clientPeer/ClientPeer.ts
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2023 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 { ClientConfig, ConnectionState, IFluenceClient, PeerIdB58, RelayOptions } from '@fluencelabs/interfaces';
|
||||
import { RelayConnection, RelayConnectionConfig } from '../connection/RelayConnection.js';
|
||||
import { fromOpts, KeyPair } from '../keypair/index.js';
|
||||
import { FluencePeer, PeerConfig } from '../jsPeer/FluencePeer.js';
|
||||
import { relayOptionToMultiaddr } from '../util/libp2pUtils.js';
|
||||
import { IAvmRunner, IMarineHost } from '../marine/interfaces.js';
|
||||
import { JsServiceHost } from '../jsServiceHost/JsServiceHost.js';
|
||||
import { logger } from '../util/logger.js';
|
||||
|
||||
const log = logger('client');
|
||||
|
||||
const DEFAULT_TTL_MS = 7000;
|
||||
const MAX_OUTBOUND_STREAMS = 1024;
|
||||
const MAX_INBOUND_STREAMS = 1024;
|
||||
|
||||
export const makeClientPeerConfig = async (
|
||||
relay: RelayOptions,
|
||||
config: ClientConfig,
|
||||
): Promise<{ peerConfig: PeerConfig; relayConfig: RelayConnectionConfig; keyPair: KeyPair }> => {
|
||||
const opts = config?.keyPair || { type: 'Ed25519', source: 'random' };
|
||||
const keyPair = await fromOpts(opts);
|
||||
const relayAddress = relayOptionToMultiaddr(relay);
|
||||
|
||||
return {
|
||||
peerConfig: {
|
||||
debug: {
|
||||
printParticleId: config?.debug?.printParticleId || false,
|
||||
},
|
||||
defaultTtlMs: config?.defaultTtlMs || DEFAULT_TTL_MS,
|
||||
},
|
||||
relayConfig: {
|
||||
peerId: keyPair.getLibp2pPeerId(),
|
||||
relayAddress: relayAddress,
|
||||
dialTimeoutMs: config?.connectionOptions?.dialTimeoutMs,
|
||||
maxInboundStreams: config?.connectionOptions?.maxInboundStreams || MAX_OUTBOUND_STREAMS,
|
||||
maxOutboundStreams: config?.connectionOptions?.maxOutboundStreams || MAX_INBOUND_STREAMS,
|
||||
},
|
||||
keyPair: keyPair,
|
||||
};
|
||||
};
|
||||
|
||||
export class ClientPeer extends FluencePeer implements IFluenceClient {
|
||||
private relayPeerId: PeerIdB58;
|
||||
private relayConnection: RelayConnection;
|
||||
|
||||
constructor(
|
||||
peerConfig: PeerConfig,
|
||||
relayConfig: RelayConnectionConfig,
|
||||
keyPair: KeyPair,
|
||||
marine: IMarineHost,
|
||||
avmRunner: IAvmRunner,
|
||||
) {
|
||||
const relayConnection = new RelayConnection(relayConfig);
|
||||
|
||||
super(peerConfig, keyPair, marine, new JsServiceHost(), avmRunner, relayConnection);
|
||||
this.relayPeerId = relayConnection.getRelayPeerId();
|
||||
this.relayConnection = relayConnection;
|
||||
}
|
||||
|
||||
getPeerId(): string {
|
||||
return this.keyPair.getPeerId();
|
||||
}
|
||||
|
||||
getPeerSecretKey(): Uint8Array {
|
||||
return this.keyPair.toEd25519PrivateKey();
|
||||
}
|
||||
|
||||
connectionState: ConnectionState = 'disconnected';
|
||||
connectionStateChangeHandler: (state: ConnectionState) => void = () => {};
|
||||
|
||||
getRelayPeerId(): string {
|
||||
return this.relayPeerId;
|
||||
}
|
||||
|
||||
onConnectionStateChange(handler: (state: ConnectionState) => void): ConnectionState {
|
||||
this.connectionStateChangeHandler = handler;
|
||||
|
||||
return this.connectionState;
|
||||
}
|
||||
|
||||
private changeConnectionState(state: ConnectionState) {
|
||||
this.connectionState = state;
|
||||
this.connectionStateChangeHandler(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the Fluence network
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
return this.start();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Disconnect from the Fluence network
|
||||
// */
|
||||
async disconnect(): Promise<void> {
|
||||
return this.stop();
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
log.trace('connecting to Fluence network');
|
||||
this.changeConnectionState('connecting');
|
||||
await super.start();
|
||||
await this.relayConnection.start();
|
||||
// TODO: check connection (`checkConnection` function) here
|
||||
this.changeConnectionState('connected');
|
||||
log.trace('connected');
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
log.trace('disconnecting from Fluence network');
|
||||
this.changeConnectionState('disconnecting');
|
||||
await this.relayConnection.stop();
|
||||
await super.stop();
|
||||
this.changeConnectionState('disconnected');
|
||||
log.trace('disconnected');
|
||||
}
|
||||
}
|
187
packages/core/js-peer/src/clientPeer/__test__/client.spec.ts
Normal file
187
packages/core/js-peer/src/clientPeer/__test__/client.spec.ts
Normal file
@ -0,0 +1,187 @@
|
||||
import { it, describe, expect } from 'vitest';
|
||||
import { handleTimeout } from '../../particle/Particle.js';
|
||||
import { doNothing } from '../../jsServiceHost/serviceUtils.js';
|
||||
import { registerHandlersHelper, withClient } from '../../util/testUtils.js';
|
||||
import { checkConnection } from '../checkConnection.js';
|
||||
import { nodes, RELAY } from './connection.js';
|
||||
import { CallServiceData } from '../../jsServiceHost/interfaces.js';
|
||||
|
||||
describe('FluenceClient usage test suite', () => {
|
||||
it('should make a call through network', async () => {
|
||||
await withClient(RELAY, {}, async (peer) => {
|
||||
// arrange
|
||||
|
||||
const result = await new Promise<string[]>((resolve, reject) => {
|
||||
const script = `
|
||||
(xor
|
||||
(seq
|
||||
(call %init_peer_id% ("load" "relay") [] init_relay)
|
||||
(seq
|
||||
(call init_relay ("op" "identity") ["hello world!"] result)
|
||||
(call %init_peer_id% ("callback" "callback") [result])
|
||||
)
|
||||
)
|
||||
(seq
|
||||
(call init_relay ("op" "identity") [])
|
||||
(call %init_peer_id% ("callback" "error") [%last_error%])
|
||||
)
|
||||
)`;
|
||||
const particle = peer.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
return reject(particle.message);
|
||||
}
|
||||
|
||||
registerHandlersHelper(peer, particle, {
|
||||
load: {
|
||||
relay: () => {
|
||||
return peer.getRelayPeerId();
|
||||
},
|
||||
},
|
||||
callback: {
|
||||
callback: (args: any) => {
|
||||
const [val] = args;
|
||||
resolve(val);
|
||||
},
|
||||
error: (args: any) => {
|
||||
const [error] = args;
|
||||
reject(error);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
|
||||
expect(result).toBe('hello world!');
|
||||
});
|
||||
});
|
||||
|
||||
it('check connection should work', async function () {
|
||||
await withClient(RELAY, {}, async (peer) => {
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
expect(isConnected).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('check connection should work with ttl', async function () {
|
||||
await withClient(RELAY, {}, async (peer) => {
|
||||
const isConnected = await checkConnection(peer, 10000);
|
||||
|
||||
expect(isConnected).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('two clients should work inside the same time javascript process', async () => {
|
||||
await withClient(RELAY, {}, async (peer1) => {
|
||||
await withClient(RELAY, {}, async (peer2) => {
|
||||
const res = new Promise((resolve) => {
|
||||
peer2.internals.regHandler.common('test', 'test', (req: CallServiceData) => {
|
||||
resolve(req.args[0]);
|
||||
return {
|
||||
result: {},
|
||||
retCode: 0,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const script = `
|
||||
(seq
|
||||
(call "${peer1.getRelayPeerId()}" ("op" "identity") [])
|
||||
(call "${peer2.getPeerId()}" ("test" "test") ["test"])
|
||||
)
|
||||
`;
|
||||
const particle = peer1.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
throw particle;
|
||||
}
|
||||
|
||||
peer1.internals.initiateParticle(particle, doNothing);
|
||||
|
||||
expect(await res).toEqual('test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should make connection to network', () => {
|
||||
it('address as string', async () => {
|
||||
await withClient(nodes[0].multiaddr, {}, async (peer) => {
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
expect(isConnected).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('address as node', async () => {
|
||||
await withClient(nodes[0], {}, async (peer) => {
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
expect(isConnected).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('With connection options: dialTimeout', async () => {
|
||||
await withClient(RELAY, { connectionOptions: { dialTimeoutMs: 100000 } }, async (peer) => {
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
expect(isConnected).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('With connection options: skipCheckConnection', async () => {
|
||||
await withClient(RELAY, { connectionOptions: { skipCheckConnection: true } }, async (peer) => {
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
expect(isConnected).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('With connection options: defaultTTL', async () => {
|
||||
await withClient(RELAY, { defaultTtlMs: 1 }, async (peer) => {
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
expect(isConnected).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('Should throw correct error when the client tries to send a particle not to the relay', async () => {
|
||||
await withClient(RELAY, {}, async (peer) => {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
const script = `
|
||||
(xor
|
||||
(call "incorrect_peer_id" ("any" "service") [])
|
||||
(call %init_peer_id% ("callback" "error") [%last_error%])
|
||||
)`;
|
||||
const particle = peer.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
return reject(particle.message);
|
||||
}
|
||||
|
||||
registerHandlersHelper(peer, particle, {
|
||||
callback: {
|
||||
error: (args: any) => {
|
||||
const [error] = args;
|
||||
reject(error);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, (stage) => {
|
||||
if (stage.stage === 'sendingError') {
|
||||
reject(stage.errorMessage);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await promise;
|
||||
|
||||
await expect(promise).rejects.toMatch(
|
||||
'Particle is expected to be sent to only the single peer (relay which client is connected to)',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -15,3 +15,5 @@ export const nodes = [
|
||||
peerId: '12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3',
|
||||
},
|
||||
];
|
||||
|
||||
export const RELAY = nodes[0].multiaddr;
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
* Copyright 2023 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.
|
||||
@ -13,39 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Buffer } from 'buffer';
|
||||
import { CallServiceData, CallServiceResult, CallServiceResultType, ResultCodes } from '../interfaces/commonTypes.js';
|
||||
import { FluencePeer } from './FluencePeer.js';
|
||||
import { ParticleExecutionStage } from './Particle.js';
|
||||
import { ClientPeer } from './ClientPeer.js';
|
||||
|
||||
import { logger } from '../util/logger.js';
|
||||
import { WrapFnIntoServiceCall } from '../jsServiceHost/serviceUtils.js';
|
||||
import { handleTimeout } from '../particle/Particle.js';
|
||||
|
||||
const log = logger('connection');
|
||||
|
||||
export const MakeServiceCall =
|
||||
(fn: (args: any[]) => CallServiceResultType) =>
|
||||
(req: CallServiceData): CallServiceResult => ({
|
||||
retCode: ResultCodes.success,
|
||||
result: fn(req.args),
|
||||
});
|
||||
|
||||
export const handleTimeout = (fn: () => void) => (stage: ParticleExecutionStage) => {
|
||||
if (stage.stage === 'expired') {
|
||||
fn();
|
||||
}
|
||||
};
|
||||
export const doNothing = (..._args: Array<unknown>) => undefined;
|
||||
|
||||
/**
|
||||
* Checks the network connection by sending a ping-like request to relay node
|
||||
* @param { FluenceClient } peer - The Fluence Client instance.
|
||||
* @param { ClientPeer } peer - The Fluence Client instance.
|
||||
*/
|
||||
export const checkConnection = async (peer: FluencePeer, ttl?: number): Promise<boolean> => {
|
||||
if (!peer.getStatus().isConnected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export const checkConnection = async (peer: ClientPeer, ttl?: number): Promise<boolean> => {
|
||||
const msg = Math.random().toString(36).substring(7);
|
||||
|
||||
const promise = new Promise<string>((resolve, reject) => {
|
||||
@ -76,8 +56,8 @@ export const checkConnection = async (peer: FluencePeer, ttl?: number): Promise<
|
||||
particle.id,
|
||||
'load',
|
||||
'relay',
|
||||
MakeServiceCall(() => {
|
||||
return peer.getStatus().relayPeerId;
|
||||
WrapFnIntoServiceCall(() => {
|
||||
return peer.getRelayPeerId();
|
||||
}),
|
||||
);
|
||||
|
||||
@ -85,7 +65,7 @@ export const checkConnection = async (peer: FluencePeer, ttl?: number): Promise<
|
||||
particle.id,
|
||||
'load',
|
||||
'msg',
|
||||
MakeServiceCall(() => {
|
||||
WrapFnIntoServiceCall(() => {
|
||||
return msg;
|
||||
}),
|
||||
);
|
||||
@ -94,7 +74,7 @@ export const checkConnection = async (peer: FluencePeer, ttl?: number): Promise<
|
||||
particle.id,
|
||||
'callback',
|
||||
'callback',
|
||||
MakeServiceCall((args) => {
|
||||
WrapFnIntoServiceCall((args) => {
|
||||
const [val] = args;
|
||||
setTimeout(() => {
|
||||
resolve(val);
|
||||
@ -107,7 +87,7 @@ export const checkConnection = async (peer: FluencePeer, ttl?: number): Promise<
|
||||
particle.id,
|
||||
'callback',
|
||||
'error',
|
||||
MakeServiceCall((args) => {
|
||||
WrapFnIntoServiceCall((args) => {
|
||||
const [error] = args;
|
||||
setTimeout(() => {
|
||||
reject(error);
|
||||
@ -131,23 +111,7 @@ export const checkConnection = async (peer: FluencePeer, ttl?: number): Promise<
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
log.error('error on establishing connection. Relay: %s error: %j', e, peer.getStatus().relayPeerId);
|
||||
log.error('error on establishing connection. Relay: %s error: %j', e, peer.getRelayPeerId());
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export function jsonify(obj: unknown) {
|
||||
return JSON.stringify(obj, null, 4);
|
||||
}
|
||||
|
||||
export const isString = (x: unknown): x is string => {
|
||||
return x !== null && typeof x === 'string';
|
||||
};
|
||||
|
||||
export class ServiceError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
|
||||
Object.setPrototypeOf(this, ServiceError.prototype);
|
||||
}
|
||||
}
|
@ -1,13 +1,19 @@
|
||||
import {
|
||||
ArrowWithoutCallbacks,
|
||||
FnConfig,
|
||||
FunctionCallDef,
|
||||
NonArrowType,
|
||||
getArgumentTypes,
|
||||
isReturnTypeVoid,
|
||||
IFluenceClient,
|
||||
CallAquaFunction,
|
||||
} from '@fluencelabs/interfaces';
|
||||
/*
|
||||
* Copyright 2023 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 { getArgumentTypes, isReturnTypeVoid, CallAquaFunctionType } from '@fluencelabs/interfaces';
|
||||
|
||||
import {
|
||||
injectRelayService,
|
||||
@ -34,7 +40,7 @@ const log = logger('aqua');
|
||||
* @param args - args in the form of JSON where each key corresponds to the name of the argument
|
||||
* @returns
|
||||
*/
|
||||
export const callAquaFunction: CallAquaFunction = ({ def, script, config, peer, args }) => {
|
||||
export const callAquaFunction: CallAquaFunctionType = ({ def, script, config, peer, args }) => {
|
||||
log.trace('calling aqua function %j', { def, script, config, args });
|
||||
const argumentTypes = getArgumentTypes(def);
|
||||
|
||||
|
@ -1,7 +1,22 @@
|
||||
import { jsonify } from '../js-peer/utils.js';
|
||||
/*
|
||||
* Copyright 2023 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 { jsonify } from '../util/utils.js';
|
||||
import { match } from 'ts-pattern';
|
||||
import type { ArrowType, ArrowWithoutCallbacks, NonArrowType } from '@fluencelabs/interfaces';
|
||||
import { CallServiceData } from '../interfaces/commonTypes.js';
|
||||
import { CallServiceData } from '../jsServiceHost/interfaces.js';
|
||||
|
||||
/**
|
||||
* Convert value from its representation in aqua language to representation in typescript
|
||||
|
@ -1,11 +1,26 @@
|
||||
import type { RegisterService } from '@fluencelabs/interfaces';
|
||||
/*
|
||||
* Copyright 2023 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 type { RegisterServiceType } from '@fluencelabs/interfaces';
|
||||
import { registerGlobalService, userHandlerService } from './services.js';
|
||||
|
||||
import { logger } from '../util/logger.js';
|
||||
|
||||
const log = logger('aqua');
|
||||
|
||||
export const registerService: RegisterService = ({ peer, def, serviceId, service }) => {
|
||||
export const registerService: RegisterServiceType = ({ peer, def, serviceId, service }) => {
|
||||
log.trace('registering aqua service %o', { def, serviceId, service });
|
||||
|
||||
// Checking for missing keys
|
||||
|
@ -1,18 +1,33 @@
|
||||
/*
|
||||
* Copyright 2023 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 { SecurityTetraplet } from '@fluencelabs/avm';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { Particle } from '../js-peer/Particle.js';
|
||||
import { CallServiceData, GenericCallServiceHandler, ResultCodes } from '../interfaces/commonTypes.js';
|
||||
import { Particle } from '../particle/Particle.js';
|
||||
|
||||
import { aquaArgs2Ts, responseServiceValue2ts, returnType2Aqua, ts2aqua } from './conversions.js';
|
||||
import {
|
||||
IFluenceClient,
|
||||
CallParams,
|
||||
ArrowWithoutCallbacks,
|
||||
FunctionCallConstants,
|
||||
FunctionCallDef,
|
||||
NonArrowType,
|
||||
IFluenceInternalApi,
|
||||
} from '@fluencelabs/interfaces';
|
||||
import { CallServiceData, GenericCallServiceHandler, ResultCodes } from '../jsServiceHost/interfaces.js';
|
||||
|
||||
export interface ServiceDescription {
|
||||
serviceId: string;
|
||||
@ -23,7 +38,7 @@ export interface ServiceDescription {
|
||||
/**
|
||||
* Creates a service which injects relay's peer id into aqua space
|
||||
*/
|
||||
export const injectRelayService = (def: FunctionCallDef, peer: IFluenceClient) => {
|
||||
export const injectRelayService = (def: FunctionCallDef, peer: IFluenceInternalApi) => {
|
||||
return {
|
||||
serviceId: def.names.getDataSrv,
|
||||
fnName: def.names.relay,
|
||||
@ -168,10 +183,14 @@ const extractCallParams = (req: CallServiceData, arrow: ArrowWithoutCallbacks):
|
||||
return callParams;
|
||||
};
|
||||
|
||||
export const registerParticleScopeService = (peer: IFluenceClient, particle: Particle, service: ServiceDescription) => {
|
||||
export const registerParticleScopeService = (
|
||||
peer: IFluenceInternalApi,
|
||||
particle: Particle,
|
||||
service: ServiceDescription,
|
||||
) => {
|
||||
peer.internals.regHandler.forParticle(particle.id, service.serviceId, service.fnName, service.handler);
|
||||
};
|
||||
|
||||
export const registerGlobalService = (peer: IFluenceClient, service: ServiceDescription) => {
|
||||
export const registerGlobalService = (peer: IFluenceInternalApi, service: ServiceDescription) => {
|
||||
peer.internals.regHandler.common(service.serviceId, service.fnName, service.handler);
|
||||
};
|
||||
|
@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { PeerIdB58 } from '@fluencelabs/interfaces';
|
||||
import { FluenceConnection, ParticleHandler } from '../interfaces/index.js';
|
||||
import { pipe } from 'it-pipe';
|
||||
import { encode, decode } from 'it-length-prefixed';
|
||||
import type { PeerId } from '@libp2p/interface-peer-id';
|
||||
@ -25,23 +24,28 @@ import { mplex } from '@libp2p/mplex';
|
||||
import { webSockets } from '@libp2p/websockets';
|
||||
import { all } from '@libp2p/websockets/filters';
|
||||
import { multiaddr } from '@multiformats/multiaddr';
|
||||
import type { MultiaddrInput, Multiaddr } from '@multiformats/multiaddr';
|
||||
import type { Connection } from '@libp2p/interface-connection';
|
||||
import type { Multiaddr } from '@multiformats/multiaddr';
|
||||
|
||||
import map from 'it-map';
|
||||
import { fromString } from 'uint8arrays/from-string';
|
||||
import { toString } from 'uint8arrays/to-string';
|
||||
|
||||
import { logger } from '../util/logger.js';
|
||||
import { Subject } from 'rxjs';
|
||||
import { throwIfHasNoPeerId } from '../util/libp2pUtils.js';
|
||||
import { IConnection } from './interfaces.js';
|
||||
import { IParticle } from '../particle/interfaces.js';
|
||||
import { Particle, serializeToString } from '../particle/Particle.js';
|
||||
import { IStartable } from '../util/commonTypes.js';
|
||||
|
||||
const log = logger('connection');
|
||||
|
||||
export const PROTOCOL_NAME = '/fluence/particle/2.0.0';
|
||||
|
||||
/**
|
||||
* Options to configure fluence connection
|
||||
* Options to configure fluence relay connection
|
||||
*/
|
||||
export interface FluenceConnectionOptions {
|
||||
export interface RelayConnectionConfig {
|
||||
/**
|
||||
* Peer id of the Fluence Peer
|
||||
*/
|
||||
@ -50,32 +54,57 @@ export interface FluenceConnectionOptions {
|
||||
/**
|
||||
* Multiaddress of the relay to make connection to
|
||||
*/
|
||||
relayAddress: MultiaddrInput;
|
||||
relayAddress: Multiaddr;
|
||||
|
||||
/**
|
||||
* The dialing timeout in milliseconds
|
||||
*/
|
||||
dialTimeoutMs?: number;
|
||||
|
||||
/**
|
||||
* The maximum number of inbound streams for the libp2p node.
|
||||
* Default: 1024
|
||||
*/
|
||||
maxInboundStreams: number;
|
||||
|
||||
/**
|
||||
* The maximum number of outbound streams for the libp2p node.
|
||||
* Default: 1024
|
||||
*/
|
||||
maxOutboundStreams: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation for JS peers which connects to Fluence through relay node
|
||||
*/
|
||||
export class RelayConnection extends FluenceConnection {
|
||||
constructor(
|
||||
public peerId: PeerIdB58,
|
||||
private _lib2p2Peer: Libp2p,
|
||||
private _relayAddress: Multiaddr,
|
||||
public readonly relayPeerId: PeerIdB58,
|
||||
) {
|
||||
super();
|
||||
export class RelayConnection implements IStartable, IConnection {
|
||||
private relayAddress: Multiaddr;
|
||||
private lib2p2Peer: Libp2p | null = null;
|
||||
|
||||
constructor(private config: RelayConnectionConfig) {
|
||||
this.relayAddress = multiaddr(this.config.relayAddress);
|
||||
throwIfHasNoPeerId(this.relayAddress);
|
||||
}
|
||||
|
||||
private _connection?: Connection;
|
||||
getRelayPeerId(): string {
|
||||
// since we check for peer id in constructor, we can safely use ! here
|
||||
return this.relayAddress.getPeerId()!;
|
||||
}
|
||||
|
||||
supportsRelay(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
particleSource = new Subject<IParticle>();
|
||||
|
||||
async start(): Promise<void> {
|
||||
// check if already started
|
||||
if (this.lib2p2Peer !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
static async createConnection(options: FluenceConnectionOptions): Promise<RelayConnection> {
|
||||
const lib2p2Peer = await createLibp2p({
|
||||
peerId: options.peerId,
|
||||
peerId: this.config.peerId,
|
||||
transports: [
|
||||
webSockets({
|
||||
filter: all,
|
||||
@ -83,30 +112,32 @@ export class RelayConnection extends FluenceConnection {
|
||||
],
|
||||
streamMuxers: [mplex()],
|
||||
connectionEncryption: [noise()],
|
||||
connectionManager: {
|
||||
dialTimeout: this.config.dialTimeoutMs,
|
||||
},
|
||||
});
|
||||
|
||||
const relayMultiaddr = multiaddr(options.relayAddress);
|
||||
const relayPeerId = relayMultiaddr.getPeerId();
|
||||
if (relayPeerId === null) {
|
||||
throw new Error('Specified multiaddr is invalid or missing peer id: ' + options.relayAddress);
|
||||
this.lib2p2Peer = lib2p2Peer;
|
||||
this.lib2p2Peer.start();
|
||||
await this.connect();
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
// check if already stopped
|
||||
if (this.lib2p2Peer === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new RelayConnection(
|
||||
// force new line
|
||||
options.peerId.toString(),
|
||||
lib2p2Peer,
|
||||
relayMultiaddr,
|
||||
relayPeerId,
|
||||
);
|
||||
await this.lib2p2Peer.unhandle(PROTOCOL_NAME);
|
||||
await this.lib2p2Peer.stop();
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
await this._lib2p2Peer.unhandle(PROTOCOL_NAME);
|
||||
await this._lib2p2Peer.stop();
|
||||
}
|
||||
async sendParticle(nextPeerIds: PeerIdB58[], particle: IParticle): Promise<void> {
|
||||
if (this.lib2p2Peer === null) {
|
||||
throw new Error('Relay connection is not started');
|
||||
}
|
||||
|
||||
async sendParticle(nextPeerIds: PeerIdB58[], particle: string): Promise<void> {
|
||||
if (nextPeerIds.length !== 1 && nextPeerIds[0] !== this.relayPeerId) {
|
||||
if (nextPeerIds.length !== 1 && nextPeerIds[0] !== this.getRelayPeerId()) {
|
||||
throw new Error(
|
||||
`Relay connection only accepts peer id of the connected relay. Got: ${JSON.stringify(
|
||||
nextPeerIds,
|
||||
@ -123,27 +154,23 @@ export class RelayConnection extends FluenceConnection {
|
||||
const sink = this._connection.streams[0].sink;
|
||||
*/
|
||||
|
||||
const stream = await this._lib2p2Peer.dialProtocol(this._relayAddress, PROTOCOL_NAME);
|
||||
const stream = await this.lib2p2Peer.dialProtocol(this.relayAddress, PROTOCOL_NAME);
|
||||
const sink = stream.sink;
|
||||
|
||||
pipe(
|
||||
[fromString(particle)],
|
||||
[fromString(serializeToString(particle))],
|
||||
// @ts-ignore
|
||||
encode(),
|
||||
sink,
|
||||
);
|
||||
}
|
||||
|
||||
async connect(onIncomingParticle: ParticleHandler) {
|
||||
await this._lib2p2Peer.start();
|
||||
private async connect() {
|
||||
if (this.lib2p2Peer === null) {
|
||||
throw new Error('Relay connection is not started');
|
||||
}
|
||||
|
||||
// TODO: make it configurable
|
||||
const handleOptions = {
|
||||
maxInboundStreams: 1024,
|
||||
maxOutboundStreams: 1024,
|
||||
};
|
||||
|
||||
this._lib2p2Peer.handle(
|
||||
this.lib2p2Peer.handle(
|
||||
[PROTOCOL_NAME],
|
||||
async ({ connection, stream }) => {
|
||||
pipe(
|
||||
@ -156,7 +183,8 @@ export class RelayConnection extends FluenceConnection {
|
||||
try {
|
||||
for await (const msg of source) {
|
||||
try {
|
||||
onIncomingParticle(msg);
|
||||
const particle = Particle.fromString(msg);
|
||||
this.particleSource.next(particle);
|
||||
} catch (e) {
|
||||
log.error('error on handling a new incoming message: %j', e);
|
||||
}
|
||||
@ -167,17 +195,20 @@ export class RelayConnection extends FluenceConnection {
|
||||
},
|
||||
);
|
||||
},
|
||||
handleOptions,
|
||||
{
|
||||
maxInboundStreams: this.config.maxInboundStreams,
|
||||
maxOutboundStreams: this.config.maxOutboundStreams,
|
||||
},
|
||||
);
|
||||
|
||||
log.debug("dialing to the node with client's address: %s", this._lib2p2Peer.peerId.toString());
|
||||
log.debug("dialing to the node with client's address: %s", this.lib2p2Peer.peerId.toString());
|
||||
|
||||
try {
|
||||
this._connection = await this._lib2p2Peer.dial(this._relayAddress);
|
||||
await this.lib2p2Peer.dial(this.relayAddress);
|
||||
} catch (e: any) {
|
||||
if (e.name === 'AggregateError' && e._errors?.length === 1) {
|
||||
const error = e._errors[0];
|
||||
throw new Error(`Error dialing node ${this._relayAddress}:\n${error.code}\n${error.message}`);
|
||||
throw new Error(`Error dialing node ${this.relayAddress}:\n${error.code}\n${error.message}`);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
45
packages/core/js-peer/src/connection/interfaces.ts
Normal file
45
packages/core/js-peer/src/connection/interfaces.ts
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2023 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 type { PeerIdB58 } from '@fluencelabs/interfaces';
|
||||
import type { Subscribable } from 'rxjs';
|
||||
import { IParticle } from '../particle/interfaces.js';
|
||||
|
||||
/**
|
||||
* Interface for connection used in Fluence Peer.
|
||||
*/
|
||||
export interface IConnection {
|
||||
/**
|
||||
* Observable that emits particles received from the connection.
|
||||
*/
|
||||
particleSource: Subscribable<IParticle>;
|
||||
|
||||
/**
|
||||
* Send particle to the network using the connection.
|
||||
* @param nextPeerIds - list of peer ids to send the particle to
|
||||
* @param particle - particle to send
|
||||
*/
|
||||
sendParticle(nextPeerIds: PeerIdB58[], particle: IParticle): Promise<void>;
|
||||
|
||||
/**
|
||||
* Get peer id of the relay peer. Throws an error if the connection doesn't support relay.
|
||||
*/
|
||||
getRelayPeerId(): PeerIdB58;
|
||||
|
||||
/**
|
||||
* Check if the connection supports relay.
|
||||
*/
|
||||
supportsRelay(): boolean;
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
import { it, describe, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { DEFAULT_CONFIG, FluencePeer } from '../../jsPeer/FluencePeer.js';
|
||||
import { CallServiceData, ResultCodes } from '../../jsServiceHost/interfaces.js';
|
||||
import { KeyPair } from '../../keypair/index.js';
|
||||
import { EphemeralNetworkClient } from '../client.js';
|
||||
|
||||
import { EphemeralNetwork, defaultConfig } from '../network.js';
|
||||
|
||||
let en: EphemeralNetwork;
|
||||
let client: FluencePeer;
|
||||
const relay = defaultConfig.peers[0].peerId;
|
||||
|
||||
// TODO: race condition here. Needs to be fixed
|
||||
describe.skip('Ephemeral networks tests', () => {
|
||||
beforeEach(async () => {
|
||||
en = new EphemeralNetwork(defaultConfig);
|
||||
await en.up();
|
||||
|
||||
const kp = await KeyPair.randomEd25519();
|
||||
client = new EphemeralNetworkClient(DEFAULT_CONFIG, kp, en, relay);
|
||||
await client.start();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (client) {
|
||||
await client.stop();
|
||||
}
|
||||
if (en) {
|
||||
await en.down();
|
||||
}
|
||||
});
|
||||
|
||||
it('smoke test', async function () {
|
||||
// arrange
|
||||
const peers = defaultConfig.peers.map((x) => x.peerId);
|
||||
|
||||
const script = `
|
||||
(seq
|
||||
(call "${relay}" ("op" "noop") [])
|
||||
(seq
|
||||
(call "${peers[1]}" ("op" "noop") [])
|
||||
(seq
|
||||
(call "${peers[2]}" ("op" "noop") [])
|
||||
(seq
|
||||
(call "${peers[3]}" ("op" "noop") [])
|
||||
(seq
|
||||
(call "${peers[4]}" ("op" "noop") [])
|
||||
(seq
|
||||
(call "${peers[5]}" ("op" "noop") [])
|
||||
(seq
|
||||
(call "${relay}" ("op" "noop") [])
|
||||
(call %init_peer_id% ("test" "test") [])
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
`;
|
||||
|
||||
const particle = client.internals.createNewParticle(script);
|
||||
|
||||
const promise = new Promise<string>((resolve) => {
|
||||
client.internals.regHandler.forParticle(particle.id, 'test', 'test', (req: CallServiceData) => {
|
||||
resolve('success');
|
||||
return {
|
||||
result: 'test',
|
||||
retCode: ResultCodes.success,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// act
|
||||
client.internals.initiateParticle(particle, () => {});
|
||||
|
||||
// assert
|
||||
await expect(promise).resolves.toBe('success');
|
||||
});
|
||||
});
|
39
packages/core/js-peer/src/ephemeral/client.ts
Normal file
39
packages/core/js-peer/src/ephemeral/client.ts
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2023 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 { PeerIdB58 } from '@fluencelabs/interfaces';
|
||||
import { MarineBasedAvmRunner } from '../jsPeer/avm.js';
|
||||
import { FluencePeer, PeerConfig } from '../jsPeer/FluencePeer.js';
|
||||
import { KeyPair } from '../keypair/index.js';
|
||||
import { WasmLoaderFromNpm } from '../marine/deps-loader/node.js';
|
||||
import { WorkerLoader } from '../marine/worker-script/workerLoader.js';
|
||||
import { MarineBackgroundRunner } from '../marine/worker/index.js';
|
||||
import { EphemeralNetwork } from './network.js';
|
||||
import { JsServiceHost } from '../jsServiceHost/JsServiceHost.js';
|
||||
|
||||
/**
|
||||
* Ephemeral network client is a FluencePeer that connects to a relay peer in an ephemeral network.
|
||||
*/
|
||||
export class EphemeralNetworkClient extends FluencePeer {
|
||||
constructor(config: PeerConfig, keyPair: KeyPair, network: EphemeralNetwork, relay: PeerIdB58) {
|
||||
const workerLoader = new WorkerLoader();
|
||||
const controlModuleLoader = new WasmLoaderFromNpm('@fluencelabs/marine-js', 'marine-js.wasm');
|
||||
const avmModuleLoader = new WasmLoaderFromNpm('@fluencelabs/avm', 'avm.wasm');
|
||||
const marine = new MarineBackgroundRunner(workerLoader, controlModuleLoader);
|
||||
const avm = new MarineBasedAvmRunner(marine, avmModuleLoader);
|
||||
const conn = network.getRelayConnection(keyPair.getPeerId(), relay);
|
||||
super(config, keyPair, marine, new JsServiceHost(), avm, conn);
|
||||
}
|
||||
}
|
290
packages/core/js-peer/src/ephemeral/network.ts
Normal file
290
packages/core/js-peer/src/ephemeral/network.ts
Normal file
@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright 2023 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 { PeerIdB58 } from '@fluencelabs/interfaces';
|
||||
import { fromBase64Sk, KeyPair } from '../keypair/index.js';
|
||||
import { MarineBackgroundRunner } from '../marine/worker/index.js';
|
||||
|
||||
import { WorkerLoaderFromFs } from '../marine/deps-loader/node.js';
|
||||
|
||||
import { logger } from '../util/logger.js';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Particle } from '../particle/Particle.js';
|
||||
|
||||
import { WasmLoaderFromNpm } from '../marine/deps-loader/node.js';
|
||||
import { MarineBasedAvmRunner } from '../jsPeer/avm.js';
|
||||
import { DEFAULT_CONFIG, FluencePeer } from '../jsPeer/FluencePeer.js';
|
||||
import { IConnection } from '../connection/interfaces.js';
|
||||
import { IAvmRunner, IMarineHost } from '../marine/interfaces.js';
|
||||
import { JsServiceHost } from '../jsServiceHost/JsServiceHost.js';
|
||||
|
||||
const log = logger('ephemeral');
|
||||
|
||||
interface EphemeralConfig {
|
||||
peers: Array<{
|
||||
peerId: PeerIdB58;
|
||||
sk: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const defaultConfig = {
|
||||
peers: [
|
||||
{
|
||||
peerId: '12D3KooWJankP2PcEDYCZDdJ26JsU8BMRfdGWyGqbtFiWyoKVtmx',
|
||||
sk: 'dWNAHhDVuFj9bEieILMu6TcCFRxBJdOPIvAWmf4sZQI=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWSBTB5sYxdwayUyTnqopBwABsnGFY3p4dTx5hABYDtJjV',
|
||||
sk: 'dOmaxAeu4Th+MJ22vRDLMFTNbiDgKNXar9fW9ofAMgQ=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWQjwf781DJ41moW5RrZXypLdnTbo6aMsoA8QLctGGX8RB',
|
||||
sk: 'TgzaLlxXuOMDNuuuTKEHUKsW0jM4AmX0gahFvkB1KgE=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWCXWTLFyY1mqKnNAhLQTsjW1zqDzCMbUs8M4a8zdz28HK',
|
||||
sk: 'hiO2Ta8g2ibMQ7iu5yj9CfN+qQCwE8oRShjr7ortKww=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWPmZpf4ng6GMS39HLagxsXbjiTPLH5CFJpFAHyN6amw6V',
|
||||
sk: 'LzJtOHTqxfrlHDW40BKiLfjai8JU4yW6/s2zrXLCcQE=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWKrx8PZxM1R9A8tp2jmrFf6c6q1ZQiWfD4QkNgh7fWSoF',
|
||||
sk: 'XMhlk/xr1FPcp7sKQhS18doXlq1x16EMhBC2NGW2LQ4=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWCbJHvnzSZEXjR1UJmtSUozuJK13iRiCYHLN1gjvm4TZZ',
|
||||
sk: 'KXPAIqxrSHr7v0ngv3qagcqivFvnQ0xd3s1/rKmi8QU=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWEvKe7WQHp42W4xhHRgTAWQjtDWyH38uJbLHAsMuTtYvD',
|
||||
sk: 'GCYMAshGnsrNtrHhuT7ayzh5uCzX99J03PmAXoOcCgw=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWSznSHN3BGrSykBXkLkFsqo9SYB73wVauVdqeuRt562cC',
|
||||
sk: 'UP+SEuznS0h259VbFquzyOJAQ4W5iIwhP+hd1PmUQQ0=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWF57jwbShfnT3c4dNfRDdGjr6SQ3B71m87UVpEpSWHFwi',
|
||||
sk: '8dl+Crm5RSh0eh+LqLKwX8/Eo4QLpvIjfD8L0wzX4A4=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWBWrzpSg9nwMLBCa2cJubUjTv63Mfy6PYg9rHGbetaV5C',
|
||||
sk: 'qolc1FcpJ+vHDon0HeXdUYnstjV1wiVx2p0mjblrfAg=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWNkLVU6juM8oyN2SVq5nBd2kp7Rf4uzJH1hET6vj6G5j6',
|
||||
sk: 'vN6QzWILTM7hSHp+iGkKxiXcqs8bzlnH3FPaRaDGSQY=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWKo1YwGL5vivPiKJMJS7wjtB6B2nJNdSXPkSABT4NKBUU',
|
||||
sk: 'YbDQ++bsor2kei7rYAsu2SbyoiOYPRzFRZWnNRUpBgQ=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWLUyBKmmNCyxaPkXoWcUFPcy5qrZsUo2E1tyM6CJmGJvC',
|
||||
sk: 'ptB9eSFMKudAtHaFgDrRK/1oIMrhBujxbMw2Pzwx/wA=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWAEZXME4KMu9FvLezsJWDbYFe2zyujyMnDT1AgcAxgcCk',
|
||||
sk: 'xtwTOKgAbDIgkuPf7RKiR7gYyZ1HY4mOgFMv3sOUcAQ=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWEhXetsFVAD9h2dRz9XgFpfidho1TCZVhFrczX8h8qgzY',
|
||||
sk: '1I2MGuiKG1F4FDMiRihVOcOP2mxzOLWJ99MeexK27A4=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWDBfVNdMyV3hPEF4WLBmx9DwD2t2SYuqZ2mztYmDzZWM1',
|
||||
sk: 'eqJ4Bp7iN4aBXgPH0ezwSg+nVsatkYtfrXv9obI0YQ0=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWSyY7wiSiR4vbXa1WtZawi3ackMTqcQhEPrvqtagoWPny',
|
||||
sk: 'UVM3SBJhPYIY/gafpnd9/q/Fn9V4BE9zkgrvF1T7Pgc=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWFZmBMGG9PxTs9s6ASzkLGKJWMyPheA5ruaYc2FDkDTmv',
|
||||
sk: '8RbZfEVpQhPVuhv64uqxENDuSoyJrslQoSQJznxsTQ0=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWBbhUaqqur6KHPunnKxXjY1daCtqJdy4wRji89LmAkVB4',
|
||||
sk: 'RbgKmG6soWW9uOi7yRedm+0Qck3f3rw6MSnDP7AcBQs=',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export interface IEphemeralConnection extends IConnection {
|
||||
readonly selfPeerId: PeerIdB58;
|
||||
readonly connections: Map<PeerIdB58, IEphemeralConnection>;
|
||||
receiveParticle(particle: Particle): void;
|
||||
}
|
||||
|
||||
export class EphemeralConnection implements IConnection, IEphemeralConnection {
|
||||
readonly selfPeerId: PeerIdB58;
|
||||
readonly connections: Map<PeerIdB58, IEphemeralConnection> = new Map();
|
||||
|
||||
constructor(selfPeerId: PeerIdB58) {
|
||||
this.selfPeerId = selfPeerId;
|
||||
}
|
||||
|
||||
connectToOther(other: IEphemeralConnection) {
|
||||
if (other.selfPeerId === this.selfPeerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.connections.set(other.selfPeerId, other);
|
||||
other.connections.set(this.selfPeerId, this);
|
||||
}
|
||||
|
||||
disconnectFromOther(other: IEphemeralConnection) {
|
||||
this.connections.delete(other.selfPeerId);
|
||||
other.connections.delete(this.selfPeerId);
|
||||
}
|
||||
|
||||
disconnectFromAll() {
|
||||
for (let other of this.connections.values()) {
|
||||
this.disconnectFromOther(other);
|
||||
}
|
||||
}
|
||||
|
||||
particleSource = new Subject<Particle>();
|
||||
|
||||
receiveParticle(particle: Particle): void {
|
||||
this.particleSource.next(Particle.fromString(particle.toString()));
|
||||
}
|
||||
|
||||
async sendParticle(nextPeerIds: string[], particle: Particle): Promise<void> {
|
||||
const from = this.selfPeerId;
|
||||
for (let to of nextPeerIds) {
|
||||
const destConnection = this.connections.get(to);
|
||||
if (destConnection === undefined) {
|
||||
log.error('peer %s has no connection with %s', from, to);
|
||||
continue;
|
||||
}
|
||||
|
||||
// log.trace(`Sending particle from %s, to %j, particleId %s`, from, to, particle.id);
|
||||
destConnection.receiveParticle(particle);
|
||||
}
|
||||
}
|
||||
|
||||
getRelayPeerId(): string {
|
||||
if (this.connections.size === 1) {
|
||||
return this.connections.keys().next().value;
|
||||
}
|
||||
|
||||
throw new Error('relay is not supported in this Ephemeral network peer');
|
||||
}
|
||||
|
||||
supportsRelay(): boolean {
|
||||
return this.connections.size === 1;
|
||||
}
|
||||
}
|
||||
|
||||
class EphemeralPeer extends FluencePeer {
|
||||
ephemeralConnection: EphemeralConnection;
|
||||
|
||||
constructor(keyPair: KeyPair, marine: IMarineHost, avm: IAvmRunner) {
|
||||
const conn = new EphemeralConnection(keyPair.getPeerId());
|
||||
super(DEFAULT_CONFIG, keyPair, marine, new JsServiceHost(), avm, conn);
|
||||
|
||||
this.ephemeralConnection = conn;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ephemeral network implementation.
|
||||
* Ephemeral network is a virtual network which runs locally and focuses on p2p interaction by removing connectivity layer out of the equation.
|
||||
*/
|
||||
export class EphemeralNetwork {
|
||||
private peers: Map<PeerIdB58, EphemeralPeer> = new Map();
|
||||
|
||||
workerLoader: WorkerLoaderFromFs;
|
||||
controlModuleLoader: WasmLoaderFromNpm;
|
||||
avmModuleLoader: WasmLoaderFromNpm;
|
||||
|
||||
constructor(public readonly config: EphemeralConfig) {
|
||||
// shared worker for all the peers
|
||||
this.workerLoader = new WorkerLoaderFromFs('../../marine/worker-script');
|
||||
this.controlModuleLoader = new WasmLoaderFromNpm('@fluencelabs/marine-js', 'marine-js.wasm');
|
||||
this.avmModuleLoader = new WasmLoaderFromNpm('@fluencelabs/avm', 'avm.wasm');
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the Ephemeral network up
|
||||
*/
|
||||
async up(): Promise<void> {
|
||||
log.trace('starting ephemeral network up...');
|
||||
|
||||
const promises = this.config.peers.map(async (x) => {
|
||||
const kp = await fromBase64Sk(x.sk);
|
||||
const marine = new MarineBackgroundRunner(this.workerLoader, this.controlModuleLoader);
|
||||
const avm = new MarineBasedAvmRunner(marine, this.avmModuleLoader);
|
||||
const peerId = kp.getPeerId();
|
||||
if (peerId !== x.peerId) {
|
||||
throw new Error(`Invalid config: peer id ${x.peerId} does not match the secret key ${x.sk}`);
|
||||
}
|
||||
|
||||
return new EphemeralPeer(kp, marine, avm);
|
||||
});
|
||||
|
||||
const peers = await Promise.all(promises);
|
||||
|
||||
for (let i = 0; i < peers.length; i++) {
|
||||
for (let j = 0; j < i; j++) {
|
||||
if (i === j) {
|
||||
continue;
|
||||
}
|
||||
|
||||
peers[i].ephemeralConnection.connectToOther(peers[j].ephemeralConnection);
|
||||
}
|
||||
}
|
||||
|
||||
const startPromises = peers.map((x) => x.start());
|
||||
await Promise.all(startPromises);
|
||||
|
||||
for (let p of peers) {
|
||||
this.peers.set(p.keyPair.getPeerId(), p);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts the ephemeral network down. Will disconnect all connected peers.
|
||||
*/
|
||||
async down(): Promise<void> {
|
||||
log.trace('shutting down ephemeral network...');
|
||||
const peers = Array.from(this.peers.entries());
|
||||
const promises = peers.map(async ([k, p]) => {
|
||||
await p.ephemeralConnection.disconnectFromAll();
|
||||
await p.stop();
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
this.peers.clear();
|
||||
log.trace('ephemeral network shut down');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a relay connection to the specified peer.
|
||||
*/
|
||||
getRelayConnection(peerId: PeerIdB58, relayPeerId: PeerIdB58): IConnection {
|
||||
const relay = this.peers.get(relayPeerId);
|
||||
if (relay === undefined) {
|
||||
throw new Error(`Peer ${relayPeerId} is not found`);
|
||||
}
|
||||
|
||||
const res = new EphemeralConnection(peerId);
|
||||
res.connectToOther(relay.ephemeralConnection);
|
||||
return res;
|
||||
}
|
||||
}
|
@ -1,802 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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 'buffer';
|
||||
|
||||
import { RelayConnection } from '../connection/index.js';
|
||||
import { FluenceConnection, IAvmRunner, IMarine } from '../interfaces/index.js';
|
||||
import { fromOpts, KeyPair } from '../keypair/index.js';
|
||||
import {
|
||||
CallServiceData,
|
||||
CallServiceResult,
|
||||
GenericCallServiceHandler,
|
||||
ResultCodes,
|
||||
} from '../interfaces/commonTypes.js';
|
||||
import type {
|
||||
PeerIdB58,
|
||||
IFluenceClient,
|
||||
KeyPairOptions,
|
||||
RelayOptions,
|
||||
ClientOptions,
|
||||
ConnectionState,
|
||||
} from '@fluencelabs/interfaces/dist/fluenceClient';
|
||||
import { Particle, ParticleExecutionStage, ParticleQueueItem } from './Particle.js';
|
||||
import { jsonify, isString, ServiceError } from './utils.js';
|
||||
import { concatMap, filter, pipe, Subject, tap } from 'rxjs';
|
||||
import { builtInServices } from './builtins/common.js';
|
||||
import { defaultSigGuard, Sig } from './builtins/Sig.js';
|
||||
import { registerSig } from './_aqua/services.js';
|
||||
import { registerSrv } from './_aqua/single-module-srv.js';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
import { JSONValue } from '@fluencelabs/avm';
|
||||
import { NodeUtils, Srv } from './builtins/SingleModuleSrv.js';
|
||||
import { registerNodeUtils } from './_aqua/node-utils.js';
|
||||
import type { MultiaddrInput } from '@multiformats/multiaddr';
|
||||
|
||||
import { logger } from '../util/logger.js';
|
||||
|
||||
const log = logger('particle');
|
||||
|
||||
const DEFAULT_TTL = 7000;
|
||||
|
||||
export type PeerConfig = ClientOptions & { relay?: RelayOptions };
|
||||
|
||||
type PeerStatus =
|
||||
| {
|
||||
isInitialized: false;
|
||||
peerId: null;
|
||||
isConnected: false;
|
||||
relayPeerId: null;
|
||||
}
|
||||
| {
|
||||
isInitialized: true;
|
||||
peerId: PeerIdB58;
|
||||
isConnected: false;
|
||||
relayPeerId: null;
|
||||
}
|
||||
| {
|
||||
isInitialized: true;
|
||||
peerId: PeerIdB58;
|
||||
isConnected: true;
|
||||
relayPeerId: PeerIdB58;
|
||||
}
|
||||
| {
|
||||
isInitialized: true;
|
||||
peerId: PeerIdB58;
|
||||
isConnected: true;
|
||||
isDirect: true;
|
||||
relayPeerId: null;
|
||||
};
|
||||
|
||||
/**
|
||||
* This class implements the Fluence protocol for javascript-based environments.
|
||||
* It provides all the necessary features to communicate with Fluence network
|
||||
*/
|
||||
export class FluencePeer implements IFluenceClient {
|
||||
connectionState: ConnectionState = 'disconnected';
|
||||
connectionStateChangeHandler: (state: ConnectionState) => void = () => {};
|
||||
|
||||
constructor(private marine: IMarine, private avmRunner: IAvmRunner) {}
|
||||
|
||||
/**
|
||||
* Internal contract to cast unknown objects to IFluenceClient.
|
||||
* If an unknown object has this property then we assume it is in fact a Peer and it implements IFluenceClient
|
||||
* Check against this variable MUST NOT be coupled with any `FluencePeer` because otherwise it might get bundled
|
||||
* brining a lot of unnecessary stuff alongside with it
|
||||
*/
|
||||
__isFluenceAwesome = true;
|
||||
|
||||
/**
|
||||
* TODO: remove this from here. Switch to `ConnectionState` instead
|
||||
* @deprecated
|
||||
*/
|
||||
getStatus(): PeerStatus {
|
||||
if (this._keyPair === undefined) {
|
||||
return {
|
||||
isInitialized: false,
|
||||
peerId: null,
|
||||
isConnected: false,
|
||||
relayPeerId: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (this.connection === null) {
|
||||
return {
|
||||
isInitialized: true,
|
||||
peerId: this._keyPair.getPeerId(),
|
||||
isConnected: false,
|
||||
relayPeerId: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (this.connection.relayPeerId === null) {
|
||||
return {
|
||||
isInitialized: true,
|
||||
peerId: this._keyPair.getPeerId(),
|
||||
isConnected: true,
|
||||
isDirect: true,
|
||||
relayPeerId: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isInitialized: true,
|
||||
peerId: this._keyPair.getPeerId(),
|
||||
isConnected: true,
|
||||
relayPeerId: this.connection.relayPeerId,
|
||||
};
|
||||
}
|
||||
|
||||
getPeerId(): string {
|
||||
return this.getStatus().peerId!;
|
||||
}
|
||||
|
||||
getRelayPeerId(): string {
|
||||
return this.getStatus().relayPeerId!;
|
||||
}
|
||||
|
||||
getPeerSecretKey(): Uint8Array {
|
||||
if (!this._keyPair) {
|
||||
throw new Error("Can't get key pair: peer is not initialized");
|
||||
}
|
||||
|
||||
return this._keyPair.toEd25519PrivateKey();
|
||||
}
|
||||
|
||||
onConnectionStateChange(handler: (state: ConnectionState) => void): ConnectionState {
|
||||
this.connectionStateChangeHandler = handler;
|
||||
|
||||
return this.connectionState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the Fluence network
|
||||
* @param relay - relay node to connect to
|
||||
* @param options - client options
|
||||
*/
|
||||
async connect(relay: RelayOptions, options?: ClientOptions): Promise<void> {
|
||||
return this.start({ relay, ...options });
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the Fluence network
|
||||
*/
|
||||
disconnect(): Promise<void> {
|
||||
return this.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the peer: starts the Aqua VM, initializes the default call service handlers
|
||||
* and (optionally) connect to the Fluence network
|
||||
* @param config - object specifying peer configuration
|
||||
*/
|
||||
async start(config: PeerConfig = {}): Promise<void> {
|
||||
this.changeConnectionState('connecting');
|
||||
const keyPair = await makeKeyPair(config.keyPair);
|
||||
await this.init(config, keyPair);
|
||||
|
||||
const conn = await configToConnection(keyPair, config.relay, config.connectionOptions?.dialTimeoutMs);
|
||||
|
||||
if (conn !== null) {
|
||||
await this._connect(conn);
|
||||
}
|
||||
this.changeConnectionState('connected');
|
||||
}
|
||||
|
||||
getServices() {
|
||||
if (this._classServices === undefined) {
|
||||
throw new Error(`Can't get services: peer is not initialized`);
|
||||
}
|
||||
return {
|
||||
...this._classServices,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers marine service within the Fluence peer from wasm file.
|
||||
* Following helper functions can be used to load wasm files:
|
||||
* * loadWasmFromFileSystem
|
||||
* * loadWasmFromNpmPackage
|
||||
* * loadWasmFromServer
|
||||
* @param wasm - buffer with the wasm file for service
|
||||
* @param serviceId - the service id by which the service can be accessed in aqua
|
||||
*/
|
||||
async registerMarineService(wasm: SharedArrayBuffer | Buffer, serviceId: string): Promise<void> {
|
||||
if (!this.marine) {
|
||||
throw new Error("Can't register marine service: peer is not initialized");
|
||||
}
|
||||
if (this._containsService(serviceId)) {
|
||||
throw new Error(`Service with '${serviceId}' id already exists`);
|
||||
}
|
||||
|
||||
await this.marine.createService(wasm, serviceId);
|
||||
this._marineServices.add(serviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified marine service from the Fluence peer
|
||||
* @param serviceId - the service id to remove
|
||||
*/
|
||||
removeMarineService(serviceId: string): void {
|
||||
this._marineServices.delete(serviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-initializes the peer: stops all the underlying workflows, stops the Aqua VM
|
||||
* and disconnects from the Fluence network
|
||||
*/
|
||||
async stop() {
|
||||
this.changeConnectionState('disconnecting');
|
||||
this._keyPair = undefined; // This will set peer to non-initialized state and stop particle processing
|
||||
this._stopParticleProcessing();
|
||||
await this._disconnect();
|
||||
await this.marine.stop();
|
||||
await this.avmRunner.stop();
|
||||
this._classServices = undefined;
|
||||
|
||||
this._particleSpecificHandlers.clear();
|
||||
this._commonHandlers.clear();
|
||||
this._marineServices.clear();
|
||||
this.changeConnectionState('disconnected');
|
||||
}
|
||||
|
||||
// internal api
|
||||
|
||||
/**
|
||||
* @private Is not intended to be used manually. Subject to change
|
||||
*/
|
||||
get internals() {
|
||||
return {
|
||||
getConnectionState: () => this.connectionState,
|
||||
|
||||
getRelayPeerId: () => this.getStatus().relayPeerId,
|
||||
|
||||
parseAst: async (air: string): Promise<{ success: boolean; data: any }> => {
|
||||
const status = this.getStatus();
|
||||
|
||||
if (!status.isInitialized) {
|
||||
new Error("Can't use avm: peer is not initialized");
|
||||
}
|
||||
|
||||
const res = await this.marine.callService('avm', 'ast', [air], undefined);
|
||||
if (!isString(res)) {
|
||||
throw new Error(`Call to avm:ast expected to return string. Actual return: ${res}`);
|
||||
}
|
||||
|
||||
try {
|
||||
if (res.startsWith('error')) {
|
||||
return {
|
||||
success: false,
|
||||
data: res,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: true,
|
||||
data: JSON.parse(res),
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error('Failed to call avm. Result: ' + res + '. Error: ' + err);
|
||||
}
|
||||
},
|
||||
createNewParticle: (script: string, ttl: number = this._defaultTTL) => {
|
||||
const status = this.getStatus();
|
||||
|
||||
if (!status.isInitialized) {
|
||||
return new Error("Can't create new particle: peer is not initialized");
|
||||
}
|
||||
|
||||
return Particle.createNew(script, ttl, status.peerId);
|
||||
},
|
||||
/**
|
||||
* Initiates a new particle execution starting from local peer
|
||||
* @param particle - particle to start execution of
|
||||
*/
|
||||
initiateParticle: (particle: Particle, onStageChange: (stage: ParticleExecutionStage) => void): void => {
|
||||
const status = this.getStatus();
|
||||
if (!status.isInitialized) {
|
||||
throw new Error('Cannot initiate new particle: peer is not initialized');
|
||||
}
|
||||
|
||||
if (this._printParticleId) {
|
||||
console.log('Particle id: ', particle.id);
|
||||
}
|
||||
|
||||
if (particle.initPeerId === undefined) {
|
||||
particle.initPeerId = status.peerId;
|
||||
}
|
||||
|
||||
if (particle.ttl === undefined) {
|
||||
particle.ttl = this._defaultTTL;
|
||||
}
|
||||
|
||||
this._incomingParticles.next({
|
||||
particle: particle,
|
||||
onStageChange: onStageChange,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Register Call Service handler functions
|
||||
*/
|
||||
regHandler: {
|
||||
/**
|
||||
* Register handler for all particles
|
||||
*/
|
||||
common: (
|
||||
// force new line
|
||||
serviceId: string,
|
||||
fnName: string,
|
||||
handler: GenericCallServiceHandler,
|
||||
) => {
|
||||
this._commonHandlers.set(serviceFnKey(serviceId, fnName), handler);
|
||||
},
|
||||
/**
|
||||
* Register handler which will be called only for particle with the specific id
|
||||
*/
|
||||
forParticle: (
|
||||
particleId: string,
|
||||
serviceId: string,
|
||||
fnName: string,
|
||||
handler: GenericCallServiceHandler,
|
||||
) => {
|
||||
let psh = this._particleSpecificHandlers.get(particleId);
|
||||
if (psh === undefined) {
|
||||
psh = new Map<string, GenericCallServiceHandler>();
|
||||
this._particleSpecificHandlers.set(particleId, psh);
|
||||
}
|
||||
|
||||
psh.set(serviceFnKey(serviceId, fnName), handler);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private Subject to change. Do not use this method directly
|
||||
*/
|
||||
async init(config: Omit<PeerConfig, 'keyPair'>, keyPair: KeyPair) {
|
||||
this._keyPair = keyPair;
|
||||
|
||||
const peerId = this._keyPair.getPeerId();
|
||||
|
||||
if (config?.debug?.printParticleId) {
|
||||
this._printParticleId = true;
|
||||
}
|
||||
|
||||
this._defaultTTL = config?.defaultTtlMs ?? DEFAULT_TTL;
|
||||
|
||||
await this.marine.start();
|
||||
await this.avmRunner.start();
|
||||
|
||||
registerDefaultServices(this);
|
||||
|
||||
this._classServices = {
|
||||
sig: new Sig(this._keyPair),
|
||||
srv: new Srv(this),
|
||||
};
|
||||
this._classServices.sig.securityGuard = defaultSigGuard(peerId);
|
||||
registerSig(this, 'sig', this._classServices.sig);
|
||||
registerSig(this, peerId, this._classServices.sig);
|
||||
|
||||
registerSrv(this, 'single_module_srv', this._classServices.srv);
|
||||
registerNodeUtils(this, 'node_utils', new NodeUtils(this));
|
||||
|
||||
this._startParticleProcessing();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private Subject to change. Do not use this method directly
|
||||
*/
|
||||
async _connect(connection: FluenceConnection): Promise<void> {
|
||||
if (this.connection) {
|
||||
await this.connection.disconnect();
|
||||
}
|
||||
|
||||
this.connection = connection;
|
||||
await this.connection.connect(this._onIncomingParticle.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private Subject to change. Do not use this method directly
|
||||
*/
|
||||
async _disconnect(): Promise<void> {
|
||||
await this.connection?.disconnect();
|
||||
}
|
||||
|
||||
// private
|
||||
|
||||
private changeConnectionState(state: ConnectionState) {
|
||||
this.connectionState = state;
|
||||
this.connectionStateChangeHandler(state);
|
||||
}
|
||||
|
||||
// Queues for incoming and outgoing particles
|
||||
|
||||
private _incomingParticles = new Subject<ParticleQueueItem>();
|
||||
private _outgoingParticles = new Subject<ParticleQueueItem & { nextPeerIds: PeerIdB58[] }>();
|
||||
|
||||
// Call service handler
|
||||
|
||||
private _marineServices = new Set<string>();
|
||||
private _particleSpecificHandlers = new Map<string, Map<string, GenericCallServiceHandler>>();
|
||||
private _commonHandlers = new Map<string, GenericCallServiceHandler>();
|
||||
|
||||
private _classServices?: {
|
||||
sig: Sig;
|
||||
srv: Srv;
|
||||
};
|
||||
|
||||
private _containsService(serviceId: string): boolean {
|
||||
return this._marineServices.has(serviceId) || this._commonHandlers.has(serviceId);
|
||||
}
|
||||
|
||||
// Internal peer state
|
||||
|
||||
private connection: FluenceConnection | null = null;
|
||||
private _printParticleId = false;
|
||||
private _defaultTTL: number = DEFAULT_TTL;
|
||||
private _keyPair: KeyPair | undefined;
|
||||
private _timeouts: Array<NodeJS.Timeout> = [];
|
||||
private _particleQueues = new Map<string, Subject<ParticleQueueItem>>();
|
||||
|
||||
private _onIncomingParticle(p: string) {
|
||||
const particle = Particle.fromString(p);
|
||||
this._incomingParticles.next({ particle, onStageChange: () => {} });
|
||||
}
|
||||
|
||||
private _startParticleProcessing() {
|
||||
this._incomingParticles
|
||||
.pipe(
|
||||
tap((x) => {
|
||||
log.debug('id %s. received:', x.particle.id);
|
||||
log.trace('id %s. data: %j', x.particle.id, {
|
||||
initPeerId: x.particle.initPeerId,
|
||||
timestamp: x.particle.timestamp,
|
||||
tttl: x.particle.ttl,
|
||||
signature: x.particle.signature,
|
||||
});
|
||||
|
||||
log.trace('id %s. script: %s', x.particle.id, x.particle.script);
|
||||
log.trace('id %s. call results: %j', x.particle.id, x.particle.callResults);
|
||||
}),
|
||||
filterExpiredParticles(this._expireParticle.bind(this)),
|
||||
)
|
||||
.subscribe((item) => {
|
||||
const p = item.particle;
|
||||
let particlesQueue = this._particleQueues.get(p.id);
|
||||
|
||||
if (!particlesQueue) {
|
||||
particlesQueue = this._createParticlesProcessingQueue();
|
||||
this._particleQueues.set(p.id, particlesQueue);
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
this._expireParticle(item);
|
||||
}, p.actualTtl());
|
||||
|
||||
this._timeouts.push(timeout);
|
||||
}
|
||||
|
||||
particlesQueue.next(item);
|
||||
});
|
||||
|
||||
this._outgoingParticles.subscribe((item) => {
|
||||
// Do not send particle after the peer has been stopped
|
||||
if (!this.getStatus().isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.connection) {
|
||||
log.error('id %s. cannot send, peer is not connected', item.particle.id);
|
||||
item.onStageChange({ stage: 'sendingError' });
|
||||
return;
|
||||
}
|
||||
log.debug('id %s. sending particle into network', item.particle.id);
|
||||
this.connection
|
||||
?.sendParticle(item.nextPeerIds, item.particle.toString())
|
||||
.then(() => {
|
||||
item.onStageChange({ stage: 'sent' });
|
||||
})
|
||||
.catch((e: any) => {
|
||||
log.error('id %s. send failed %j', item.particle.id, e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _expireParticle(item: ParticleQueueItem) {
|
||||
const particleId = item.particle.id;
|
||||
log.debug(
|
||||
'id %s. particle has expired after %d. Deleting particle-related queues and handlers',
|
||||
item.particle.id,
|
||||
item.particle.ttl,
|
||||
);
|
||||
|
||||
this._particleQueues.delete(particleId);
|
||||
this._particleSpecificHandlers.delete(particleId);
|
||||
|
||||
item.onStageChange({ stage: 'expired' });
|
||||
}
|
||||
|
||||
private _createParticlesProcessingQueue() {
|
||||
const particlesQueue = new Subject<ParticleQueueItem>();
|
||||
let prevData: Uint8Array = Buffer.from([]);
|
||||
|
||||
particlesQueue
|
||||
.pipe(
|
||||
filterExpiredParticles(this._expireParticle.bind(this)),
|
||||
|
||||
concatMap(async (item) => {
|
||||
const status = this.getStatus();
|
||||
if (!status.isInitialized || this.marine === undefined) {
|
||||
// If `.stop()` was called return null to stop particle processing immediately
|
||||
return null;
|
||||
}
|
||||
|
||||
// IMPORTANT!
|
||||
// AVM runner execution and prevData <-> newData swapping
|
||||
// MUST happen sequentially (in a critical section).
|
||||
// Otherwise the race might occur corrupting the prevData
|
||||
|
||||
log.debug('id %s. sending particle to interpreter', item.particle.id);
|
||||
log.trace('id %s. prevData: %a', item.particle.id, prevData);
|
||||
const avmCallResult = await this.avmRunner.run(
|
||||
{
|
||||
initPeerId: item.particle.initPeerId,
|
||||
currentPeerId: status.peerId,
|
||||
timestamp: item.particle.timestamp,
|
||||
ttl: item.particle.ttl,
|
||||
},
|
||||
item.particle.script,
|
||||
prevData,
|
||||
item.particle.data,
|
||||
item.particle.callResults,
|
||||
);
|
||||
|
||||
if (!(avmCallResult instanceof Error) && avmCallResult.retCode === 0) {
|
||||
const newData = Buffer.from(avmCallResult.data);
|
||||
prevData = newData;
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
result: avmCallResult,
|
||||
};
|
||||
}),
|
||||
)
|
||||
.subscribe((item) => {
|
||||
// If `.stop()` was called then item will be null and we need to stop particle processing immediately
|
||||
if (item === null || !this.getStatus().isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not proceed further if the particle is expired
|
||||
if (item.particle.hasExpired()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not continue if there was an error in particle interpretation
|
||||
if (item.result instanceof Error) {
|
||||
log.error('id %s. interpreter failed: %s', item.particle.id, item.result.message);
|
||||
item.onStageChange({ stage: 'interpreterError', errorMessage: item.result.message });
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.result.retCode !== 0) {
|
||||
log.error(
|
||||
'id %s. interpreter failed: retCode: %d, message: %s',
|
||||
item.particle.id,
|
||||
item.result.retCode,
|
||||
item.result.errorMessage,
|
||||
);
|
||||
log.trace('id %s. avm data: %a', item.particle.id, item.result.data);
|
||||
item.onStageChange({ stage: 'interpreterError', errorMessage: item.result.errorMessage });
|
||||
return;
|
||||
}
|
||||
|
||||
log.trace(
|
||||
'id %s. interpreter result: retCode: %d, avm data: %a',
|
||||
item.particle.id,
|
||||
item.result.retCode,
|
||||
item.result.data,
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
item.onStageChange({ stage: 'interpreted' });
|
||||
}, 0);
|
||||
|
||||
// send particle further if requested
|
||||
if (item.result.nextPeerPks.length > 0) {
|
||||
const newParticle = item.particle.clone();
|
||||
const newData = Buffer.from(item.result.data);
|
||||
newParticle.data = newData;
|
||||
this._outgoingParticles.next({
|
||||
...item,
|
||||
particle: newParticle,
|
||||
nextPeerIds: item.result.nextPeerPks,
|
||||
});
|
||||
}
|
||||
|
||||
// execute call requests if needed
|
||||
// and put particle with the results back to queue
|
||||
if (item.result.callRequests.length > 0) {
|
||||
for (const [key, cr] of item.result.callRequests) {
|
||||
const req = {
|
||||
fnName: cr.functionName,
|
||||
args: cr.arguments,
|
||||
serviceId: cr.serviceId,
|
||||
tetraplets: cr.tetraplets,
|
||||
particleContext: item.particle.getParticleContext(),
|
||||
};
|
||||
|
||||
if (item.particle.hasExpired()) {
|
||||
// just in case do not call any services if the particle is already expired
|
||||
return;
|
||||
}
|
||||
this._execSingleCallRequest(req)
|
||||
.catch((err): CallServiceResult => {
|
||||
if (err instanceof ServiceError) {
|
||||
return {
|
||||
retCode: ResultCodes.error,
|
||||
result: err.message,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
retCode: ResultCodes.error,
|
||||
result: `Handler failed. fnName="${req.fnName}" serviceId="${
|
||||
req.serviceId
|
||||
}" error: ${err.toString()}`,
|
||||
};
|
||||
})
|
||||
.then((res) => {
|
||||
const serviceResult = {
|
||||
result: jsonify(res.result),
|
||||
retCode: res.retCode,
|
||||
};
|
||||
|
||||
const newParticle = item.particle.clone();
|
||||
newParticle.callResults = [[key, serviceResult]];
|
||||
newParticle.data = Buffer.from([]);
|
||||
|
||||
particlesQueue.next({ ...item, particle: newParticle });
|
||||
});
|
||||
}
|
||||
} else {
|
||||
item.onStageChange({ stage: 'localWorkDone' });
|
||||
}
|
||||
});
|
||||
|
||||
return particlesQueue;
|
||||
}
|
||||
|
||||
private async _execSingleCallRequest(req: CallServiceData): Promise<CallServiceResult> {
|
||||
const particleId = req.particleContext.particleId;
|
||||
log.trace('id %s. executing call service handler %j', particleId, req);
|
||||
|
||||
if (this.marine && this._marineServices.has(req.serviceId)) {
|
||||
const result = await this.marine.callService(req.serviceId, req.fnName, req.args, undefined);
|
||||
|
||||
return {
|
||||
retCode: ResultCodes.success,
|
||||
result: result as JSONValue,
|
||||
};
|
||||
}
|
||||
|
||||
const key = serviceFnKey(req.serviceId, req.fnName);
|
||||
const psh = this._particleSpecificHandlers.get(particleId);
|
||||
let handler: GenericCallServiceHandler | undefined;
|
||||
|
||||
// we should prioritize handler for this particle if there is one
|
||||
// if particle-specific handlers exist for this particle try getting handler there
|
||||
if (psh !== undefined) {
|
||||
handler = psh.get(key);
|
||||
}
|
||||
|
||||
// then try to find a common handler for all particles with this service-fn key
|
||||
// if there is no particle-specific handler, get one from common map
|
||||
if (handler === undefined) {
|
||||
handler = this._commonHandlers.get(key);
|
||||
}
|
||||
|
||||
// if no handler is found return useful error message to AVM
|
||||
if (handler === undefined) {
|
||||
return {
|
||||
retCode: ResultCodes.error,
|
||||
result: `No handler has been registered for serviceId='${req.serviceId}' fnName='${
|
||||
req.fnName
|
||||
}' args='${jsonify(req.args)}'`,
|
||||
};
|
||||
}
|
||||
|
||||
// if we found a handler, execute it
|
||||
const res = await handler(req);
|
||||
|
||||
if (res.result === undefined) {
|
||||
res.result = null;
|
||||
}
|
||||
|
||||
log.trace('id %s. executed call service handler, req: %j, res: %j ', particleId, req, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
private _stopParticleProcessing() {
|
||||
// do not hang if the peer has been stopped while some of the timeouts are still being executed
|
||||
this._timeouts.forEach((timeout) => {
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
this._particleQueues.clear();
|
||||
}
|
||||
}
|
||||
|
||||
async function configToConnection(
|
||||
keyPair: KeyPair,
|
||||
connection?: RelayOptions,
|
||||
dialTimeoutMs?: number,
|
||||
): Promise<FluenceConnection | null> {
|
||||
if (!connection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (connection instanceof FluenceConnection) {
|
||||
return connection;
|
||||
}
|
||||
|
||||
let connectToMultiAddr: MultiaddrInput;
|
||||
// figuring out what was specified as input
|
||||
const tmp = connection as any;
|
||||
if (tmp.multiaddr !== undefined) {
|
||||
// specified as FluenceNode (object with multiaddr and peerId props)
|
||||
connectToMultiAddr = tmp.multiaddr;
|
||||
} else {
|
||||
// specified as MultiaddrInput
|
||||
connectToMultiAddr = tmp;
|
||||
}
|
||||
|
||||
const res = await RelayConnection.createConnection({
|
||||
peerId: keyPair.getLibp2pPeerId(),
|
||||
relayAddress: connectToMultiAddr,
|
||||
dialTimeoutMs: dialTimeoutMs,
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
function serviceFnKey(serviceId: string, fnName: string) {
|
||||
return `${serviceId}/${fnName}`;
|
||||
}
|
||||
|
||||
function registerDefaultServices(peer: FluencePeer) {
|
||||
Object.entries(builtInServices).forEach(([serviceId, service]) => {
|
||||
Object.entries(service).forEach(([fnName, fn]) => {
|
||||
peer.internals.regHandler.common(serviceId, fnName, fn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function filterExpiredParticles(onParticleExpiration: (item: ParticleQueueItem) => void) {
|
||||
return pipe(
|
||||
tap((item: ParticleQueueItem) => {
|
||||
if (item.particle.hasExpired()) {
|
||||
onParticleExpiration(item);
|
||||
}
|
||||
}),
|
||||
filter((x: ParticleQueueItem) => !x.particle.hasExpired()),
|
||||
);
|
||||
}
|
||||
|
||||
async function makeKeyPair(opts?: KeyPairOptions) {
|
||||
opts = opts || { type: 'Ed25519', source: 'random' };
|
||||
return fromOpts(opts);
|
||||
}
|
@ -1,114 +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 { fromUint8Array, toUint8Array } from 'js-base64';
|
||||
import { CallResultsArray, LogLevel } from '@fluencelabs/avm';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { ParticleContext } from '../interfaces/commonTypes.js';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
export class Particle {
|
||||
// TODO: make it not optional (should be added to the constructor)
|
||||
signature?: string;
|
||||
callResults: CallResultsArray = [];
|
||||
|
||||
constructor(
|
||||
public id: string,
|
||||
public timestamp: number,
|
||||
public script: string,
|
||||
public data: Uint8Array,
|
||||
public ttl: number,
|
||||
public initPeerId: string,
|
||||
) {}
|
||||
|
||||
static createNew(script: string, ttl: number, initPeerId: string): Particle {
|
||||
return new Particle(genUUID(), Date.now(), script, Buffer.from([]), ttl, initPeerId);
|
||||
}
|
||||
|
||||
static fromString(str: string): Particle {
|
||||
const json = JSON.parse(str);
|
||||
const res = new Particle(
|
||||
json.id,
|
||||
json.timestamp,
|
||||
json.script,
|
||||
toUint8Array(json.data),
|
||||
json.ttl,
|
||||
json.init_peer_id,
|
||||
);
|
||||
|
||||
res.signature = json.signature;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
getParticleContext(): ParticleContext {
|
||||
return {
|
||||
particleId: this.id,
|
||||
initPeerId: this.initPeerId,
|
||||
timestamp: this.timestamp,
|
||||
ttl: this.ttl,
|
||||
signature: this.signature,
|
||||
};
|
||||
}
|
||||
|
||||
actualTtl(): number {
|
||||
return this.timestamp + this.ttl - Date.now();
|
||||
}
|
||||
|
||||
hasExpired(): boolean {
|
||||
return this.actualTtl() <= 0;
|
||||
}
|
||||
|
||||
clone(): Particle {
|
||||
const res = new Particle(this.id, this.timestamp, this.script, this.data, this.ttl, this.initPeerId);
|
||||
|
||||
res.signature = this.signature;
|
||||
res.callResults = this.callResults;
|
||||
return res;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return JSON.stringify({
|
||||
action: 'Particle',
|
||||
id: this.id,
|
||||
init_peer_id: this.initPeerId,
|
||||
timestamp: this.timestamp,
|
||||
ttl: this.ttl,
|
||||
script: this.script,
|
||||
// TODO: copy signature from a particle after signatures will be implemented on nodes
|
||||
signature: [],
|
||||
data: this.data && fromUint8Array(this.data),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export type ParticleExecutionStage =
|
||||
| { stage: 'received' }
|
||||
| { stage: 'interpreted' }
|
||||
| { stage: 'interpreterError'; errorMessage: string }
|
||||
| { stage: 'localWorkDone' }
|
||||
| { stage: 'sent' }
|
||||
| { stage: 'sendingError' }
|
||||
| { stage: 'expired' };
|
||||
|
||||
export interface ParticleQueueItem {
|
||||
particle: Particle;
|
||||
onStageChange: (state: ParticleExecutionStage) => void;
|
||||
}
|
||||
|
||||
function genUUID() {
|
||||
return uuidv4();
|
||||
}
|
@ -1,417 +0,0 @@
|
||||
import { it, describe, expect } from 'vitest';
|
||||
|
||||
import { nodes } from '../connection.js';
|
||||
import { checkConnection, doNothing, handleTimeout } from '../../utils.js';
|
||||
import { registerHandlersHelper, mkTestPeer, withPeer, withConnectedPeer } from '../util.js';
|
||||
import { FluencePeer } from '../../FluencePeer.js';
|
||||
import { isFluencePeer } from '@fluencelabs/interfaces';
|
||||
|
||||
describe('Typescript usage suite', () => {
|
||||
it('should perform test for FluencePeer class correctly', () => {
|
||||
// arrange
|
||||
const peer = mkTestPeer();
|
||||
const number = 1;
|
||||
const object = { str: 'Hello!' };
|
||||
const undefinedVal = undefined;
|
||||
|
||||
// act
|
||||
const isPeerPeer = isFluencePeer(peer);
|
||||
const isNumberPeer = isFluencePeer(number);
|
||||
const isObjectPeer = isFluencePeer(object);
|
||||
const isUndefinedPeer = isFluencePeer(undefinedVal);
|
||||
|
||||
// act
|
||||
expect(isPeerPeer).toBe(true);
|
||||
expect(isNumberPeer).toBe(false);
|
||||
expect(isObjectPeer).toBe(false);
|
||||
expect(isUndefinedPeer).toBe(false);
|
||||
});
|
||||
|
||||
describe('Should expose correct peer status', () => {
|
||||
it('Should expose correct status for uninitialized peer', () => {
|
||||
const peer = mkTestPeer();
|
||||
const status = peer.getStatus();
|
||||
|
||||
expect(status.isConnected).toBe(false);
|
||||
expect(status.isInitialized).toBe(false);
|
||||
expect(status.peerId).toBe(null);
|
||||
expect(status.relayPeerId).toBe(null);
|
||||
});
|
||||
|
||||
it('Should expose correct status for initialized but not connected peer', async () => {
|
||||
await withPeer(async (peer) => {
|
||||
// arrange
|
||||
|
||||
// act
|
||||
const status = peer.getStatus();
|
||||
|
||||
// assert
|
||||
expect(status.isConnected).toBe(false);
|
||||
expect(status.isInitialized).toBe(true);
|
||||
expect(status.peerId).not.toBe(null);
|
||||
expect(status.relayPeerId).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should expose correct status for connected peer', async () => {
|
||||
await withConnectedPeer(async (peer) => {
|
||||
// arrange
|
||||
|
||||
// act
|
||||
const status = peer.getStatus();
|
||||
|
||||
// assert
|
||||
expect(status.isConnected).toBe(true);
|
||||
expect(status.isInitialized).toBe(true);
|
||||
expect(status.peerId).not.toBe(null);
|
||||
expect(status.relayPeerId).not.toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should make a call through network', async () => {
|
||||
await withConnectedPeer(async (peer) => {
|
||||
// arrange
|
||||
|
||||
const result = await new Promise<string[]>((resolve, reject) => {
|
||||
const script = `
|
||||
(xor
|
||||
(seq
|
||||
(call %init_peer_id% ("load" "relay") [] init_relay)
|
||||
(seq
|
||||
(call init_relay ("op" "identity") ["hello world!"] result)
|
||||
(call %init_peer_id% ("callback" "callback") [result])
|
||||
)
|
||||
)
|
||||
(seq
|
||||
(call init_relay ("op" "identity") [])
|
||||
(call %init_peer_id% ("callback" "error") [%last_error%])
|
||||
)
|
||||
)`;
|
||||
const particle = peer.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
return reject(particle.message);
|
||||
}
|
||||
|
||||
registerHandlersHelper(peer, particle, {
|
||||
load: {
|
||||
relay: () => {
|
||||
return peer.getStatus().relayPeerId;
|
||||
},
|
||||
},
|
||||
callback: {
|
||||
callback: (args: any) => {
|
||||
const [val] = args;
|
||||
resolve(val);
|
||||
},
|
||||
error: (args: any) => {
|
||||
const [error] = args;
|
||||
reject(error);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
|
||||
expect(result).toBe('hello world!');
|
||||
});
|
||||
});
|
||||
|
||||
it('check connection should work', async function () {
|
||||
await withConnectedPeer(async (peer) => {
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
expect(isConnected).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('check connection should work with ttl', async function () {
|
||||
await withConnectedPeer(async (peer) => {
|
||||
const isConnected = await checkConnection(peer, 10000);
|
||||
|
||||
expect(isConnected).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('two clients should work inside the same time browser', async () => {
|
||||
await withConnectedPeer(async (peer1) => {
|
||||
await withConnectedPeer(async (peer2) => {
|
||||
const res = new Promise((resolve) => {
|
||||
peer2.internals.regHandler.common('test', 'test', (req) => {
|
||||
resolve(req.args[0]);
|
||||
return {
|
||||
result: {},
|
||||
retCode: 0,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const script = `
|
||||
(seq
|
||||
(call "${peer1.getStatus().relayPeerId}" ("op" "identity") [])
|
||||
(call "${peer2.getStatus().peerId}" ("test" "test") ["test"])
|
||||
)
|
||||
`;
|
||||
const particle = peer1.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
throw particle;
|
||||
}
|
||||
|
||||
peer1.internals.initiateParticle(particle, doNothing);
|
||||
|
||||
expect(await res).toEqual('test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should make connection to network', () => {
|
||||
it('address as string', async () => {
|
||||
await withConnectedPeer(async (peer) => {
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
expect(isConnected).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('address as multiaddr', async () => {
|
||||
await withConnectedPeer(async (peer) => {
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
expect(isConnected).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('address as node', async () => {
|
||||
await withConnectedPeer(async (peer) => {
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
expect(isConnected).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('With connection options: dialTimeout', async () => {
|
||||
await withPeer(
|
||||
async (peer) => {
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
expect(isConnected).toBeTruthy();
|
||||
},
|
||||
{ relay: nodes[0], connectionOptions: { dialTimeoutMs: 100000 } },
|
||||
);
|
||||
});
|
||||
|
||||
it('With connection options: skipCheckConnection', async () => {
|
||||
await withPeer(
|
||||
async (peer) => {
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
expect(isConnected).toBeTruthy();
|
||||
},
|
||||
{ relay: nodes[0], connectionOptions: { skipCheckConnection: true } },
|
||||
);
|
||||
});
|
||||
|
||||
it('With connection options: defaultTTL', async () => {
|
||||
await withPeer(
|
||||
async (peer) => {
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
expect(isConnected).toBeFalsy();
|
||||
},
|
||||
{ relay: nodes[0], defaultTtlMs: 1 },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should successfully call identity on local peer', async function () {
|
||||
await withPeer(async (peer) => {
|
||||
const res = await new Promise<string>((resolve, reject) => {
|
||||
const script = `
|
||||
(seq
|
||||
(call %init_peer_id% ("op" "identity") ["test"] res)
|
||||
(call %init_peer_id% ("callback" "callback") [res])
|
||||
)
|
||||
`;
|
||||
const particle = peer.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
return reject(particle.message);
|
||||
}
|
||||
|
||||
registerHandlersHelper(peer, particle, {
|
||||
callback: {
|
||||
callback: async (args: any) => {
|
||||
const [res] = args;
|
||||
resolve(res);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
|
||||
expect(res).toBe('test');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should throw correct message when calling non existing local service', async function () {
|
||||
await withConnectedPeer(async (peer) => {
|
||||
const res = callIncorrectService(peer);
|
||||
|
||||
await expect(res).rejects.toMatchObject({
|
||||
message: expect.stringContaining(
|
||||
`No handler has been registered for serviceId='incorrect' fnName='incorrect' args='[]'\"'`,
|
||||
),
|
||||
// instruction: 'call %init_peer_id% ("incorrect" "incorrect") [] res',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not crash if undefined is passed as a variable', async () => {
|
||||
await withPeer(async (peer) => {
|
||||
const res = await new Promise<any>((resolve, reject) => {
|
||||
const script = `
|
||||
(seq
|
||||
(call %init_peer_id% ("load" "arg") [] arg)
|
||||
(seq
|
||||
(call %init_peer_id% ("op" "identity") [arg] res)
|
||||
(call %init_peer_id% ("callback" "callback") [res])
|
||||
)
|
||||
)`;
|
||||
const particle = peer.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
return reject(particle.message);
|
||||
}
|
||||
|
||||
registerHandlersHelper(peer, particle, {
|
||||
load: {
|
||||
arg: () => undefined,
|
||||
},
|
||||
callback: {
|
||||
callback: (args: any) => {
|
||||
const [val] = args;
|
||||
resolve(val);
|
||||
},
|
||||
error: (args: any) => {
|
||||
const [error] = args;
|
||||
reject(error);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
|
||||
expect(res).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not crash if an error ocurred in user-defined handler', async () => {
|
||||
await withPeer(async (peer) => {
|
||||
const promise = new Promise<any>((_resolve, reject) => {
|
||||
const script = `
|
||||
(xor
|
||||
(call %init_peer_id% ("load" "arg") [] arg)
|
||||
(call %init_peer_id% ("callback" "error") [%last_error%])
|
||||
)`;
|
||||
const particle = peer.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
return reject(particle.message);
|
||||
}
|
||||
|
||||
registerHandlersHelper(peer, particle, {
|
||||
load: {
|
||||
arg: () => {
|
||||
throw new Error('my super custom error message');
|
||||
},
|
||||
},
|
||||
callback: {
|
||||
error: (args: any) => {
|
||||
const [error] = args;
|
||||
reject(error);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
|
||||
await expect(promise).rejects.toMatchObject({
|
||||
message: expect.stringContaining('my super custom error message'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Should return error if particle is created on a stopped peer', async () => {
|
||||
const peer = mkTestPeer();
|
||||
const particle = peer.internals.createNewParticle(`(null)`);
|
||||
|
||||
expect(particle instanceof Error).toBe(true);
|
||||
});
|
||||
|
||||
it.skip('Should throw correct error when the client tries to send a particle not to the relay', async () => {
|
||||
await withConnectedPeer(async (peer) => {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
const script = `
|
||||
(xor
|
||||
(call "incorrect_peer_id" ("any" "service") [])
|
||||
(call %init_peer_id% ("callback" "error") [%last_error%])
|
||||
)`;
|
||||
const particle = peer.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
return reject(particle.message);
|
||||
}
|
||||
|
||||
registerHandlersHelper(peer, particle, {
|
||||
callback: {
|
||||
error: (args: any) => {
|
||||
const [error] = args;
|
||||
reject(error);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, doNothing);
|
||||
});
|
||||
|
||||
await expect(promise).rejects.toMatch(
|
||||
'Particle is expected to be sent to only the single peer (relay which client is connected to)',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function callIncorrectService(peer: FluencePeer): Promise<string[]> {
|
||||
return new Promise<any[]>((resolve, reject) => {
|
||||
const script = `
|
||||
(xor
|
||||
(call %init_peer_id% ("incorrect" "incorrect") [] res)
|
||||
(call %init_peer_id% ("callback" "error") [%last_error%])
|
||||
)`;
|
||||
const particle = peer.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
return reject(particle.message);
|
||||
}
|
||||
|
||||
registerHandlersHelper(peer, particle, {
|
||||
callback: {
|
||||
callback: (args: any) => {
|
||||
resolve(args);
|
||||
},
|
||||
error: (args: any) => {
|
||||
const [error] = args;
|
||||
reject(error);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
import { it, describe, expect } from 'vitest';
|
||||
|
||||
import { handleTimeout } from '../../utils.js';
|
||||
import { nodes } from '../connection.js';
|
||||
import { mkTestPeer, registerHandlersHelper } from '../util.js';
|
||||
|
||||
describe('Smoke test', () => {
|
||||
it('Simple call', async () => {
|
||||
// arrange
|
||||
const peer = mkTestPeer();
|
||||
await peer.start({
|
||||
relay: nodes[0],
|
||||
});
|
||||
|
||||
const result = await new Promise<string[]>((resolve, reject) => {
|
||||
const script = `
|
||||
(xor
|
||||
(seq
|
||||
(call %init_peer_id% ("load" "relay") [] init_relay)
|
||||
(seq
|
||||
(call init_relay ("op" "identity") ["hello world!"] result)
|
||||
(call %init_peer_id% ("callback" "callback") [result])
|
||||
)
|
||||
)
|
||||
(seq
|
||||
(call init_relay ("op" "identity") [])
|
||||
(call %init_peer_id% ("callback" "error") [%last_error%])
|
||||
)
|
||||
)`;
|
||||
const particle = peer.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
return reject(particle.message);
|
||||
}
|
||||
|
||||
registerHandlersHelper(peer, particle, {
|
||||
load: {
|
||||
relay: () => {
|
||||
return peer.getStatus().relayPeerId;
|
||||
},
|
||||
},
|
||||
callback: {
|
||||
callback: (args: any) => {
|
||||
const [val] = args;
|
||||
resolve(val);
|
||||
},
|
||||
error: (args: any) => {
|
||||
const [error] = args;
|
||||
reject(error);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
|
||||
await peer.stop();
|
||||
|
||||
expect(result).toBe('hello world!');
|
||||
});
|
||||
});
|
@ -1,35 +0,0 @@
|
||||
import { it, describe, expect, beforeAll, afterAll } from 'vitest';
|
||||
|
||||
import { mkTestPeer } from '../util.js';
|
||||
|
||||
const peer = mkTestPeer();
|
||||
|
||||
describe('Parse ast tests', () => {
|
||||
beforeAll(async () => {
|
||||
await peer.start();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await peer.stop();
|
||||
});
|
||||
|
||||
it('Correct ast should be parsed correctly', async function () {
|
||||
const air = `(null)`;
|
||||
const res = await peer.internals.parseAst(air);
|
||||
|
||||
expect(res).toStrictEqual({
|
||||
success: true,
|
||||
data: { Null: null },
|
||||
});
|
||||
});
|
||||
|
||||
it('Incorrect ast should result in corresponding error', async function () {
|
||||
const air = `(null`;
|
||||
const res = await peer.internals.parseAst(air);
|
||||
|
||||
expect(res).toStrictEqual({
|
||||
success: false,
|
||||
data: expect.stringContaining('error'),
|
||||
});
|
||||
});
|
||||
});
|
@ -1,84 +0,0 @@
|
||||
import { KeyPair } from '@fluencelabs/keypair';
|
||||
import { EphemeralNetwork, defaultConfig } from '../../ephemeral';
|
||||
import { ResultCodes } from '../../commonTypes';
|
||||
import { FluencePeer } from '../../FluencePeer';
|
||||
import { mkTestPeer } from '../util';
|
||||
|
||||
let en: EphemeralNetwork;
|
||||
let peer: FluencePeer;
|
||||
|
||||
// TODO: jest tests hang when running this test. Fix it (DXJ-219)
|
||||
describe.skip('Ephemeral networks tests', () => {
|
||||
beforeEach(async () => {
|
||||
en = new EphemeralNetwork(defaultConfig);
|
||||
await en.up();
|
||||
const relay = defaultConfig.peers[0].peerId;
|
||||
|
||||
peer = mkTestPeer();
|
||||
await peer.init({
|
||||
KeyPair: await KeyPair.randomEd25519(),
|
||||
});
|
||||
|
||||
const conn = en.getRelayConnection(relay, peer);
|
||||
await peer.connect(conn);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (peer) {
|
||||
await peer.stop();
|
||||
}
|
||||
if (en) {
|
||||
await en.down();
|
||||
}
|
||||
});
|
||||
|
||||
it('smoke test', async function () {
|
||||
const relay = peer.getStatus().relayPeerId!;
|
||||
|
||||
const peers = defaultConfig.peers.map((x) => x.peerId);
|
||||
|
||||
const script = `
|
||||
(seq
|
||||
(call "${relay}" ("op" "noop") [])
|
||||
(seq
|
||||
(call "${peers[0]}" ("op" "noop") [])
|
||||
(seq
|
||||
(call "${peers[1]}" ("op" "noop") [])
|
||||
(seq
|
||||
(call "${peers[2]}" ("op" "noop") [])
|
||||
(seq
|
||||
(call "${peers[3]}" ("op" "noop") [])
|
||||
(seq
|
||||
(call "${peers[4]}" ("op" "noop") [])
|
||||
(seq
|
||||
(call "${relay}" ("op" "noop") [])
|
||||
(call %init_peer_id% ("test" "test") [])
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
`;
|
||||
|
||||
const particle = peer.internals.createNewParticle(script);
|
||||
if (particle instanceof Error) {
|
||||
throw particle;
|
||||
}
|
||||
|
||||
const promise = new Promise<string>((resolve) => {
|
||||
peer.internals.regHandler.forParticle(particle.id, 'test', 'test', (req) => {
|
||||
resolve('success');
|
||||
return {
|
||||
result: 'test',
|
||||
retCode: ResultCodes.success,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, () => {});
|
||||
|
||||
await expect(promise).resolves.toBe('success');
|
||||
});
|
||||
});
|
@ -1,83 +0,0 @@
|
||||
import * as api from '@fluencelabs/aqua-api/aqua-api.js';
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import { FluencePeer, PeerConfig } from '../FluencePeer.js';
|
||||
import { Particle } from '../Particle.js';
|
||||
import { MakeServiceCall } from '../utils.js';
|
||||
import { avmModuleLoader, controlModuleLoader } from '../utilsForNode.js';
|
||||
import { ServiceDef } from '@fluencelabs/interfaces';
|
||||
import { callAquaFunction } from '../../compilerSupport/callFunction.js';
|
||||
|
||||
import { MarineBackgroundRunner } from '../../marine/worker/index.js';
|
||||
import { MarineBasedAvmRunner } from '../avm.js';
|
||||
import { nodes } from './connection.js';
|
||||
import { WorkerLoaderFromFs } from '../../marine/deps-loader/node.js';
|
||||
|
||||
export const registerHandlersHelper = (
|
||||
peer: FluencePeer,
|
||||
particle: Particle,
|
||||
handlers: Record<string, Record<string, any>>,
|
||||
) => {
|
||||
Object.entries(handlers).forEach(([serviceId, service]) => {
|
||||
Object.entries(service).forEach(([fnName, fn]) => {
|
||||
peer.internals.regHandler.forParticle(particle.id, serviceId, fnName, MakeServiceCall(fn));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export type CompiledFnCall = (peer: FluencePeer, args: { [key: string]: any }) => Promise<unknown>;
|
||||
export type CompiledFile = {
|
||||
functions: { [key: string]: CompiledFnCall };
|
||||
services: { [key: string]: ServiceDef };
|
||||
};
|
||||
|
||||
export const compileAqua = async (aquaFile: string): Promise<CompiledFile> => {
|
||||
await fs.access(aquaFile);
|
||||
|
||||
const compilationResult = await api.Aqua.compile(new api.Path(aquaFile), [], undefined);
|
||||
|
||||
if (compilationResult.errors.length > 0) {
|
||||
throw new Error('Aqua compilation failed. Error: ' + compilationResult.errors.join('/n'));
|
||||
}
|
||||
|
||||
const functions = Object.entries(compilationResult.functions)
|
||||
.map(([name, fnInfo]) => {
|
||||
const callFn = (peer: FluencePeer, args: { [key: string]: any }) => {
|
||||
return callAquaFunction({
|
||||
def: fnInfo.funcDef,
|
||||
script: fnInfo.script,
|
||||
config: {},
|
||||
peer: peer,
|
||||
args,
|
||||
});
|
||||
};
|
||||
return { [name]: callFn };
|
||||
})
|
||||
.reduce((agg, obj) => {
|
||||
return { ...agg, ...obj };
|
||||
}, {});
|
||||
|
||||
return { functions, services: compilationResult.services };
|
||||
};
|
||||
|
||||
export const mkTestPeer = () => {
|
||||
const workerLoader = new WorkerLoaderFromFs('../../marine/worker-script');
|
||||
|
||||
const marine = new MarineBackgroundRunner(workerLoader, controlModuleLoader);
|
||||
const avm = new MarineBasedAvmRunner(marine, avmModuleLoader);
|
||||
return new FluencePeer(marine, avm);
|
||||
};
|
||||
|
||||
export const withPeer = async (action: (p: FluencePeer) => Promise<void>, config?: PeerConfig) => {
|
||||
const p = mkTestPeer();
|
||||
try {
|
||||
await p.start(config);
|
||||
await action(p);
|
||||
} finally {
|
||||
await p!.stop();
|
||||
}
|
||||
};
|
||||
|
||||
export const withConnectedPeer = async (action: (p: FluencePeer) => Promise<void>, config?: PeerConfig) => {
|
||||
return withPeer(action, { relay: nodes[0] });
|
||||
};
|
@ -1,9 +0,0 @@
|
||||
import { IFluenceClient, ServiceDef } from '@fluencelabs/interfaces';
|
||||
import { registerService } from '../../compilerSupport/registerService.js';
|
||||
|
||||
export const registerServiceImpl = (
|
||||
peer: IFluenceClient,
|
||||
def: ServiceDef,
|
||||
serviceId: string | undefined,
|
||||
service: any,
|
||||
) => registerService({ peer, def, service, serviceId });
|
@ -1,252 +0,0 @@
|
||||
import { PeerIdB58 } from '@fluencelabs/interfaces';
|
||||
import { FluenceConnection, ParticleHandler } from '../interfaces/index.js';
|
||||
import { fromBase64Sk } from '../keypair/index.js';
|
||||
import { FluencePeer } from './FluencePeer.js';
|
||||
import { MarineBackgroundRunner } from '../marine/worker/index.js';
|
||||
import { avmModuleLoader, controlModuleLoader } from './utilsForNode.js';
|
||||
import { MarineBasedAvmRunner } from './avm.js';
|
||||
|
||||
import { WorkerLoaderFromFs } from '../marine/deps-loader/node.js';
|
||||
|
||||
import { logger } from '../util/logger.js';
|
||||
|
||||
interface EphemeralConfig {
|
||||
peers: Array<{
|
||||
peerId: PeerIdB58;
|
||||
sk: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface PeerAdapter {
|
||||
isEphemeral: boolean;
|
||||
peer: FluencePeer;
|
||||
peerId: PeerIdB58;
|
||||
onIncoming: ParticleHandler;
|
||||
connections: Set<PeerIdB58>;
|
||||
}
|
||||
|
||||
const log = logger('ephemeral');
|
||||
|
||||
export const defaultConfig = {
|
||||
peers: [
|
||||
{
|
||||
peerId: '12D3KooWJankP2PcEDYCZDdJ26JsU8BMRfdGWyGqbtFiWyoKVtmx',
|
||||
sk: 'dWNAHhDVuFj9bEieILMu6TcCFRxBJdOPIvAWmf4sZQI=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWSBTB5sYxdwayUyTnqopBwABsnGFY3p4dTx5hABYDtJjV',
|
||||
sk: 'dOmaxAeu4Th+MJ22vRDLMFTNbiDgKNXar9fW9ofAMgQ=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWQjwf781DJ41moW5RrZXypLdnTbo6aMsoA8QLctGGX8RB',
|
||||
sk: 'TgzaLlxXuOMDNuuuTKEHUKsW0jM4AmX0gahFvkB1KgE=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWCXWTLFyY1mqKnNAhLQTsjW1zqDzCMbUs8M4a8zdz28HK',
|
||||
sk: 'hiO2Ta8g2ibMQ7iu5yj9CfN+qQCwE8oRShjr7ortKww=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWPmZpf4ng6GMS39HLagxsXbjiTPLH5CFJpFAHyN6amw6V',
|
||||
sk: 'LzJtOHTqxfrlHDW40BKiLfjai8JU4yW6/s2zrXLCcQE=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWKrx8PZxM1R9A8tp2jmrFf6c6q1ZQiWfD4QkNgh7fWSoF',
|
||||
sk: 'XMhlk/xr1FPcp7sKQhS18doXlq1x16EMhBC2NGW2LQ4=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWCbJHvnzSZEXjR1UJmtSUozuJK13iRiCYHLN1gjvm4TZZ',
|
||||
sk: 'KXPAIqxrSHr7v0ngv3qagcqivFvnQ0xd3s1/rKmi8QU=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWEvKe7WQHp42W4xhHRgTAWQjtDWyH38uJbLHAsMuTtYvD',
|
||||
sk: 'GCYMAshGnsrNtrHhuT7ayzh5uCzX99J03PmAXoOcCgw=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWSznSHN3BGrSykBXkLkFsqo9SYB73wVauVdqeuRt562cC',
|
||||
sk: 'UP+SEuznS0h259VbFquzyOJAQ4W5iIwhP+hd1PmUQQ0=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWF57jwbShfnT3c4dNfRDdGjr6SQ3B71m87UVpEpSWHFwi',
|
||||
sk: '8dl+Crm5RSh0eh+LqLKwX8/Eo4QLpvIjfD8L0wzX4A4=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWBWrzpSg9nwMLBCa2cJubUjTv63Mfy6PYg9rHGbetaV5C',
|
||||
sk: 'qolc1FcpJ+vHDon0HeXdUYnstjV1wiVx2p0mjblrfAg=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWNkLVU6juM8oyN2SVq5nBd2kp7Rf4uzJH1hET6vj6G5j6',
|
||||
sk: 'vN6QzWILTM7hSHp+iGkKxiXcqs8bzlnH3FPaRaDGSQY=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWKo1YwGL5vivPiKJMJS7wjtB6B2nJNdSXPkSABT4NKBUU',
|
||||
sk: 'YbDQ++bsor2kei7rYAsu2SbyoiOYPRzFRZWnNRUpBgQ=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWLUyBKmmNCyxaPkXoWcUFPcy5qrZsUo2E1tyM6CJmGJvC',
|
||||
sk: 'ptB9eSFMKudAtHaFgDrRK/1oIMrhBujxbMw2Pzwx/wA=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWAEZXME4KMu9FvLezsJWDbYFe2zyujyMnDT1AgcAxgcCk',
|
||||
sk: 'xtwTOKgAbDIgkuPf7RKiR7gYyZ1HY4mOgFMv3sOUcAQ=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWEhXetsFVAD9h2dRz9XgFpfidho1TCZVhFrczX8h8qgzY',
|
||||
sk: '1I2MGuiKG1F4FDMiRihVOcOP2mxzOLWJ99MeexK27A4=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWDBfVNdMyV3hPEF4WLBmx9DwD2t2SYuqZ2mztYmDzZWM1',
|
||||
sk: 'eqJ4Bp7iN4aBXgPH0ezwSg+nVsatkYtfrXv9obI0YQ0=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWSyY7wiSiR4vbXa1WtZawi3ackMTqcQhEPrvqtagoWPny',
|
||||
sk: 'UVM3SBJhPYIY/gafpnd9/q/Fn9V4BE9zkgrvF1T7Pgc=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWFZmBMGG9PxTs9s6ASzkLGKJWMyPheA5ruaYc2FDkDTmv',
|
||||
sk: '8RbZfEVpQhPVuhv64uqxENDuSoyJrslQoSQJznxsTQ0=',
|
||||
},
|
||||
{
|
||||
peerId: '12D3KooWBbhUaqqur6KHPunnKxXjY1daCtqJdy4wRji89LmAkVB4',
|
||||
sk: 'RbgKmG6soWW9uOi7yRedm+0Qck3f3rw6MSnDP7AcBQs=',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Ephemeral network implementation.
|
||||
* Ephemeral network is a virtual network which runs locally and focuses on p2p interaction by removing connectivity layer out of the equation.
|
||||
*/
|
||||
export class EphemeralNetwork {
|
||||
private _peers: Map<PeerIdB58, PeerAdapter> = new Map();
|
||||
|
||||
constructor(public readonly config: EphemeralConfig) {}
|
||||
|
||||
/**
|
||||
* Starts the Ephemeral network up
|
||||
*/
|
||||
async up(): Promise<void> {
|
||||
log.trace('starting ephemeral network up...');
|
||||
const allPeerIds = this.config.peers.map((x) => x.peerId);
|
||||
// shared worker for all the peers
|
||||
const workerLoader = new WorkerLoaderFromFs('../../marine/worker-script');
|
||||
|
||||
const promises = this.config.peers.map(async (x) => {
|
||||
const marine = new MarineBackgroundRunner(workerLoader, controlModuleLoader);
|
||||
const avm = new MarineBasedAvmRunner(marine, avmModuleLoader);
|
||||
const peer = new FluencePeer(marine, avm);
|
||||
const sendParticle = async (nextPeerIds: string[], particle: string): Promise<void> => {
|
||||
this._send(peer.getStatus().peerId!, nextPeerIds, particle);
|
||||
};
|
||||
const kp = await fromBase64Sk(x.sk);
|
||||
if (kp.getPeerId() !== x.peerId) {
|
||||
throw new Error(`Invalid config: peer id ${x.peerId} does not match the secret key ${x.sk}`);
|
||||
}
|
||||
await peer.init({}, kp);
|
||||
|
||||
let handler: ParticleHandler | null = null;
|
||||
const connectionCtor = class extends FluenceConnection {
|
||||
relayPeerId = null;
|
||||
|
||||
async connect(onIncomingParticle: ParticleHandler): Promise<void> {
|
||||
handler = onIncomingParticle;
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
handler = null;
|
||||
}
|
||||
|
||||
sendParticle = sendParticle;
|
||||
};
|
||||
|
||||
await peer._connect(new connectionCtor());
|
||||
|
||||
const peerId = peer.getStatus().peerId!;
|
||||
const ephPeer: PeerAdapter = {
|
||||
isEphemeral: true,
|
||||
connections: new Set(allPeerIds.filter((x) => x !== peerId)),
|
||||
peer: peer,
|
||||
peerId: peerId,
|
||||
onIncoming: handler!,
|
||||
};
|
||||
return [peerId, ephPeer] as const;
|
||||
});
|
||||
const values = await Promise.all(promises);
|
||||
this._peers = new Map(values);
|
||||
log.trace('ephemeral network started...');
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts the ephemeral network down. Will disconnect all connected peers.
|
||||
*/
|
||||
async down(): Promise<void> {
|
||||
log.trace('shutting down ephemeral network...');
|
||||
const peers = Array.from(this._peers.entries());
|
||||
const promises = peers.map(([k, p]) => {
|
||||
return p.isEphemeral ? p.peer.stop() : p.peer._disconnect();
|
||||
});
|
||||
await Promise.all(promises);
|
||||
this._peers.clear();
|
||||
log.trace('ephemeral network shut down');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the FluenceConnection which can be used to connect to the ephemeral networks via the specified relay peer.
|
||||
*/
|
||||
getRelayConnection(relay: PeerIdB58, peer: FluencePeer): FluenceConnection {
|
||||
const me = this;
|
||||
const relayPeer = this._peers.get(relay);
|
||||
if (relayPeer === undefined) {
|
||||
throw new Error(`Relay with peer Id: ${relay} has not been found in ephemeral network`);
|
||||
}
|
||||
const connectionCtor = class extends FluenceConnection {
|
||||
relayPeerId = relay;
|
||||
|
||||
async connect(onIncomingParticle: ParticleHandler): Promise<void> {
|
||||
const peerId = peer.getStatus().peerId!;
|
||||
me._peers.set(peerId, {
|
||||
isEphemeral: false,
|
||||
peer: peer,
|
||||
onIncoming: onIncomingParticle,
|
||||
peerId: peerId,
|
||||
connections: new Set([relay]),
|
||||
});
|
||||
relayPeer.connections.add(peerId);
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
const peerId = peer.getStatus().peerId!;
|
||||
relayPeer.connections.delete(peerId);
|
||||
me._peers.delete(peerId);
|
||||
}
|
||||
async sendParticle(nextPeerIds: string[], particle: string): Promise<void> {
|
||||
const peerId = peer.getStatus().peerId!;
|
||||
me._send(peerId, nextPeerIds, particle);
|
||||
}
|
||||
};
|
||||
return new connectionCtor();
|
||||
}
|
||||
|
||||
private async _send(from: PeerIdB58, to: PeerIdB58[], particle: string) {
|
||||
log.trace(`Sending particle from %s, to %j`, from, to);
|
||||
const peer = this._peers.get(from);
|
||||
if (peer === undefined) {
|
||||
log.error(`Peer ${from} cannot be found in ephemeral network`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let dest of to) {
|
||||
if (!peer.connections.has(dest)) {
|
||||
log.error(`Peer ${from} has no connection with ${dest}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const destPeer = this._peers.get(dest);
|
||||
if (destPeer === undefined) {
|
||||
log.error(`peer ${destPeer} cannot be found in ephemeral network`);
|
||||
continue;
|
||||
}
|
||||
|
||||
destPeer.onIncoming(particle);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import { WorkerLoaderFromFs, WasmLoaderFromFs, WasmLoaderFromNpm } from '../marine/deps-loader/node.js';
|
||||
|
||||
// TODO!: after moving to ESM loaders stopped working. Should be fixed in scope of DXJ-194
|
||||
export const controlModuleLoader = new WasmLoaderFromNpm('@fluencelabs/marine-js', 'marine-js.wasm');
|
||||
export const avmModuleLoader = new WasmLoaderFromNpm('@fluencelabs/avm', 'avm.wasm');
|
557
packages/core/js-peer/src/jsPeer/FluencePeer.ts
Normal file
557
packages/core/js-peer/src/jsPeer/FluencePeer.ts
Normal file
@ -0,0 +1,557 @@
|
||||
/*
|
||||
* Copyright 2021 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 { KeyPair } from '../keypair/index.js';
|
||||
|
||||
import type { PeerIdB58 } from '@fluencelabs/interfaces';
|
||||
import {
|
||||
cloneWithNewData,
|
||||
getActualTTL,
|
||||
hasExpired,
|
||||
Particle,
|
||||
ParticleExecutionStage,
|
||||
ParticleQueueItem,
|
||||
} from '../particle/Particle.js';
|
||||
import { jsonify, isString } from '../util/utils.js';
|
||||
import { concatMap, filter, pipe, Subject, tap, Unsubscribable } from 'rxjs';
|
||||
import { defaultSigGuard, Sig } from '../services/Sig.js';
|
||||
import { registerSig } from '../services/_aqua/services.js';
|
||||
import { registerSrv } from '../services/_aqua/single-module-srv.js';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
import { Srv } from '../services/SingleModuleSrv.js';
|
||||
|
||||
import { logger } from '../util/logger.js';
|
||||
import { getParticleContext, registerDefaultServices, ServiceError } from '../jsServiceHost/serviceUtils.js';
|
||||
import { IParticle } from '../particle/interfaces.js';
|
||||
import { IConnection } from '../connection/interfaces.js';
|
||||
import { IAvmRunner, IMarineHost } from '../marine/interfaces.js';
|
||||
import {
|
||||
CallServiceData,
|
||||
CallServiceResult,
|
||||
GenericCallServiceHandler,
|
||||
IJsServiceHost,
|
||||
ResultCodes,
|
||||
} from '../jsServiceHost/interfaces.js';
|
||||
import { JSONValue } from '../util/commonTypes.js';
|
||||
|
||||
const log_particle = logger('particle');
|
||||
const log_peer = logger('peer');
|
||||
|
||||
export type PeerConfig = {
|
||||
/**
|
||||
* Sets the default TTL for all particles originating from the peer with no TTL specified.
|
||||
* If the originating particle's TTL is defined then that value will be used
|
||||
* If the option is not set default TTL will be 7000
|
||||
*/
|
||||
defaultTtlMs: number;
|
||||
|
||||
/**
|
||||
* Enables\disabled various debugging features
|
||||
*/
|
||||
debug: {
|
||||
/**
|
||||
* If set to true, newly initiated particle ids will be printed to console.
|
||||
* Useful to see what particle id is responsible for aqua function
|
||||
*/
|
||||
printParticleId: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export const DEFAULT_CONFIG: PeerConfig = {
|
||||
debug: {
|
||||
printParticleId: false,
|
||||
},
|
||||
defaultTtlMs: 7000,
|
||||
};
|
||||
|
||||
/**
|
||||
* This class implements the Fluence protocol for javascript-based environments.
|
||||
* It provides all the necessary features to communicate with Fluence network
|
||||
*/
|
||||
export abstract class FluencePeer {
|
||||
constructor(
|
||||
protected readonly config: PeerConfig,
|
||||
public readonly keyPair: KeyPair,
|
||||
protected readonly marineHost: IMarineHost,
|
||||
protected readonly jsServiceHost: IJsServiceHost,
|
||||
protected readonly avmRunner: IAvmRunner,
|
||||
protected readonly connection: IConnection,
|
||||
) {
|
||||
this._initServices();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal contract to cast unknown objects to IFluenceClient.
|
||||
* If an unknown object has this property then we assume it is in fact a Peer and it implements IFluenceClient
|
||||
* Check against this variable MUST NOT be coupled with any `FluencePeer` because otherwise it might get bundled
|
||||
* brining a lot of unnecessary stuff alongside with it
|
||||
*/
|
||||
__isFluenceAwesome = true;
|
||||
|
||||
async start(): Promise<void> {
|
||||
log_peer.trace('starting Fluence peer');
|
||||
if (this.config?.debug?.printParticleId) {
|
||||
this.printParticleId = true;
|
||||
}
|
||||
|
||||
await this.marineHost.start();
|
||||
await this.avmRunner.start();
|
||||
|
||||
this._startParticleProcessing();
|
||||
this.isInitialized = true;
|
||||
log_peer.trace('started Fluence peer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-initializes the peer: stops all the underlying workflows, stops the Aqua VM
|
||||
* and disconnects from the Fluence network
|
||||
*/
|
||||
async stop() {
|
||||
log_peer.trace('stopping Fluence peer');
|
||||
this._particleSourceSubscription?.unsubscribe();
|
||||
this._stopParticleProcessing();
|
||||
await this.marineHost.stop();
|
||||
await this.avmRunner.stop();
|
||||
|
||||
this.isInitialized = false;
|
||||
log_peer.trace('stopped Fluence peer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers marine service within the Fluence peer from wasm file.
|
||||
* Following helper functions can be used to load wasm files:
|
||||
* * loadWasmFromFileSystem
|
||||
* * loadWasmFromNpmPackage
|
||||
* * loadWasmFromServer
|
||||
* @param wasm - buffer with the wasm file for service
|
||||
* @param serviceId - the service id by which the service can be accessed in aqua
|
||||
*/
|
||||
async registerMarineService(wasm: SharedArrayBuffer | Buffer, serviceId: string): Promise<void> {
|
||||
if (!this.marineHost) {
|
||||
throw new Error("Can't register marine service: peer is not initialized");
|
||||
}
|
||||
|
||||
if (this.jsServiceHost.hasService(serviceId)) {
|
||||
throw new Error(`Service with '${serviceId}' id already exists`);
|
||||
}
|
||||
|
||||
await this.marineHost.createService(wasm, serviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified marine service from the Fluence peer
|
||||
* @param serviceId - the service id to remove
|
||||
*/
|
||||
removeMarineService(serviceId: string): void {
|
||||
this.marineHost.removeService(serviceId);
|
||||
}
|
||||
|
||||
// internal api
|
||||
|
||||
/**
|
||||
* @private Is not intended to be used manually. Subject to change
|
||||
*/
|
||||
get internals() {
|
||||
return {
|
||||
getServices: () => this._classServices,
|
||||
|
||||
getRelayPeerId: () => {
|
||||
if (this.connection.supportsRelay()) {
|
||||
return this.connection.getRelayPeerId();
|
||||
}
|
||||
|
||||
throw new Error('Relay is not supported by the current connection');
|
||||
},
|
||||
|
||||
parseAst: async (air: string): Promise<{ success: boolean; data: any }> => {
|
||||
if (!this.isInitialized) {
|
||||
new Error("Can't use avm: peer is not initialized");
|
||||
}
|
||||
|
||||
const res = await this.marineHost.callService('avm', 'ast', [air], undefined);
|
||||
if (!isString(res)) {
|
||||
throw new Error(`Call to avm:ast expected to return string. Actual return: ${res}`);
|
||||
}
|
||||
|
||||
try {
|
||||
if (res.startsWith('error')) {
|
||||
return {
|
||||
success: false,
|
||||
data: res,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: true,
|
||||
data: JSON.parse(res),
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error('Failed to call avm. Result: ' + res + '. Error: ' + err);
|
||||
}
|
||||
},
|
||||
|
||||
createNewParticle: (script: string, ttl: number = this.config.defaultTtlMs): IParticle => {
|
||||
return Particle.createNew(script, this.keyPair.getPeerId(), ttl);
|
||||
},
|
||||
|
||||
/**
|
||||
* Initiates a new particle execution starting from local peer
|
||||
* @param particle - particle to start execution of
|
||||
*/
|
||||
initiateParticle: (particle: IParticle, onStageChange: (stage: ParticleExecutionStage) => void): void => {
|
||||
if (!this.isInitialized) {
|
||||
throw new Error('Cannot initiate new particle: peer is not initialized');
|
||||
}
|
||||
|
||||
if (this.printParticleId) {
|
||||
console.log('Particle id: ', particle.id);
|
||||
}
|
||||
|
||||
this._incomingParticles.next({
|
||||
particle: particle,
|
||||
callResults: [],
|
||||
onStageChange: onStageChange,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Register Call Service handler functions
|
||||
*/
|
||||
regHandler: {
|
||||
/**
|
||||
* Register handler for all particles
|
||||
*/
|
||||
common: this.jsServiceHost.registerGlobalHandler.bind(this.jsServiceHost),
|
||||
/**
|
||||
* Register handler which will be called only for particle with the specific id
|
||||
*/
|
||||
forParticle: this.jsServiceHost.registerParticleScopeHandler.bind(this.jsServiceHost),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Queues for incoming and outgoing particles
|
||||
|
||||
private _incomingParticles = new Subject<ParticleQueueItem>();
|
||||
private _outgoingParticles = new Subject<ParticleQueueItem & { nextPeerIds: PeerIdB58[] }>();
|
||||
private _timeouts: Array<NodeJS.Timeout> = [];
|
||||
private _particleSourceSubscription?: Unsubscribable;
|
||||
private _particleQueues = new Map<string, Subject<ParticleQueueItem>>();
|
||||
|
||||
// Internal peer state
|
||||
|
||||
// @ts-expect-error - initialized in constructor through `_initServices` call
|
||||
private _classServices: {
|
||||
sig: Sig;
|
||||
srv: Srv;
|
||||
};
|
||||
|
||||
private isInitialized = false;
|
||||
private printParticleId = false;
|
||||
|
||||
private _initServices() {
|
||||
this._classServices = {
|
||||
sig: new Sig(this.keyPair),
|
||||
srv: new Srv(this),
|
||||
};
|
||||
|
||||
const peerId = this.keyPair.getPeerId();
|
||||
|
||||
registerDefaultServices(this);
|
||||
|
||||
this._classServices.sig.securityGuard = defaultSigGuard(peerId);
|
||||
registerSig(this, 'sig', this._classServices.sig);
|
||||
registerSig(this, peerId, this._classServices.sig);
|
||||
|
||||
registerSrv(this, 'single_module_srv', this._classServices.srv);
|
||||
}
|
||||
|
||||
private _startParticleProcessing() {
|
||||
this._particleSourceSubscription = this.connection.particleSource.subscribe({
|
||||
next: (p) => {
|
||||
this._incomingParticles.next({ particle: p, callResults: [], onStageChange: () => {} });
|
||||
},
|
||||
});
|
||||
|
||||
this._incomingParticles
|
||||
.pipe(
|
||||
tap((item) => {
|
||||
log_particle.debug('id %s. received:', item.particle.id);
|
||||
log_particle.trace('id %s. data: %j', item.particle.id, {
|
||||
initPeerId: item.particle.initPeerId,
|
||||
timestamp: item.particle.timestamp,
|
||||
tttl: item.particle.ttl,
|
||||
signature: item.particle.signature,
|
||||
});
|
||||
|
||||
log_particle.trace('id %s. script: %s', item.particle.id, item.particle.script);
|
||||
log_particle.trace('id %s. call results: %j', item.particle.id, item.callResults);
|
||||
}),
|
||||
filterExpiredParticles(this._expireParticle.bind(this)),
|
||||
)
|
||||
.subscribe((item) => {
|
||||
const p = item.particle;
|
||||
let particlesQueue = this._particleQueues.get(p.id);
|
||||
|
||||
if (!particlesQueue) {
|
||||
particlesQueue = this._createParticlesProcessingQueue();
|
||||
this._particleQueues.set(p.id, particlesQueue);
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
this._expireParticle(item);
|
||||
}, getActualTTL(p));
|
||||
|
||||
this._timeouts.push(timeout);
|
||||
}
|
||||
|
||||
particlesQueue.next(item);
|
||||
});
|
||||
|
||||
this._outgoingParticles.subscribe((item) => {
|
||||
// Do not send particle after the peer has been stopped
|
||||
if (!this.isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_particle.debug(
|
||||
'id %s. sending particle into network. Next peer ids: %s',
|
||||
item.particle.id,
|
||||
item.nextPeerIds.toString(),
|
||||
);
|
||||
|
||||
this.connection
|
||||
?.sendParticle(item.nextPeerIds, item.particle)
|
||||
.then(() => {
|
||||
item.onStageChange({ stage: 'sent' });
|
||||
})
|
||||
.catch((e: any) => {
|
||||
log_particle.error('id %s. send failed %j', item.particle.id, e);
|
||||
item.onStageChange({ stage: 'sendingError', errorMessage: e.toString() });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _expireParticle(item: ParticleQueueItem) {
|
||||
const particleId = item.particle.id;
|
||||
log_particle.debug(
|
||||
'id %s. particle has expired after %d. Deleting particle-related queues and handlers',
|
||||
item.particle.id,
|
||||
item.particle.ttl,
|
||||
);
|
||||
|
||||
this._particleQueues.delete(particleId);
|
||||
this.jsServiceHost.removeParticleScopeHandlers(particleId);
|
||||
|
||||
item.onStageChange({ stage: 'expired' });
|
||||
}
|
||||
|
||||
private _createParticlesProcessingQueue() {
|
||||
const particlesQueue = new Subject<ParticleQueueItem>();
|
||||
let prevData: Uint8Array = Buffer.from([]);
|
||||
|
||||
particlesQueue
|
||||
.pipe(
|
||||
filterExpiredParticles(this._expireParticle.bind(this)),
|
||||
|
||||
concatMap(async (item) => {
|
||||
if (!this.isInitialized || this.marineHost === undefined) {
|
||||
// If `.stop()` was called return null to stop particle processing immediately
|
||||
return null;
|
||||
}
|
||||
|
||||
// IMPORTANT!
|
||||
// AVM runner execution and prevData <-> newData swapping
|
||||
// MUST happen sequentially (in a critical section).
|
||||
// Otherwise the race might occur corrupting the prevData
|
||||
|
||||
log_particle.debug('id %s. sending particle to interpreter', item.particle.id);
|
||||
log_particle.trace('id %s. prevData: %a', item.particle.id, prevData);
|
||||
const avmCallResult = await this.avmRunner.run(
|
||||
{
|
||||
initPeerId: item.particle.initPeerId,
|
||||
currentPeerId: this.keyPair.getPeerId(),
|
||||
timestamp: item.particle.timestamp,
|
||||
ttl: item.particle.ttl,
|
||||
},
|
||||
item.particle.script,
|
||||
prevData,
|
||||
item.particle.data,
|
||||
item.callResults,
|
||||
);
|
||||
|
||||
if (!(avmCallResult instanceof Error) && avmCallResult.retCode === 0) {
|
||||
const newData = Buffer.from(avmCallResult.data);
|
||||
prevData = newData;
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
result: avmCallResult,
|
||||
};
|
||||
}),
|
||||
)
|
||||
.subscribe((item) => {
|
||||
// If peer was stopped, do not proceed further
|
||||
if (item === null || !this.isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not proceed further if the particle is expired
|
||||
if (hasExpired(item.particle)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not continue if there was an error in particle interpretation
|
||||
if (item.result instanceof Error) {
|
||||
log_particle.error('id %s. interpreter failed: %s', item.particle.id, item.result.message);
|
||||
item.onStageChange({ stage: 'interpreterError', errorMessage: item.result.message });
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.result.retCode !== 0) {
|
||||
log_particle.error(
|
||||
'id %s. interpreter failed: retCode: %d, message: %s',
|
||||
item.particle.id,
|
||||
item.result.retCode,
|
||||
item.result.errorMessage,
|
||||
);
|
||||
log_particle.trace('id %s. avm data: %a', item.particle.id, item.result.data);
|
||||
item.onStageChange({ stage: 'interpreterError', errorMessage: item.result.errorMessage });
|
||||
return;
|
||||
}
|
||||
|
||||
log_particle.trace(
|
||||
'id %s. interpreter result: retCode: %d, avm data: %a',
|
||||
item.particle.id,
|
||||
item.result.retCode,
|
||||
item.result.data,
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
item.onStageChange({ stage: 'interpreted' });
|
||||
}, 0);
|
||||
|
||||
// send particle further if requested
|
||||
if (item.result.nextPeerPks.length > 0) {
|
||||
const newParticle = cloneWithNewData(item.particle, Buffer.from(item.result.data));
|
||||
this._outgoingParticles.next({
|
||||
...item,
|
||||
particle: newParticle,
|
||||
nextPeerIds: item.result.nextPeerPks,
|
||||
});
|
||||
}
|
||||
|
||||
// execute call requests if needed
|
||||
// and put particle with the results back to queue
|
||||
if (item.result.callRequests.length > 0) {
|
||||
for (const [key, cr] of item.result.callRequests) {
|
||||
const req = {
|
||||
fnName: cr.functionName,
|
||||
args: cr.arguments,
|
||||
serviceId: cr.serviceId,
|
||||
tetraplets: cr.tetraplets,
|
||||
particleContext: getParticleContext(item.particle),
|
||||
};
|
||||
|
||||
if (hasExpired(item.particle)) {
|
||||
// just in case do not call any services if the particle is already expired
|
||||
return;
|
||||
}
|
||||
this._execSingleCallRequest(req)
|
||||
.catch((err): CallServiceResult => {
|
||||
if (err instanceof ServiceError) {
|
||||
return {
|
||||
retCode: ResultCodes.error,
|
||||
result: err.message,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
retCode: ResultCodes.error,
|
||||
result: `Service call failed. fnName="${req.fnName}" serviceId="${
|
||||
req.serviceId
|
||||
}" error: ${err.toString()}`,
|
||||
};
|
||||
})
|
||||
.then((res) => {
|
||||
const serviceResult = {
|
||||
result: jsonify(res.result),
|
||||
retCode: res.retCode,
|
||||
};
|
||||
|
||||
const newParticle = cloneWithNewData(item.particle, Buffer.from([]));
|
||||
particlesQueue.next({
|
||||
...item,
|
||||
particle: newParticle,
|
||||
callResults: [[key, serviceResult]],
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
item.onStageChange({ stage: 'localWorkDone' });
|
||||
}
|
||||
});
|
||||
|
||||
return particlesQueue;
|
||||
}
|
||||
|
||||
private async _execSingleCallRequest(req: CallServiceData): Promise<CallServiceResult> {
|
||||
const particleId = req.particleContext.particleId;
|
||||
log_particle.trace('id %s. executing call service handler %j', particleId, req);
|
||||
|
||||
if (this.marineHost && this.marineHost.hasService(req.serviceId)) {
|
||||
const result = await this.marineHost.callService(req.serviceId, req.fnName, req.args, undefined);
|
||||
|
||||
return {
|
||||
retCode: ResultCodes.success,
|
||||
result: result as JSONValue,
|
||||
};
|
||||
}
|
||||
|
||||
let res = await this.jsServiceHost.callService(req);
|
||||
|
||||
if (res === null) {
|
||||
res = {
|
||||
retCode: ResultCodes.error,
|
||||
result: `No service found for service call: serviceId='${req.serviceId}', fnName='${
|
||||
req.fnName
|
||||
}' args='${jsonify(req.args)}'`,
|
||||
};
|
||||
}
|
||||
|
||||
log_particle.trace('id %s. executed call service handler, req: %j, res: %j ', particleId, req, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
private _stopParticleProcessing() {
|
||||
// do not hang if the peer has been stopped while some of the timeouts are still being executed
|
||||
this._timeouts.forEach((timeout) => {
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
this._particleQueues.clear();
|
||||
}
|
||||
}
|
||||
|
||||
function filterExpiredParticles(onParticleExpiration: (item: ParticleQueueItem) => void) {
|
||||
return pipe(
|
||||
tap((item: ParticleQueueItem) => {
|
||||
if (hasExpired(item.particle)) {
|
||||
onParticleExpiration(item);
|
||||
}
|
||||
}),
|
||||
filter((x: ParticleQueueItem) => !hasExpired(x.particle)),
|
||||
);
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
import { it, describe, expect } from 'vitest';
|
||||
import { registerHandlersHelper, withPeer } from '../../util/testUtils.js';
|
||||
import { handleTimeout } from '../../particle/Particle.js';
|
||||
|
||||
import { handleTimeout } from '../../utils.js';
|
||||
import { registerHandlersHelper, withPeer } from '../util.js';
|
||||
|
||||
describe('Avm spec', () => {
|
||||
describe('Basic AVM functionality in Fluence Peer tests', () => {
|
||||
it('Simple call', async () => {
|
||||
await withPeer(async (peer) => {
|
||||
const res = await new Promise<string[]>((resolve, reject) => {
|
29
packages/core/js-peer/src/jsPeer/__test__/parseAst.spec.ts
Normal file
29
packages/core/js-peer/src/jsPeer/__test__/parseAst.spec.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { it, describe, expect } from 'vitest';
|
||||
|
||||
import { withPeer } from '../../util/testUtils.js';
|
||||
|
||||
describe('Parse ast tests', () => {
|
||||
it('Correct ast should be parsed correctly', async () => {
|
||||
withPeer(async (peer) => {
|
||||
const air = `(null)`;
|
||||
const res = await peer.internals.parseAst(air);
|
||||
|
||||
expect(res).toStrictEqual({
|
||||
success: true,
|
||||
data: { Null: null },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Incorrect ast should result in corresponding error', async () => {
|
||||
withPeer(async (peer) => {
|
||||
const air = `(null`;
|
||||
const res = await peer.internals.parseAst(air);
|
||||
|
||||
expect(res).toStrictEqual({
|
||||
success: false,
|
||||
data: expect.stringContaining('error'),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
178
packages/core/js-peer/src/jsPeer/__test__/peer.spec.ts
Normal file
178
packages/core/js-peer/src/jsPeer/__test__/peer.spec.ts
Normal file
@ -0,0 +1,178 @@
|
||||
import { it, describe, expect } from 'vitest';
|
||||
|
||||
import { isFluencePeer } from '@fluencelabs/interfaces';
|
||||
import { mkTestPeer, registerHandlersHelper, withPeer } from '../../util/testUtils.js';
|
||||
import { handleTimeout } from '../../particle/Particle.js';
|
||||
import { FluencePeer } from '../FluencePeer.js';
|
||||
|
||||
describe('FluencePeer usage test suite', () => {
|
||||
it('should perform test for FluencePeer class correctly', async () => {
|
||||
// arrange
|
||||
const peer = await mkTestPeer();
|
||||
const number = 1;
|
||||
const object = { str: 'Hello!' };
|
||||
const undefinedVal = undefined;
|
||||
|
||||
// act
|
||||
const isPeerPeer = isFluencePeer(peer);
|
||||
const isNumberPeer = isFluencePeer(number);
|
||||
const isObjectPeer = isFluencePeer(object);
|
||||
const isUndefinedPeer = isFluencePeer(undefinedVal);
|
||||
|
||||
// act
|
||||
expect(isPeerPeer).toBe(true);
|
||||
expect(isNumberPeer).toBe(false);
|
||||
expect(isObjectPeer).toBe(false);
|
||||
expect(isUndefinedPeer).toBe(false);
|
||||
});
|
||||
|
||||
it('Should successfully call identity on local peer', async function () {
|
||||
await withPeer(async (peer) => {
|
||||
const res = await new Promise<string>((resolve, reject) => {
|
||||
const script = `
|
||||
(seq
|
||||
(call %init_peer_id% ("op" "identity") ["test"] res)
|
||||
(call %init_peer_id% ("callback" "callback") [res])
|
||||
)
|
||||
`;
|
||||
const particle = peer.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
return reject(particle.message);
|
||||
}
|
||||
|
||||
registerHandlersHelper(peer, particle, {
|
||||
callback: {
|
||||
callback: async (args: any) => {
|
||||
const [res] = args;
|
||||
resolve(res);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
|
||||
expect(res).toBe('test');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should throw correct message when calling non existing local service', async function () {
|
||||
await withPeer(async (peer) => {
|
||||
const res = callIncorrectService(peer);
|
||||
|
||||
await expect(res).rejects.toMatchObject({
|
||||
message: expect.stringContaining(
|
||||
`"No service found for service call: serviceId='incorrect', fnName='incorrect' args='[]'"`,
|
||||
),
|
||||
instruction: 'call %init_peer_id% ("incorrect" "incorrect") [] res',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not crash if undefined is passed as a variable', async () => {
|
||||
await withPeer(async (peer) => {
|
||||
const res = await new Promise<any>((resolve, reject) => {
|
||||
const script = `
|
||||
(seq
|
||||
(call %init_peer_id% ("load" "arg") [] arg)
|
||||
(seq
|
||||
(call %init_peer_id% ("op" "identity") [arg] res)
|
||||
(call %init_peer_id% ("callback" "callback") [res])
|
||||
)
|
||||
)`;
|
||||
const particle = peer.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
return reject(particle.message);
|
||||
}
|
||||
|
||||
registerHandlersHelper(peer, particle, {
|
||||
load: {
|
||||
arg: () => undefined,
|
||||
},
|
||||
callback: {
|
||||
callback: (args: any) => {
|
||||
const [val] = args;
|
||||
resolve(val);
|
||||
},
|
||||
error: (args: any) => {
|
||||
const [error] = args;
|
||||
reject(error);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
|
||||
expect(res).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not crash if an error ocurred in user-defined handler', async () => {
|
||||
await withPeer(async (peer) => {
|
||||
const promise = new Promise<any>((_resolve, reject) => {
|
||||
const script = `
|
||||
(xor
|
||||
(call %init_peer_id% ("load" "arg") [] arg)
|
||||
(call %init_peer_id% ("callback" "error") [%last_error%])
|
||||
)`;
|
||||
const particle = peer.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
return reject(particle.message);
|
||||
}
|
||||
|
||||
registerHandlersHelper(peer, particle, {
|
||||
load: {
|
||||
arg: () => {
|
||||
throw new Error('my super custom error message');
|
||||
},
|
||||
},
|
||||
callback: {
|
||||
error: (args: any) => {
|
||||
const [error] = args;
|
||||
reject(error);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
|
||||
await expect(promise).rejects.toMatchObject({
|
||||
message: expect.stringContaining('my super custom error message'),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function callIncorrectService(peer: FluencePeer): Promise<string[]> {
|
||||
return new Promise<any[]>((resolve, reject) => {
|
||||
const script = `
|
||||
(xor
|
||||
(call %init_peer_id% ("incorrect" "incorrect") [] res)
|
||||
(call %init_peer_id% ("callback" "error") [%last_error%])
|
||||
)`;
|
||||
const particle = peer.internals.createNewParticle(script);
|
||||
|
||||
if (particle instanceof Error) {
|
||||
return reject(particle.message);
|
||||
}
|
||||
|
||||
registerHandlersHelper(peer, particle, {
|
||||
callback: {
|
||||
callback: (args: any) => {
|
||||
resolve(args);
|
||||
},
|
||||
error: (args: any) => {
|
||||
const [error] = args;
|
||||
reject(error);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
}
|
@ -1,9 +1,24 @@
|
||||
/*
|
||||
* Copyright 2023 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 type { CallResultsArray, InterpreterResult, RunParameters } from '@fluencelabs/avm';
|
||||
import { deserializeAvmResult, serializeAvmArgs } from '@fluencelabs/avm';
|
||||
import type { IMarine, IAvmRunner, IWasmLoader } from '../interfaces/index.js';
|
||||
import { IAvmRunner, IMarineHost, IWasmLoader } from '../marine/interfaces.js';
|
||||
|
||||
export class MarineBasedAvmRunner implements IAvmRunner {
|
||||
constructor(private marine: IMarine, private avmWasmLoader: IWasmLoader) {}
|
||||
constructor(private marine: IMarineHost, private avmWasmLoader: IWasmLoader) {}
|
||||
|
||||
async run(
|
||||
runParams: RunParameters,
|
111
packages/core/js-peer/src/jsServiceHost/JsServiceHost.ts
Normal file
111
packages/core/js-peer/src/jsServiceHost/JsServiceHost.ts
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2023 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 { CallServiceData, CallServiceResult, GenericCallServiceHandler, IJsServiceHost } from './interfaces.js';
|
||||
|
||||
export class JsServiceHost implements IJsServiceHost {
|
||||
private particleScopeHandlers = new Map<string, Map<string, GenericCallServiceHandler>>();
|
||||
private commonHandlers = new Map<string, GenericCallServiceHandler>();
|
||||
|
||||
/**
|
||||
* Returns true if any handler for the specified serviceId is registered
|
||||
*/
|
||||
hasService(serviceId: string): boolean {
|
||||
return this.commonHandlers.has(serviceId) || this.particleScopeHandlers.has(serviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all handlers associated with the specified particle scope
|
||||
* @param particleId Particle ID to remove handlers for
|
||||
*/
|
||||
removeParticleScopeHandlers(particleId: string): void {
|
||||
this.particleScopeHandlers.delete(particleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find call service handler for specified particle
|
||||
* @param serviceId Service ID as specified in `call` air instruction
|
||||
* @param fnName Function name as specified in `call` air instruction
|
||||
* @param particleId Particle ID
|
||||
*/
|
||||
getHandler(serviceId: string, fnName: string, particleId: string): GenericCallServiceHandler | null {
|
||||
const key = serviceFnKey(serviceId, fnName);
|
||||
const psh = this.particleScopeHandlers.get(particleId);
|
||||
let handler: GenericCallServiceHandler | undefined = undefined;
|
||||
|
||||
// we should prioritize handler for this particle if there is one
|
||||
// if particle-scoped handler exist for this particle try getting handler there
|
||||
if (psh !== undefined) {
|
||||
handler = psh.get(key);
|
||||
}
|
||||
|
||||
// then try to find a common handler for all particles with this service-fn key
|
||||
// if there is no particle-specific handler, get one from common map
|
||||
if (handler === undefined) {
|
||||
handler = this.commonHandlers.get(key);
|
||||
}
|
||||
|
||||
return handler || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute service call for specified call service data. Return null if no handler was found
|
||||
*/
|
||||
async callService(req: CallServiceData): Promise<CallServiceResult | null> {
|
||||
const handler = this.getHandler(req.serviceId, req.fnName, req.particleContext.particleId);
|
||||
|
||||
if (handler === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await handler(req);
|
||||
|
||||
// Otherwise AVM might break
|
||||
if (result.result === undefined) {
|
||||
result.result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register handler for all particles
|
||||
*/
|
||||
registerGlobalHandler(serviceId: string, fnName: string, handler: GenericCallServiceHandler): void {
|
||||
this.commonHandlers.set(serviceFnKey(serviceId, fnName), handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register handler which will be called only for particle with the specific id
|
||||
*/
|
||||
registerParticleScopeHandler(
|
||||
particleId: string,
|
||||
serviceId: string,
|
||||
fnName: string,
|
||||
handler: GenericCallServiceHandler,
|
||||
): void {
|
||||
let psh = this.particleScopeHandlers.get(particleId);
|
||||
if (psh === undefined) {
|
||||
psh = new Map<string, GenericCallServiceHandler>();
|
||||
this.particleScopeHandlers.set(particleId, psh);
|
||||
}
|
||||
|
||||
psh.set(serviceFnKey(serviceId, fnName), handler);
|
||||
}
|
||||
}
|
||||
|
||||
function serviceFnKey(serviceId: string, fnName: string) {
|
||||
return `${serviceId}/${fnName}`;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
* Copyright 2023 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.
|
||||
@ -13,9 +13,54 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { PeerIdB58 } from '@fluencelabs/interfaces';
|
||||
import type { SecurityTetraplet } from '@fluencelabs/avm';
|
||||
import { JSONValue } from '../util/commonTypes.js';
|
||||
|
||||
/**
|
||||
* JS Service host a low level interface for managing pure javascript services.
|
||||
* It operates on a notion of Call Service Handlers - functions which are called when a `call` air instruction is executed on the local peer.
|
||||
*/
|
||||
export interface IJsServiceHost {
|
||||
/**
|
||||
* Returns true if any handler for the specified serviceId is registered
|
||||
*/
|
||||
hasService(serviceId: string): boolean;
|
||||
|
||||
/**
|
||||
* Find call service handler for specified particle
|
||||
* @param serviceId Service ID as specified in `call` air instruction
|
||||
* @param fnName Function name as specified in `call` air instruction
|
||||
* @param particleId Particle ID
|
||||
*/
|
||||
getHandler(serviceId: string, fnName: string, particleId: string): GenericCallServiceHandler | null;
|
||||
|
||||
/**
|
||||
* Execute service call for specified call service data
|
||||
*/
|
||||
callService(req: CallServiceData): Promise<CallServiceResult | null>;
|
||||
|
||||
/**
|
||||
* Register handler for all particles
|
||||
*/
|
||||
registerGlobalHandler(serviceId: string, fnName: string, handler: GenericCallServiceHandler): void;
|
||||
|
||||
/**
|
||||
* Register handler which will be called only for particle with the specific id
|
||||
*/
|
||||
registerParticleScopeHandler(
|
||||
particleId: string,
|
||||
serviceId: string,
|
||||
fnName: string,
|
||||
handler: GenericCallServiceHandler,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Removes all handlers associated with the specified particle scope
|
||||
* @param particleId Particle ID to remove handlers for
|
||||
*/
|
||||
removeParticleScopeHandlers(particleId: string): void;
|
||||
}
|
||||
|
||||
export enum ResultCodes {
|
||||
success = 0,
|
||||
@ -106,7 +151,3 @@ export interface CallServiceResult {
|
||||
*/
|
||||
result: CallServiceResultType;
|
||||
}
|
||||
|
||||
export type JSONValue = string | number | boolean | null | { [x: string]: JSONValue } | Array<JSONValue>;
|
||||
export type JSONArray = Array<JSONValue>;
|
||||
export type JSONObject = { [x: string]: JSONValue };
|
60
packages/core/js-peer/src/jsServiceHost/serviceUtils.ts
Normal file
60
packages/core/js-peer/src/jsServiceHost/serviceUtils.ts
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2023 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 { FluencePeer } from '../jsPeer/FluencePeer.js';
|
||||
import { IParticle } from '../particle/interfaces.js';
|
||||
import { builtInServices } from '../services/builtins.js';
|
||||
import {
|
||||
CallServiceData,
|
||||
CallServiceResult,
|
||||
CallServiceResultType,
|
||||
ParticleContext,
|
||||
ResultCodes,
|
||||
} from './interfaces.js';
|
||||
|
||||
export const doNothing = (..._args: Array<unknown>) => undefined;
|
||||
|
||||
export const WrapFnIntoServiceCall =
|
||||
(fn: (args: any[]) => CallServiceResultType) =>
|
||||
(req: CallServiceData): CallServiceResult => ({
|
||||
retCode: ResultCodes.success,
|
||||
result: fn(req.args),
|
||||
});
|
||||
|
||||
export class ServiceError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
|
||||
Object.setPrototypeOf(this, ServiceError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export const getParticleContext = (particle: IParticle): ParticleContext => {
|
||||
return {
|
||||
particleId: particle.id,
|
||||
initPeerId: particle.initPeerId,
|
||||
timestamp: particle.timestamp,
|
||||
ttl: particle.ttl,
|
||||
signature: particle.signature,
|
||||
};
|
||||
};
|
||||
|
||||
export function registerDefaultServices(peer: FluencePeer) {
|
||||
Object.entries(builtInServices).forEach(([serviceId, service]) => {
|
||||
Object.entries(service).forEach(([fnName, fn]) => {
|
||||
peer.internals.regHandler.common(serviceId, fnName, fn);
|
||||
});
|
||||
});
|
||||
}
|
@ -3,14 +3,14 @@ import { it, describe, expect, beforeAll } from 'vitest';
|
||||
import * as fs from 'fs';
|
||||
import * as url from 'url';
|
||||
import * as path from 'path';
|
||||
import { compileAqua, withPeer } from '../util.js';
|
||||
import { compileAqua, withPeer } from '../../util/testUtils.js';
|
||||
|
||||
let aqua: any;
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
describe('Marine js tests', () => {
|
||||
beforeAll(async () => {
|
||||
const pathToAquaFiles = path.join(__dirname, '../../../../aqua_test/marine-js.aqua');
|
||||
const pathToAquaFiles = path.join(__dirname, '../../../aqua_test/marine-js.aqua');
|
||||
const { services, functions } = await compileAqua(pathToAquaFiles);
|
||||
aqua = functions;
|
||||
});
|
||||
@ -18,7 +18,7 @@ describe('Marine js tests', () => {
|
||||
it('should call marine service correctly', async () => {
|
||||
await withPeer(async (peer) => {
|
||||
// arrange
|
||||
const wasm = await fs.promises.readFile(path.join(__dirname, '../data/greeting.wasm'));
|
||||
const wasm = await fs.promises.readFile(path.join(__dirname, '../../../data_for_test/greeting.wasm'));
|
||||
await peer.registerMarineService(wasm, 'greeting');
|
||||
|
||||
// act
|
@ -1,10 +1,25 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
// @ts-ignore
|
||||
import { BlobWorker } from 'threads';
|
||||
import { fromBase64, toUint8Array } from 'js-base64';
|
||||
// @ts-ignore
|
||||
import type { WorkerImplementation } from 'threads/dist/types/master';
|
||||
import { LazyLoader } from '../../interfaces/index.js';
|
||||
import { Buffer } from 'buffer';
|
||||
import { LazyLoader } from '../interfaces.js';
|
||||
|
||||
export class InlinedWorkerLoader extends LazyLoader<WorkerImplementation> {
|
||||
constructor(b64script: string) {
|
||||
|
@ -1,5 +1,19 @@
|
||||
/*
|
||||
* Copyright 2023 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 { createRequire } from 'module';
|
||||
import { LazyLoader } from '../../interfaces/index.js';
|
||||
|
||||
// @ts-ignore
|
||||
import type { WorkerImplementation } from 'threads/dist/types/master';
|
||||
@ -8,6 +22,7 @@ import { Worker } from 'threads';
|
||||
import { Buffer } from 'buffer';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { LazyLoader } from '../interfaces.js';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
|
@ -1,5 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Buffer } from 'buffer';
|
||||
import { LazyLoader } from '../../interfaces/index.js';
|
||||
import { LazyLoader } from '../interfaces.js';
|
||||
// @ts-ignore
|
||||
import type { WorkerImplementation } from 'threads/dist/types/master';
|
||||
|
||||
const bufferToSharedArrayBuffer = (buffer: Buffer): SharedArrayBuffer => {
|
||||
const sab = new SharedArrayBuffer(buffer.length);
|
||||
@ -17,7 +34,7 @@ const bufferToSharedArrayBuffer = (buffer: Buffer): SharedArrayBuffer => {
|
||||
* @param filePath - path to the wasm file relative to current origin
|
||||
* @returns Either SharedArrayBuffer or Buffer with the wasm file
|
||||
*/
|
||||
export const loadWasmFromServer = async (filePath: string): Promise<SharedArrayBuffer | Buffer> => {
|
||||
export const loadWasmFromUrl = async (filePath: string): Promise<SharedArrayBuffer | Buffer> => {
|
||||
const fullUrl = window.location.origin + '/' + filePath;
|
||||
const res = await fetch(fullUrl);
|
||||
const ab = await res.arrayBuffer();
|
||||
@ -33,8 +50,14 @@ export const loadWasmFromServer = async (filePath: string): Promise<SharedArrayB
|
||||
return buffer;
|
||||
};
|
||||
|
||||
export class WebLoaderFromUrl extends LazyLoader<SharedArrayBuffer | Buffer> {
|
||||
export class WasmLoaderFromUrl extends LazyLoader<SharedArrayBuffer | Buffer> {
|
||||
constructor(filePath: string) {
|
||||
super(() => loadWasmFromServer(filePath));
|
||||
super(() => loadWasmFromUrl(filePath));
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkerLoaderFromUrl extends LazyLoader<WorkerImplementation> {
|
||||
constructor(scriptPath: string) {
|
||||
super(() => new Worker(scriptPath));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
* Copyright 2023 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.
|
||||
@ -13,28 +13,34 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { PeerIdB58 } from '@fluencelabs/interfaces';
|
||||
import type { JSONArray, JSONObject, LogLevel } from '@fluencelabs/marine-js/dist/types';
|
||||
import type { RunParameters, CallResultsArray, InterpreterResult } from '@fluencelabs/avm';
|
||||
import { CallResultsArray, InterpreterResult, RunParameters } from '@fluencelabs/avm';
|
||||
import { IStartable, JSONArray, JSONObject } from '../util/commonTypes.js';
|
||||
import { Buffer } from 'buffer';
|
||||
// @ts-ignore
|
||||
import type { WorkerImplementation } from 'threads/dist/types/master';
|
||||
|
||||
export type ParticleHandler = (particle: string) => void;
|
||||
|
||||
/**
|
||||
* Base class for connectivity layer to Fluence Network
|
||||
* Contract for marine host implementations. Marine host is responsible for creating calling and removing marine services
|
||||
*/
|
||||
export abstract class FluenceConnection {
|
||||
abstract readonly relayPeerId: PeerIdB58 | null;
|
||||
abstract connect(onIncomingParticle: ParticleHandler): Promise<void>;
|
||||
abstract disconnect(): Promise<void>;
|
||||
abstract sendParticle(nextPeerIds: PeerIdB58[], particle: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IMarine extends IModule {
|
||||
export interface IMarineHost extends IStartable {
|
||||
/**
|
||||
* Creates marine service from the given module and service id
|
||||
*/
|
||||
createService(serviceModule: SharedArrayBuffer | Buffer, serviceId: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Removes marine service with the given service id
|
||||
*/
|
||||
removeService(serviceId: string): void;
|
||||
|
||||
/**
|
||||
* Returns true if any service with the specified service id is registered
|
||||
*/
|
||||
hasService(serviceId: string): boolean;
|
||||
|
||||
/**
|
||||
* Calls the specified function of the specified service with the given arguments
|
||||
*/
|
||||
callService(
|
||||
serviceId: string,
|
||||
functionName: string,
|
||||
@ -43,7 +49,13 @@ export interface IMarine extends IModule {
|
||||
): Promise<unknown>;
|
||||
}
|
||||
|
||||
export interface IAvmRunner extends IModule {
|
||||
/**
|
||||
* Interface for different implementations of AVM runner
|
||||
*/
|
||||
export interface IAvmRunner extends IStartable {
|
||||
/**
|
||||
* Run AVM interpreter with the specified parameters
|
||||
*/
|
||||
run(
|
||||
runParams: RunParameters,
|
||||
air: string,
|
||||
@ -53,20 +65,27 @@ export interface IAvmRunner extends IModule {
|
||||
): Promise<InterpreterResult | Error>;
|
||||
}
|
||||
|
||||
export interface IModule {
|
||||
start(): Promise<void>;
|
||||
stop(): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for something which can hold a value
|
||||
*/
|
||||
export interface IValueLoader<T> {
|
||||
getValue(): T;
|
||||
}
|
||||
|
||||
export interface IWasmLoader extends IValueLoader<SharedArrayBuffer | Buffer>, IModule {}
|
||||
/**
|
||||
* Interface for something which can load wasm files
|
||||
*/
|
||||
export interface IWasmLoader extends IValueLoader<SharedArrayBuffer | Buffer>, IStartable {}
|
||||
|
||||
export interface IWorkerLoader extends IValueLoader<WorkerImplementation>, IModule {}
|
||||
/**
|
||||
* Interface for something which can thread.js based worker
|
||||
*/
|
||||
export interface IWorkerLoader extends IValueLoader<WorkerImplementation>, IStartable {}
|
||||
|
||||
export class LazyLoader<T> implements IModule, IValueLoader<T> {
|
||||
/**
|
||||
* Lazy loader for some value. Value is loaded only when `start` method is called
|
||||
*/
|
||||
export class LazyLoader<T> implements IStartable, IValueLoader<T> {
|
||||
private value: T | null = null;
|
||||
|
||||
constructor(private loadValue: () => Promise<T> | T) {}
|
@ -17,6 +17,7 @@
|
||||
import { MarineService } from '@fluencelabs/marine-js/dist/MarineService';
|
||||
import type { Env, MarineServiceConfig } from '@fluencelabs/marine-js/dist/config';
|
||||
import type { JSONArray, JSONObject, LogMessage } from '@fluencelabs/marine-js/dist/types';
|
||||
import { Buffer } from 'buffer';
|
||||
// @ts-ignore
|
||||
import { Observable, Subject } from 'threads/observable';
|
||||
// @ts-ignore
|
||||
|
@ -1,9 +1,23 @@
|
||||
import { LazyLoader } from '../../interfaces/index.js';
|
||||
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
// @ts-ignore
|
||||
import type { WorkerImplementation } from 'threads/dist/types/master';
|
||||
// @ts-ignore
|
||||
import { Worker } from 'threads';
|
||||
import { LazyLoader } from '../interfaces.js';
|
||||
|
||||
export class WorkerLoader extends LazyLoader<WorkerImplementation> {
|
||||
constructor() {
|
||||
|
@ -14,29 +14,40 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { JSONArray, JSONObject, LogLevel } from '@fluencelabs/marine-js/dist/types';
|
||||
import type { JSONArray, JSONObject } from '@fluencelabs/marine-js/dist/types';
|
||||
import { LogFunction, logLevelToEnv } from '@fluencelabs/marine-js/dist/types';
|
||||
import type { IMarine, IWorkerLoader, IWasmLoader } from '../../interfaces/index.js';
|
||||
import type { MarineBackgroundInterface } from '../worker-script/index.js';
|
||||
// @ts-ignore
|
||||
import { spawn, Thread } from 'threads';
|
||||
// @ts-ignore
|
||||
import type { ModuleThread } from 'threads';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
import { MarineLogger, marineLogger } from '../../util/logger.js';
|
||||
import { IMarineHost, IWasmLoader, IWorkerLoader } from '../interfaces.js';
|
||||
|
||||
export class MarineBackgroundRunner implements IMarine {
|
||||
export class MarineBackgroundRunner implements IMarineHost {
|
||||
private marineServices = new Set<string>();
|
||||
private workerThread?: ModuleThread<MarineBackgroundInterface>;
|
||||
|
||||
private loggers: Map<string, MarineLogger> = new Map();
|
||||
|
||||
constructor(private workerLoader: IWorkerLoader, private controlModuleLoader: IWasmLoader) {}
|
||||
|
||||
hasService(serviceId: string): boolean {
|
||||
return this.marineServices.has(serviceId);
|
||||
}
|
||||
|
||||
removeService(serviceId: string): void {
|
||||
this.marineServices.delete(serviceId);
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
if (this.workerThread) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.marineServices = new Set();
|
||||
await this.workerLoader.start();
|
||||
await this.controlModuleLoader.start();
|
||||
const worker = this.workerLoader.getValue();
|
||||
@ -53,7 +64,7 @@ export class MarineBackgroundRunner implements IMarine {
|
||||
await this.workerThread.init(wasm);
|
||||
}
|
||||
|
||||
createService(serviceModule: SharedArrayBuffer | Buffer, serviceId: string): Promise<void> {
|
||||
async createService(serviceModule: SharedArrayBuffer | Buffer, serviceId: string): Promise<void> {
|
||||
if (!this.workerThread) {
|
||||
throw 'Worker is not initialized';
|
||||
}
|
||||
@ -62,7 +73,8 @@ export class MarineBackgroundRunner implements IMarine {
|
||||
// We enable all possible log levels passing the control for exact printouts to the logger
|
||||
const env = logLevelToEnv('trace');
|
||||
this.loggers.set(serviceId, marineLogger(serviceId));
|
||||
return this.workerThread.createService(serviceModule, serviceId, undefined, env);
|
||||
await this.workerThread.createService(serviceModule, serviceId, undefined, env);
|
||||
this.marineServices.add(serviceId);
|
||||
}
|
||||
|
||||
callService(
|
||||
@ -83,6 +95,7 @@ export class MarineBackgroundRunner implements IMarine {
|
||||
return;
|
||||
}
|
||||
|
||||
this.marineServices.clear();
|
||||
await this.workerThread.terminate();
|
||||
await Thread.terminate(this.workerThread);
|
||||
}
|
||||
|
129
packages/core/js-peer/src/particle/Particle.ts
Normal file
129
packages/core/js-peer/src/particle/Particle.ts
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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 { fromUint8Array, toUint8Array } from 'js-base64';
|
||||
import { CallResultsArray } from '@fluencelabs/avm';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Buffer } from 'buffer';
|
||||
import { IParticle } from './interfaces.js';
|
||||
|
||||
export class Particle implements IParticle {
|
||||
readonly signature: undefined;
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
public readonly timestamp: number,
|
||||
public readonly script: string,
|
||||
public readonly data: Uint8Array,
|
||||
public readonly ttl: number,
|
||||
public readonly initPeerId: string,
|
||||
) {
|
||||
this.signature = undefined;
|
||||
}
|
||||
|
||||
static createNew(script: string, initPeerId: string, ttl: number): Particle {
|
||||
return new Particle(uuidv4(), Date.now(), script, Buffer.from([]), ttl, initPeerId);
|
||||
}
|
||||
|
||||
static fromString(str: string): Particle {
|
||||
const json = JSON.parse(str);
|
||||
const res = new Particle(
|
||||
json.id,
|
||||
json.timestamp,
|
||||
json.script,
|
||||
toUint8Array(json.data),
|
||||
json.ttl,
|
||||
json.init_peer_id,
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns actual ttl of a particle, i.e. ttl - time passed since particle creation
|
||||
*/
|
||||
export const getActualTTL = (particle: IParticle): number => {
|
||||
return particle.timestamp + particle.ttl - Date.now();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if particle has expired
|
||||
*/
|
||||
export const hasExpired = (particle: IParticle): boolean => {
|
||||
return getActualTTL(particle) <= 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a particle clone with new data
|
||||
*/
|
||||
export const cloneWithNewData = (particle: IParticle, newData: Uint8Array): IParticle => {
|
||||
return new Particle(particle.id, particle.timestamp, particle.script, newData, particle.ttl, particle.initPeerId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a deep copy of a particle
|
||||
*/
|
||||
export const fullClone = (particle: IParticle): IParticle => {
|
||||
return JSON.parse(JSON.stringify(particle));
|
||||
};
|
||||
|
||||
/**
|
||||
* Serializes particle into string suitable for sending through network
|
||||
*/
|
||||
export const serializeToString = (particle: IParticle): string => {
|
||||
return JSON.stringify({
|
||||
action: 'Particle',
|
||||
id: particle.id,
|
||||
init_peer_id: particle.initPeerId,
|
||||
timestamp: particle.timestamp,
|
||||
ttl: particle.ttl,
|
||||
script: particle.script,
|
||||
// TODO: copy signature from a particle after signatures will be implemented on nodes
|
||||
signature: [],
|
||||
data: particle.data && fromUint8Array(particle.data),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* When particle is executed, it goes through different stages. The type describes all possible stages and their parameters
|
||||
*/
|
||||
export type ParticleExecutionStage =
|
||||
| { stage: 'received' }
|
||||
| { stage: 'interpreted' }
|
||||
| { stage: 'interpreterError'; errorMessage: string }
|
||||
| { stage: 'localWorkDone' }
|
||||
| { stage: 'sent' }
|
||||
| { stage: 'sendingError'; errorMessage: string }
|
||||
| { stage: 'expired' };
|
||||
|
||||
/**
|
||||
* Particle queue item is a wrapper around particle, which contains additional information about particle execution
|
||||
*/
|
||||
export interface ParticleQueueItem {
|
||||
particle: IParticle;
|
||||
callResults: CallResultsArray;
|
||||
onStageChange: (state: ParticleExecutionStage) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to handle particle at expired stage
|
||||
*/
|
||||
export const handleTimeout = (fn: () => void) => (stage: ParticleExecutionStage) => {
|
||||
if (stage.stage === 'expired') {
|
||||
fn();
|
||||
}
|
||||
};
|
59
packages/core/js-peer/src/particle/interfaces.ts
Normal file
59
packages/core/js-peer/src/particle/interfaces.ts
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2023 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 { PeerIdB58 } from '@fluencelabs/interfaces';
|
||||
|
||||
/**
|
||||
* Immutable part of the particle.
|
||||
*/
|
||||
export interface IImmutableParticlePart {
|
||||
/**
|
||||
* Particle id
|
||||
*/
|
||||
readonly id: string;
|
||||
|
||||
/**
|
||||
* Particle timestamp. Specifies when the particle was created.
|
||||
*/
|
||||
readonly timestamp: number;
|
||||
|
||||
/**
|
||||
* Particle's air script
|
||||
*/
|
||||
readonly script: string;
|
||||
|
||||
/**
|
||||
* Particle's ttl. Specifies how long the particle is valid in milliseconds.
|
||||
*/
|
||||
readonly ttl: number;
|
||||
|
||||
/**
|
||||
* Peer id where the particle was initiated.
|
||||
*/
|
||||
readonly initPeerId: PeerIdB58;
|
||||
|
||||
// TODO: implement particle signatures
|
||||
readonly signature: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Particle is a data structure that is used to transfer data between peers in Fluence network.
|
||||
*/
|
||||
export interface IParticle extends IImmutableParticlePart {
|
||||
/**
|
||||
* Mutable particle data
|
||||
*/
|
||||
data: Uint8Array;
|
||||
}
|
61
packages/core/js-peer/src/services/NodeUtils.ts
Normal file
61
packages/core/js-peer/src/services/NodeUtils.ts
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2023 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 { CallParams, IFluenceInternalApi } from '@fluencelabs/interfaces';
|
||||
import { defaultGuard } from './SingleModuleSrv.js';
|
||||
import { NodeUtilsDef, registerNodeUtils } from './_aqua/node-utils.js';
|
||||
import { SecurityGuard } from './securityGuard.js';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { FluencePeer } from '../jsPeer/FluencePeer.js';
|
||||
|
||||
export class NodeUtils implements NodeUtilsDef {
|
||||
constructor(private peer: FluencePeer) {
|
||||
this.securityGuard_readFile = defaultGuard(this.peer);
|
||||
}
|
||||
|
||||
securityGuard_readFile: SecurityGuard<'path'>;
|
||||
|
||||
async read_file(path: string, callParams: CallParams<'path'>) {
|
||||
if (!this.securityGuard_readFile(callParams)) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Security guard validation failed',
|
||||
content: null,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// Strange enough, but Buffer type works here, while reading with encoding 'utf-8' doesn't
|
||||
const data: any = await readFile(path);
|
||||
return {
|
||||
success: true,
|
||||
content: data,
|
||||
error: null,
|
||||
};
|
||||
} catch (err: any) {
|
||||
return {
|
||||
success: false,
|
||||
error: err.message,
|
||||
content: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HACK:: security guard functions must be ported to user API
|
||||
export const doRegisterNodeUtils = (peer: any) => {
|
||||
registerNodeUtils(peer, 'node_utils', new NodeUtils(peer));
|
||||
};
|
@ -1,6 +1,23 @@
|
||||
/*
|
||||
* Copyright 2023 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 { CallParams, PeerIdB58 } from '@fluencelabs/interfaces';
|
||||
import { KeyPair } from '../../keypair/index.js';
|
||||
import { SigDef } from '../_aqua/services.js';
|
||||
import { KeyPair } from '../keypair/index.js';
|
||||
import { FluencePeer } from '../jsPeer/FluencePeer.js';
|
||||
import { SigDef } from './_aqua/services.js';
|
||||
import { allowOnlyParticleOriginatedAt, allowServiceFn, and, or, SecurityGuard } from './securityGuard.js';
|
||||
|
||||
export const defaultSigGuard = (peerId: PeerIdB58) => {
|
||||
@ -18,11 +35,7 @@ export const defaultSigGuard = (peerId: PeerIdB58) => {
|
||||
};
|
||||
|
||||
export class Sig implements SigDef {
|
||||
private _keyPair: KeyPair;
|
||||
|
||||
constructor(keyPair: KeyPair) {
|
||||
this._keyPair = keyPair;
|
||||
}
|
||||
constructor(private keyPair: KeyPair) {}
|
||||
|
||||
/**
|
||||
* Configurable security guard for sign method
|
||||
@ -35,7 +48,7 @@ export class Sig implements SigDef {
|
||||
* Gets the public key of KeyPair. Required by aqua
|
||||
*/
|
||||
get_peer_id() {
|
||||
return this._keyPair.getPeerId();
|
||||
return this.keyPair.getPeerId();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -53,7 +66,7 @@ export class Sig implements SigDef {
|
||||
};
|
||||
}
|
||||
|
||||
const signedData = await this._keyPair.signBytes(Uint8Array.from(data));
|
||||
const signedData = await this.keyPair.signBytes(Uint8Array.from(data));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@ -66,6 +79,10 @@ export class Sig implements SigDef {
|
||||
* Verifies the signature. Required by aqua
|
||||
*/
|
||||
verify(signature: number[], data: number[]): Promise<boolean> {
|
||||
return this._keyPair.verify(Uint8Array.from(data), Uint8Array.from(signature));
|
||||
return this.keyPair.verify(Uint8Array.from(data), Uint8Array.from(signature));
|
||||
}
|
||||
}
|
||||
|
||||
export const getDefaultSig = (peer: FluencePeer) => {
|
||||
peer.registerMarineService;
|
||||
};
|
@ -1,13 +1,28 @@
|
||||
/*
|
||||
* Copyright 2023 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 { v4 as uuidv4 } from 'uuid';
|
||||
import { SrvDef } from '../_aqua/single-module-srv.js';
|
||||
import { NodeUtilsDef } from '../_aqua/node-utils.js';
|
||||
import { FluencePeer } from '../FluencePeer.js';
|
||||
import { SrvDef } from './_aqua/single-module-srv.js';
|
||||
import { FluencePeer } from '../jsPeer/FluencePeer.js';
|
||||
import { CallParams } from '@fluencelabs/interfaces';
|
||||
import { Buffer } from 'buffer';
|
||||
import { allowOnlyParticleOriginatedAt, SecurityGuard } from './securityGuard.js';
|
||||
|
||||
export const defaultGuard = (peer: FluencePeer) => {
|
||||
return allowOnlyParticleOriginatedAt<any>(peer.getStatus().peerId!);
|
||||
return allowOnlyParticleOriginatedAt<any>(peer.keyPair.getPeerId());
|
||||
};
|
||||
|
||||
export class Srv implements SrvDef {
|
||||
@ -32,10 +47,11 @@ export class Srv implements SrvDef {
|
||||
try {
|
||||
const newServiceId = uuidv4();
|
||||
const buffer = Buffer.from(wasm_b64_content, 'base64');
|
||||
const sab = new SharedArrayBuffer(buffer.length);
|
||||
const tmp = new Uint8Array(sab);
|
||||
tmp.set(buffer, 0);
|
||||
await this.peer.registerMarineService(sab, newServiceId);
|
||||
// TODO:: figure out why SharedArrayBuffer is not working here
|
||||
// const sab = new SharedArrayBuffer(buffer.length);
|
||||
// const tmp = new Uint8Array(sab);
|
||||
// tmp.set(buffer, 0);
|
||||
await this.peer.registerMarineService(buffer, newServiceId);
|
||||
this.services.add(newServiceId);
|
||||
|
||||
return {
|
||||
@ -83,49 +99,3 @@ export class Srv implements SrvDef {
|
||||
return Array.from(this.services.values());
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeUtils implements NodeUtilsDef {
|
||||
constructor(private peer: FluencePeer) {
|
||||
this.securityGuard_readFile = defaultGuard(this.peer);
|
||||
}
|
||||
|
||||
securityGuard_readFile: SecurityGuard<'path'>;
|
||||
|
||||
async read_file(path: string, callParams: CallParams<'path'>) {
|
||||
// TODO: split node-only and universal services into different client packages
|
||||
// if (!isNode) {
|
||||
// return {
|
||||
// success: false,
|
||||
// error: 'read_file is only supported in node.js',
|
||||
// content: null,
|
||||
// };
|
||||
// }
|
||||
|
||||
if (!this.securityGuard_readFile(callParams)) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Security guard validation failed',
|
||||
content: null,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// eval('require') is needed so that
|
||||
// webpack will complain about missing dependencies for web target
|
||||
const r = eval('require');
|
||||
const fs = r('fs').promises;
|
||||
const data = await fs.readFile(path);
|
||||
return {
|
||||
success: true,
|
||||
content: data,
|
||||
error: null,
|
||||
};
|
||||
} catch (err: any) {
|
||||
return {
|
||||
success: false,
|
||||
error: err.message,
|
||||
content: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -2,11 +2,11 @@ import { it, describe, expect, test } from 'vitest';
|
||||
|
||||
import { CallParams } from '@fluencelabs/interfaces';
|
||||
import { toUint8Array } from 'js-base64';
|
||||
import { CallServiceData } from '../../../interfaces/commonTypes.js';
|
||||
import { KeyPair } from '../../../keypair/index.js';
|
||||
import { Sig, defaultSigGuard } from '../../builtins/Sig.js';
|
||||
import { allowServiceFn } from '../../builtins/securityGuard.js';
|
||||
import { builtInServices } from '../../builtins/common.js';
|
||||
import { KeyPair } from '../../keypair/index.js';
|
||||
import { Sig, defaultSigGuard } from '../Sig.js';
|
||||
import { allowServiceFn } from '../securityGuard.js';
|
||||
import { builtInServices } from '../builtins.js';
|
||||
import { CallServiceData } from '../../jsServiceHost/interfaces.js';
|
||||
|
||||
const a10b20 = `{
|
||||
"a": 10,
|
@ -1,9 +1,9 @@
|
||||
import { it, describe, expect, beforeEach, afterEach } from 'vitest';
|
||||
|
||||
import { Particle } from '../../Particle.js';
|
||||
import { doNothing } from '../../utils.js';
|
||||
import { FluencePeer } from '../../FluencePeer.js';
|
||||
import { mkTestPeer } from '../util.js';
|
||||
import { Particle } from '../../particle/Particle.js';
|
||||
import { FluencePeer } from '../../jsPeer/FluencePeer.js';
|
||||
import { mkTestPeer } from '../../util/testUtils.js';
|
||||
import { doNothing } from '../../jsServiceHost/serviceUtils.js';
|
||||
|
||||
let peer: FluencePeer;
|
||||
|
||||
@ -15,7 +15,7 @@ describe('Sig service test suite', () => {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
peer = mkTestPeer();
|
||||
peer = await mkTestPeer();
|
||||
await peer.start();
|
||||
});
|
||||
|
||||
@ -56,7 +56,7 @@ describe('Sig service test suite', () => {
|
||||
};
|
||||
});
|
||||
});
|
||||
const p = peer.internals.createNewParticle(script) as Particle;
|
||||
const p = peer.internals.createNewParticle(script);
|
||||
await peer.internals.initiateParticle(p, doNothing);
|
||||
|
||||
const [nestedFirst, nestedSecond, outerFirst, outerSecond, outerFirstString, outerFirstParsed] = await promise;
|
@ -2,11 +2,11 @@ import { it, describe, expect, beforeAll } from 'vitest';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
import { KeyPair } from '../../../keypair/index.js';
|
||||
import { allowServiceFn } from '../../builtins/securityGuard.js';
|
||||
import { Sig } from '../../builtins/Sig.js';
|
||||
import { compileAqua, withPeer } from '../util.js';
|
||||
import { registerService } from '../../../compilerSupport/registerService.js';
|
||||
import { KeyPair } from '../../keypair/index.js';
|
||||
import { allowServiceFn } from '../securityGuard.js';
|
||||
import { Sig } from '../Sig.js';
|
||||
import { registerService } from '../../compilerSupport/registerService.js';
|
||||
import { compileAqua, withPeer } from '../../util/testUtils.js';
|
||||
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
@ -16,7 +16,7 @@ let dataProviderDef: any;
|
||||
|
||||
describe('Sig service test suite', () => {
|
||||
beforeAll(async () => {
|
||||
const pathToAquaFiles = path.join(__dirname, '../../../../aqua_test/sigService.aqua');
|
||||
const pathToAquaFiles = path.join(__dirname, '../../../aqua_test/sigService.aqua');
|
||||
const { services, functions } = await compileAqua(pathToAquaFiles);
|
||||
|
||||
aqua = functions;
|
||||
@ -75,12 +75,13 @@ describe('Sig service test suite', () => {
|
||||
customSig.securityGuard = allowServiceFn('wrong', 'wrong');
|
||||
|
||||
const result = await aqua.callSig(peer, { sigId: 'CustomSig' });
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('Default sig service should be resolvable by peer id', async () => {
|
||||
await withPeer(async (peer) => {
|
||||
const sig = peer.getServices().sig;
|
||||
const sig = peer.internals.getServices().sig;
|
||||
|
||||
const data = [1, 2, 3, 4, 5];
|
||||
registerService({
|
||||
@ -95,7 +96,7 @@ describe('Sig service test suite', () => {
|
||||
});
|
||||
|
||||
const callAsSigRes = await aqua.callSig(peer, { sigId: 'sig' });
|
||||
const callAsPeerIdRes = await aqua.callSig(peer, { sigId: peer.getStatus().peerId });
|
||||
const callAsPeerIdRes = await aqua.callSig(peer, { sigId: peer.keyPair.getPeerId() });
|
||||
|
||||
expect(callAsSigRes.success).toBe(false);
|
||||
expect(callAsPeerIdRes.success).toBe(false);
|
||||
@ -104,7 +105,7 @@ describe('Sig service test suite', () => {
|
||||
|
||||
const callAsSigResAfterGuardChange = await aqua.callSig(peer, { sigId: 'sig' });
|
||||
const callAsPeerIdResAfterGuardChange = await aqua.callSig(peer, {
|
||||
sigId: peer.getStatus().peerId,
|
||||
sigId: peer.keyPair.getPeerId(),
|
||||
});
|
||||
|
||||
expect(callAsSigResAfterGuardChange.success).toBe(true);
|
@ -1,14 +1,16 @@
|
||||
import { it, describe, expect, beforeAll } from 'vitest';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
import { compileAqua, withPeer } from '../util.js';
|
||||
import { compileAqua, withPeer } from '../../util/testUtils.js';
|
||||
import { registerNodeUtils } from '../_aqua/node-utils.js';
|
||||
import { NodeUtils } from '../NodeUtils.js';
|
||||
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
||||
let aqua: any;
|
||||
|
||||
describe('Srv service test suite', () => {
|
||||
beforeAll(async () => {
|
||||
const pathToAquaFiles = path.join(__dirname, '../../../../aqua_test/srv.aqua');
|
||||
const pathToAquaFiles = path.join(__dirname, '../../../aqua_test/srv.aqua');
|
||||
const { services, functions } = await compileAqua(pathToAquaFiles);
|
||||
aqua = functions;
|
||||
});
|
||||
@ -16,7 +18,8 @@ describe('Srv service test suite', () => {
|
||||
it('Use custom srv service, success path', async () => {
|
||||
await withPeer(async (peer) => {
|
||||
// arrange
|
||||
const wasm = path.join(__dirname, '../data/greeting.wasm');
|
||||
registerNodeUtils(peer, 'node_utils', new NodeUtils(peer));
|
||||
const wasm = path.join(__dirname, '../../../data_for_test/greeting.wasm');
|
||||
|
||||
// act
|
||||
const res = await aqua.happy_path(peer, { file_path: wasm });
|
||||
@ -29,7 +32,8 @@ describe('Srv service test suite', () => {
|
||||
it('List deployed services', async () => {
|
||||
await withPeer(async (peer) => {
|
||||
// arrange
|
||||
const wasm = path.join(__dirname, '../data/greeting.wasm');
|
||||
registerNodeUtils(peer, 'node_utils', new NodeUtils(peer));
|
||||
const wasm = path.join(__dirname, '../../../data_for_test/greeting.wasm');
|
||||
|
||||
// act
|
||||
const res = await aqua.list_services(peer, { file_path: wasm });
|
||||
@ -42,19 +46,21 @@ describe('Srv service test suite', () => {
|
||||
it('Correct error for removed services', async () => {
|
||||
await withPeer(async (peer) => {
|
||||
// arrange
|
||||
const wasm = path.join(__dirname, '../data/greeting.wasm');
|
||||
registerNodeUtils(peer, 'node_utils', new NodeUtils(peer));
|
||||
const wasm = path.join(__dirname, '../../../data_for_test/greeting.wasm');
|
||||
|
||||
// act
|
||||
const res = await aqua.service_removed(peer, { file_path: wasm });
|
||||
|
||||
// assert
|
||||
expect(res).toMatch('No handler has been registered for serviceId');
|
||||
expect(res).toMatch('No service found for service call');
|
||||
});
|
||||
});
|
||||
|
||||
it('Correct error for file not found', async () => {
|
||||
await withPeer(async (peer) => {
|
||||
// arrange
|
||||
registerNodeUtils(peer, 'node_utils', new NodeUtils(peer));
|
||||
|
||||
// act
|
||||
const res = await aqua.file_not_found(peer, {});
|
||||
@ -67,6 +73,7 @@ describe('Srv service test suite', () => {
|
||||
it('Correct error for removing non existing service', async () => {
|
||||
await withPeer(async (peer) => {
|
||||
// arrange
|
||||
registerNodeUtils(peer, 'node_utils', new NodeUtils(peer));
|
||||
|
||||
// act
|
||||
const res = await aqua.removing_non_exiting(peer, {});
|
@ -6,9 +6,8 @@
|
||||
* Aqua version: 0.7.7-362
|
||||
*
|
||||
*/
|
||||
import { CallParams } from '@fluencelabs/interfaces';
|
||||
import { registerServiceImpl } from './util.js';
|
||||
import { FluencePeer } from '../FluencePeer.js';
|
||||
import { CallParams, IFluenceInternalApi } from '@fluencelabs/interfaces';
|
||||
import { registerService } from '../../compilerSupport/registerService.js';
|
||||
|
||||
// Services
|
||||
|
||||
@ -21,10 +20,12 @@ export interface NodeUtilsDef {
|
||||
| Promise<{ content: string | null; error: string | null; success: boolean }>;
|
||||
}
|
||||
|
||||
export function registerNodeUtils(peer: FluencePeer, serviceId: string, service: any) {
|
||||
registerServiceImpl(
|
||||
peer,
|
||||
{
|
||||
export function registerNodeUtils(peer: IFluenceInternalApi, serviceId: string, service: any) {
|
||||
registerService({
|
||||
peer: peer,
|
||||
service: service,
|
||||
serviceId: serviceId,
|
||||
def: {
|
||||
defaultServiceId: 'node_utils',
|
||||
functions: {
|
||||
tag: 'labeledProduct',
|
||||
@ -73,9 +74,7 @@ export function registerNodeUtils(peer: FluencePeer, serviceId: string, service:
|
||||
},
|
||||
},
|
||||
},
|
||||
serviceId,
|
||||
service,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Functions
|
@ -6,9 +6,8 @@
|
||||
* Aqua version: 0.7.7-362
|
||||
*
|
||||
*/
|
||||
import { CallParams } from '@fluencelabs/interfaces';
|
||||
import { registerServiceImpl } from './util.js';
|
||||
import { FluencePeer } from '../FluencePeer.js';
|
||||
import { CallParams, IFluenceInternalApi } from '@fluencelabs/interfaces';
|
||||
import { registerService } from '../../compilerSupport/registerService.js';
|
||||
|
||||
// Services
|
||||
|
||||
@ -27,10 +26,12 @@ export interface SigDef {
|
||||
) => boolean | Promise<boolean>;
|
||||
}
|
||||
|
||||
export function registerSig(peer: FluencePeer, serviceId: string, service: any) {
|
||||
registerServiceImpl(
|
||||
peer,
|
||||
{
|
||||
export function registerSig(peer: IFluenceInternalApi, serviceId: string, service: any) {
|
||||
registerService({
|
||||
peer: peer as any,
|
||||
service: service,
|
||||
serviceId: serviceId,
|
||||
def: {
|
||||
defaultServiceId: 'sig',
|
||||
functions: {
|
||||
tag: 'labeledProduct',
|
||||
@ -131,9 +132,7 @@ export function registerSig(peer: FluencePeer, serviceId: string, service: any)
|
||||
},
|
||||
},
|
||||
},
|
||||
serviceId,
|
||||
service,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Functions
|
@ -6,9 +6,8 @@
|
||||
* Aqua version: 0.7.7-362
|
||||
*
|
||||
*/
|
||||
import { CallParams } from '@fluencelabs/interfaces';
|
||||
import { registerServiceImpl } from './util.js';
|
||||
import { FluencePeer } from '../FluencePeer.js';
|
||||
import { CallParams, IFluenceInternalApi } from '@fluencelabs/interfaces';
|
||||
import { registerService } from '../../compilerSupport/registerService.js';
|
||||
|
||||
// Services
|
||||
|
||||
@ -26,10 +25,12 @@ export interface SrvDef {
|
||||
) => { error: string | null; success: boolean } | Promise<{ error: string | null; success: boolean }>;
|
||||
}
|
||||
|
||||
export function registerSrv(peer: FluencePeer, serviceId: string, service: any) {
|
||||
registerServiceImpl(
|
||||
peer,
|
||||
{
|
||||
export function registerSrv(peer: IFluenceInternalApi, serviceId: string, service: any) {
|
||||
registerService({
|
||||
peer: peer as any,
|
||||
serviceId,
|
||||
service,
|
||||
def: {
|
||||
defaultServiceId: 'single_module_srv',
|
||||
functions: {
|
||||
tag: 'labeledProduct',
|
||||
@ -130,9 +131,7 @@ export function registerSrv(peer: FluencePeer, serviceId: string, service: any)
|
||||
},
|
||||
},
|
||||
},
|
||||
serviceId,
|
||||
service,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Functions
|
@ -19,9 +19,9 @@ import * as bs58 from 'bs58';
|
||||
import { sha256 } from 'multiformats/hashes/sha2';
|
||||
import { CallServiceResult } from '@fluencelabs/avm';
|
||||
|
||||
import { GenericCallServiceHandler, ResultCodes } from '../../interfaces/commonTypes.js';
|
||||
import { jsonify } from '../utils.js';
|
||||
import { isString, jsonify } from '../util/utils.js';
|
||||
import { Buffer } from 'buffer';
|
||||
import { GenericCallServiceHandler, ResultCodes } from '../jsServiceHost/interfaces.js';
|
||||
|
||||
//@ts-ignore
|
||||
const { encode, decode } = bs58.default;
|
||||
@ -595,11 +595,3 @@ const checkForArgumentType = (req: { args: Array<unknown> }, index: number, type
|
||||
return error(`Argument ${index} expected to be of type ${type}, Got ${actual}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const isString = (unknown: unknown): unknown is string => {
|
||||
return unknown !== null && typeof unknown === 'string';
|
||||
};
|
||||
|
||||
export const isObject = (unknown: unknown): unknown is object => {
|
||||
return unknown !== null && typeof unknown === 'object';
|
||||
};
|
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright 2023 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 { SecurityTetraplet } from '@fluencelabs/avm';
|
||||
import { CallParams, PeerIdB58 } from '@fluencelabs/interfaces';
|
||||
|
24
packages/core/js-peer/src/util/commonTypes.ts
Normal file
24
packages/core/js-peer/src/util/commonTypes.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2023 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 interface IStartable {
|
||||
start(): Promise<void>;
|
||||
stop(): Promise<void>;
|
||||
}
|
||||
|
||||
export type JSONValue = string | number | boolean | null | { [x: string]: JSONValue } | Array<JSONValue>;
|
||||
export type JSONArray = Array<JSONValue>;
|
||||
export type JSONObject = { [x: string]: JSONValue };
|
35
packages/core/js-peer/src/util/libp2pUtils.ts
Normal file
35
packages/core/js-peer/src/util/libp2pUtils.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2023 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 { RelayOptions } from '@fluencelabs/interfaces';
|
||||
import { multiaddr, Multiaddr } from '@multiformats/multiaddr';
|
||||
import { isString } from './utils.js';
|
||||
|
||||
export function relayOptionToMultiaddr(relay: RelayOptions): Multiaddr {
|
||||
const multiaddrString = isString(relay) ? relay : relay.multiaddr;
|
||||
const ma = multiaddr(multiaddrString);
|
||||
|
||||
throwIfHasNoPeerId(ma);
|
||||
|
||||
return ma;
|
||||
}
|
||||
|
||||
export function throwIfHasNoPeerId(ma: Multiaddr): void {
|
||||
const peerId = ma.getPeerId();
|
||||
if (!peerId) {
|
||||
throw new Error('Specified multiaddr is invalid or missing peer id: ' + ma.toString());
|
||||
}
|
||||
}
|
@ -1,5 +1,21 @@
|
||||
/*
|
||||
* Copyright 2023 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 debug from 'debug';
|
||||
import { Particle } from '../js-peer/Particle.js';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
// Format avm data as a string
|
||||
debug.formatters.a = (avmData: Uint8Array) => {
|
||||
|
143
packages/core/js-peer/src/util/testUtils.ts
Normal file
143
packages/core/js-peer/src/util/testUtils.ts
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2023 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 * as api from '@fluencelabs/aqua-api/aqua-api.js';
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import { DEFAULT_CONFIG, FluencePeer, PeerConfig } from '../jsPeer/FluencePeer.js';
|
||||
import { Particle } from '../particle/Particle.js';
|
||||
import { ClientConfig, IFluenceClient, RelayOptions, ServiceDef } from '@fluencelabs/interfaces';
|
||||
import { callAquaFunction } from '../compilerSupport/callFunction.js';
|
||||
|
||||
import { MarineBackgroundRunner } from '../marine/worker/index.js';
|
||||
import { MarineBasedAvmRunner } from '../jsPeer/avm.js';
|
||||
import { WorkerLoader } from '../marine/worker-script/workerLoader.js';
|
||||
import { KeyPair } from '../keypair/index.js';
|
||||
import { Subject, Subscribable } from 'rxjs';
|
||||
import { WrapFnIntoServiceCall } from '../jsServiceHost/serviceUtils.js';
|
||||
import { JsServiceHost } from '../jsServiceHost/JsServiceHost.js';
|
||||
import { ClientPeer, makeClientPeerConfig } from '../clientPeer/ClientPeer.js';
|
||||
import { WasmLoaderFromNpm } from '../marine/deps-loader/node.js';
|
||||
import { IConnection } from '../connection/interfaces.js';
|
||||
|
||||
export const registerHandlersHelper = (
|
||||
peer: FluencePeer,
|
||||
particle: Particle,
|
||||
handlers: Record<string, Record<string, any>>,
|
||||
) => {
|
||||
Object.entries(handlers).forEach(([serviceId, service]) => {
|
||||
Object.entries(service).forEach(([fnName, fn]) => {
|
||||
peer.internals.regHandler.forParticle(particle.id, serviceId, fnName, WrapFnIntoServiceCall(fn));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export type CompiledFnCall = (peer: IFluenceClient, args: { [key: string]: any }) => Promise<unknown>;
|
||||
export type CompiledFile = {
|
||||
functions: { [key: string]: CompiledFnCall };
|
||||
services: { [key: string]: ServiceDef };
|
||||
};
|
||||
|
||||
export const compileAqua = async (aquaFile: string): Promise<CompiledFile> => {
|
||||
await fs.access(aquaFile);
|
||||
|
||||
const compilationResult = await api.Aqua.compile(new api.Path(aquaFile), [], undefined);
|
||||
|
||||
if (compilationResult.errors.length > 0) {
|
||||
throw new Error('Aqua compilation failed. Error: ' + compilationResult.errors.join('/n'));
|
||||
}
|
||||
|
||||
const functions = Object.entries(compilationResult.functions)
|
||||
.map(([name, fnInfo]) => {
|
||||
const callFn = (peer: IFluenceClient, args: { [key: string]: any }) => {
|
||||
return callAquaFunction({
|
||||
def: fnInfo.funcDef,
|
||||
script: fnInfo.script,
|
||||
config: {},
|
||||
peer: peer,
|
||||
args,
|
||||
});
|
||||
};
|
||||
return { [name]: callFn };
|
||||
})
|
||||
.reduce((agg, obj) => {
|
||||
return { ...agg, ...obj };
|
||||
}, {});
|
||||
|
||||
return { functions, services: compilationResult.services };
|
||||
};
|
||||
|
||||
class NoopConnection implements IConnection {
|
||||
getRelayPeerId(): string {
|
||||
return 'nothing_here';
|
||||
}
|
||||
supportsRelay(): boolean {
|
||||
return true;
|
||||
}
|
||||
particleSource: Subscribable<Particle> = new Subject<Particle>();
|
||||
|
||||
sendParticle(nextPeerIds: string[], particle: Particle): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export class TestPeer extends FluencePeer {
|
||||
constructor(keyPair: KeyPair, connection: IConnection) {
|
||||
const workerLoader = new WorkerLoader();
|
||||
const controlModuleLoader = new WasmLoaderFromNpm('@fluencelabs/marine-js', 'marine-js.wasm');
|
||||
const avmModuleLoader = new WasmLoaderFromNpm('@fluencelabs/avm', 'avm.wasm');
|
||||
const marine = new MarineBackgroundRunner(workerLoader, controlModuleLoader);
|
||||
const jsHost = new JsServiceHost();
|
||||
const avm = new MarineBasedAvmRunner(marine, avmModuleLoader);
|
||||
super(DEFAULT_CONFIG, keyPair, marine, jsHost, avm, connection);
|
||||
}
|
||||
}
|
||||
|
||||
export const mkTestPeer = async () => {
|
||||
const kp = await KeyPair.randomEd25519();
|
||||
const conn = new NoopConnection();
|
||||
return new TestPeer(kp, conn);
|
||||
};
|
||||
|
||||
export const withPeer = async (action: (p: FluencePeer) => Promise<void>) => {
|
||||
const p = await mkTestPeer();
|
||||
try {
|
||||
await p.start();
|
||||
await action(p);
|
||||
} finally {
|
||||
await p.stop();
|
||||
}
|
||||
};
|
||||
|
||||
export const withClient = async (
|
||||
relay: RelayOptions,
|
||||
config: ClientConfig,
|
||||
action: (client: ClientPeer) => Promise<void>,
|
||||
) => {
|
||||
const workerLoader = new WorkerLoader();
|
||||
const controlModuleLoader = new WasmLoaderFromNpm('@fluencelabs/marine-js', 'marine-js.wasm');
|
||||
const avmModuleLoader = new WasmLoaderFromNpm('@fluencelabs/avm', 'avm.wasm');
|
||||
const marine = new MarineBackgroundRunner(workerLoader, controlModuleLoader);
|
||||
const avm = new MarineBasedAvmRunner(marine, avmModuleLoader);
|
||||
const { keyPair, peerConfig, relayConfig } = await makeClientPeerConfig(relay, config);
|
||||
const client = new ClientPeer(peerConfig, relayConfig, keyPair, marine, avm);
|
||||
try {
|
||||
await client.connect();
|
||||
await action(client);
|
||||
} finally {
|
||||
await client.disconnect();
|
||||
}
|
||||
};
|
27
packages/core/js-peer/src/util/utils.ts
Normal file
27
packages/core/js-peer/src/util/utils.ts
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2021 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 function jsonify(obj: unknown) {
|
||||
return JSON.stringify(obj, null, 4);
|
||||
}
|
||||
|
||||
export const isString = (unknown: unknown): unknown is string => {
|
||||
return unknown !== null && typeof unknown === 'string';
|
||||
};
|
||||
|
||||
export const isObject = (unknown: unknown): unknown is object => {
|
||||
return unknown !== null && typeof unknown === 'object';
|
||||
};
|
338
pnpm-lock.yaml
generated
338
pnpm-lock.yaml
generated
@ -35,7 +35,7 @@ importers:
|
||||
base64-js: 1.5.1
|
||||
devDependencies:
|
||||
'@fluencelabs/aqua-lib': 0.6.0
|
||||
'@fluencelabs/cli': 0.3.9_zefx7wxidm6hdha6gwewr46oqa
|
||||
'@fluencelabs/cli': 0.3.9_mhciodunqktqftpx562wx5v3rq
|
||||
'@fluencelabs/registry': 0.8.2
|
||||
'@fluencelabs/trust-graph': 3.1.2
|
||||
|
||||
@ -123,21 +123,41 @@ importers:
|
||||
packages/client/js-client.node:
|
||||
specifiers:
|
||||
'@fluencelabs/avm': 0.35.4
|
||||
'@fluencelabs/interfaces': 0.7.4
|
||||
'@fluencelabs/js-peer': 0.8.6
|
||||
'@fluencelabs/marine-js': 0.3.45
|
||||
'@types/platform': 1.3.4
|
||||
platform: 1.3.6
|
||||
dependencies:
|
||||
'@fluencelabs/avm': 0.35.4
|
||||
'@fluencelabs/interfaces': link:../../core/interfaces
|
||||
'@fluencelabs/js-peer': link:../../core/js-peer
|
||||
'@fluencelabs/marine-js': 0.3.45
|
||||
platform: 1.3.6
|
||||
devDependencies:
|
||||
'@types/platform': 1.3.4
|
||||
|
||||
packages/client/js-client.web:
|
||||
specifiers:
|
||||
'@fluencelabs/interfaces': 0.7.4
|
||||
'@fluencelabs/js-peer': 0.8.6
|
||||
'@types/jest': 28.1.0
|
||||
'@types/node': 16.11.59
|
||||
jest: 28.1.0
|
||||
ts-jest: 28.0.2
|
||||
dependencies:
|
||||
'@fluencelabs/interfaces': link:../../core/interfaces
|
||||
'@fluencelabs/js-peer': link:../../core/js-peer
|
||||
devDependencies:
|
||||
'@types/jest': 28.1.0
|
||||
'@types/node': 16.11.59
|
||||
jest: 28.1.0_@types+node@16.11.59
|
||||
ts-jest: 28.0.2_m4pn7vsromlf5ffrouypoapnnq
|
||||
|
||||
packages/client/js-client.web.standalone:
|
||||
specifiers:
|
||||
'@fluencelabs/avm': 0.35.4
|
||||
'@fluencelabs/interfaces': 0.7.4
|
||||
'@fluencelabs/js-peer': 0.8.6
|
||||
'@fluencelabs/marine-js': 0.3.45
|
||||
'@rollup/plugin-inject': 5.0.3
|
||||
@ -152,6 +172,7 @@ importers:
|
||||
vite-plugin-replace: 0.1.1
|
||||
vite-tsconfig-paths: 4.0.3
|
||||
dependencies:
|
||||
'@fluencelabs/interfaces': link:../../core/interfaces
|
||||
'@fluencelabs/js-peer': link:../../core/js-peer
|
||||
buffer: 6.0.3
|
||||
process: 0.11.10
|
||||
@ -163,10 +184,10 @@ importers:
|
||||
'@types/node': 16.11.59
|
||||
jest: 28.1.0_@types+node@16.11.59
|
||||
js-base64: 3.7.5
|
||||
ts-jest: 28.0.2_byf75w6xilfwy3ncjzlldwxox4
|
||||
ts-jest: 28.0.2_m4pn7vsromlf5ffrouypoapnnq
|
||||
vite: 4.0.4_@types+node@16.11.59
|
||||
vite-plugin-replace: 0.1.1_vite@4.0.4
|
||||
vite-tsconfig-paths: 4.0.3_trrwuuiz4f5khno7hdf3cjz2ky
|
||||
vite-tsconfig-paths: 4.0.3_egung5nfepmolqa7uavvqho3gq
|
||||
|
||||
packages/client/tools:
|
||||
specifiers:
|
||||
@ -256,7 +277,7 @@ importers:
|
||||
devDependencies:
|
||||
'@fluencelabs/aqua-api': 0.9.3
|
||||
'@fluencelabs/aqua-lib': 0.6.0
|
||||
'@fluencelabs/cli': 0.3.9_g27jfiaq4eni6se3ukepjsbfru
|
||||
'@fluencelabs/cli': 0.3.9_qpxy5lgkhl6krmgytbtmcegdtu
|
||||
'@fluencelabs/fluence-network-environment': 1.0.13
|
||||
'@types/bs58': 4.0.1
|
||||
'@types/debug': 4.1.7
|
||||
@ -2432,23 +2453,23 @@ packages:
|
||||
/@fluencelabs/avm/0.35.4:
|
||||
resolution: {integrity: sha512-J070t5AOYIzQnNcBcYjDPUDzJTcpVboZxcrjGN4qYiOjcrtCtnnXeQKedLuBto5bRztHJdL9BzLLvzcFXhgmFQ==}
|
||||
|
||||
/@fluencelabs/cli/0.3.9_g27jfiaq4eni6se3ukepjsbfru:
|
||||
/@fluencelabs/cli/0.3.9_mhciodunqktqftpx562wx5v3rq:
|
||||
resolution: {integrity: sha512-xJYi7+AHrWt6RgWnr7Efr8Jpv0dNLoWhiCMvgSbXoFpIZzQAyNDgk5hnCdwIQ/eiJcNg0GHY0gyC+Q/d5YKc1Q==}
|
||||
engines: {node: '=16'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@fluencelabs/aqua-api': 0.10.3
|
||||
'@fluencelabs/deal-aurora': 0.1.8_oxd5x4wivhg5zjyucvh2su6sbi
|
||||
'@fluencelabs/fluence': 0.28.0_p34j5hgwlk5eyxias25f7ntdym
|
||||
'@fluencelabs/deal-aurora': 0.1.8_gc7gatgk3jrcbnir3tz2izmvqi
|
||||
'@fluencelabs/fluence': 0.28.0_zchmlqqvawdfhvpuz6nrqhtseq
|
||||
'@fluencelabs/fluence-network-environment': 1.0.14
|
||||
'@iarna/toml': 2.2.5
|
||||
'@mswjs/interceptors': 0.19.5
|
||||
'@oclif/color': 1.0.4
|
||||
'@oclif/core': 2.6.2_j777nnsruz44drbtesvg2fqc7y
|
||||
'@oclif/plugin-autocomplete': 2.1.4_j777nnsruz44drbtesvg2fqc7y
|
||||
'@oclif/plugin-help': 5.2.7_j777nnsruz44drbtesvg2fqc7y
|
||||
'@oclif/plugin-not-found': 2.3.21_j777nnsruz44drbtesvg2fqc7y
|
||||
'@walletconnect/universal-provider': 2.4.7_6ceuysb5murbu27s24v75mmoaq
|
||||
'@oclif/core': 2.6.2_4bewfcp2iebiwuold25d6rgcsy
|
||||
'@oclif/plugin-autocomplete': 2.1.4_4bewfcp2iebiwuold25d6rgcsy
|
||||
'@oclif/plugin-help': 5.2.7_4bewfcp2iebiwuold25d6rgcsy
|
||||
'@oclif/plugin-not-found': 2.3.21_4bewfcp2iebiwuold25d6rgcsy
|
||||
'@walletconnect/universal-provider': 2.4.7_mqijgjbnahaa3tnbkktxyzzb3a
|
||||
ajv: 8.12.0
|
||||
camelcase: 7.0.1
|
||||
chokidar: 3.5.3
|
||||
@ -2503,23 +2524,23 @@ packages:
|
||||
- utf-8-validate
|
||||
dev: true
|
||||
|
||||
/@fluencelabs/cli/0.3.9_zefx7wxidm6hdha6gwewr46oqa:
|
||||
/@fluencelabs/cli/0.3.9_qpxy5lgkhl6krmgytbtmcegdtu:
|
||||
resolution: {integrity: sha512-xJYi7+AHrWt6RgWnr7Efr8Jpv0dNLoWhiCMvgSbXoFpIZzQAyNDgk5hnCdwIQ/eiJcNg0GHY0gyC+Q/d5YKc1Q==}
|
||||
engines: {node: '=16'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@fluencelabs/aqua-api': 0.10.3
|
||||
'@fluencelabs/deal-aurora': 0.1.8_oxd5x4wivhg5zjyucvh2su6sbi
|
||||
'@fluencelabs/fluence': 0.28.0_p34j5hgwlk5eyxias25f7ntdym
|
||||
'@fluencelabs/deal-aurora': 0.1.8_gc7gatgk3jrcbnir3tz2izmvqi
|
||||
'@fluencelabs/fluence': 0.28.0_zchmlqqvawdfhvpuz6nrqhtseq
|
||||
'@fluencelabs/fluence-network-environment': 1.0.14
|
||||
'@iarna/toml': 2.2.5
|
||||
'@mswjs/interceptors': 0.19.5
|
||||
'@oclif/color': 1.0.4
|
||||
'@oclif/core': 2.6.2_j777nnsruz44drbtesvg2fqc7y
|
||||
'@oclif/plugin-autocomplete': 2.1.4_j777nnsruz44drbtesvg2fqc7y
|
||||
'@oclif/plugin-help': 5.2.7_j777nnsruz44drbtesvg2fqc7y
|
||||
'@oclif/plugin-not-found': 2.3.21_j777nnsruz44drbtesvg2fqc7y
|
||||
'@walletconnect/universal-provider': 2.4.7_me47trx7umfhug4hptdagpeso4
|
||||
'@oclif/core': 2.6.2_4bewfcp2iebiwuold25d6rgcsy
|
||||
'@oclif/plugin-autocomplete': 2.1.4_4bewfcp2iebiwuold25d6rgcsy
|
||||
'@oclif/plugin-help': 5.2.7_4bewfcp2iebiwuold25d6rgcsy
|
||||
'@oclif/plugin-not-found': 2.3.21_4bewfcp2iebiwuold25d6rgcsy
|
||||
'@walletconnect/universal-provider': 2.4.7_uz4bki5hahbpc2hwfvrjhsfeca
|
||||
ajv: 8.12.0
|
||||
camelcase: 7.0.1
|
||||
chokidar: 3.5.3
|
||||
@ -2624,10 +2645,10 @@ packages:
|
||||
- utf-8-validate
|
||||
dev: true
|
||||
|
||||
/@fluencelabs/deal-aurora/0.1.8_oxd5x4wivhg5zjyucvh2su6sbi:
|
||||
/@fluencelabs/deal-aurora/0.1.8_gc7gatgk3jrcbnir3tz2izmvqi:
|
||||
resolution: {integrity: sha512-h2L3F67AsFxJy+mBAAUy8gMUGf85sgT3kuLhqEstdbQ20ASjxrSsXmyVZeVQLUx4nR1ygbGll9Y+FmRFgpNwMQ==}
|
||||
dependencies:
|
||||
'@nomicfoundation/hardhat-toolbox': 1.0.2_oxd5x4wivhg5zjyucvh2su6sbi
|
||||
'@nomicfoundation/hardhat-toolbox': 1.0.2_gc7gatgk3jrcbnir3tz2izmvqi
|
||||
'@openzeppelin/contracts': 4.8.2
|
||||
'@openzeppelin/contracts-upgradeable': 4.8.2
|
||||
dotenv: 16.0.3
|
||||
@ -2697,7 +2718,7 @@ packages:
|
||||
- utf-8-validate
|
||||
dev: true
|
||||
|
||||
/@fluencelabs/fluence/0.28.0_p34j5hgwlk5eyxias25f7ntdym:
|
||||
/@fluencelabs/fluence/0.28.0_zchmlqqvawdfhvpuz6nrqhtseq:
|
||||
resolution: {integrity: sha512-SXb2vjTj8m/nw4jEILV0tu9VIFprGo8mNb2nOB5btxdsOI8GzQZkpGzTLrSd/+UagIo2GdxAu0GhBP8dxZXaqg==}
|
||||
engines: {node: '>=10', pnpm: '>=3'}
|
||||
hasBin: true
|
||||
@ -2706,7 +2727,7 @@ packages:
|
||||
'@fluencelabs/connection': 0.2.0_node-fetch@2.6.9
|
||||
'@fluencelabs/interfaces': 0.1.0
|
||||
'@fluencelabs/keypair': 0.2.0
|
||||
'@fluencelabs/marine-js': 0.3.37_g4n3hsjlbmz4ag5o32ytojordu
|
||||
'@fluencelabs/marine-js': 0.3.37_cnngzrja2umb46xxazlucyx2qu
|
||||
async: 3.2.4
|
||||
base64-js: 1.5.1
|
||||
browser-or-node: 2.0.0
|
||||
@ -2748,6 +2769,25 @@ packages:
|
||||
peer-id: 0.16.0
|
||||
dev: true
|
||||
|
||||
/@fluencelabs/marine-js/0.3.37_cnngzrja2umb46xxazlucyx2qu:
|
||||
resolution: {integrity: sha512-/Kpu3S+aDOfrOpKBAK1VeWSHKCoD36/dxtHEWHbj3Lsro0GB9zkoaZPHlFFL7rorCB+hyjAJqLDuBGI8f3l/qg==}
|
||||
dependencies:
|
||||
'@wasmer/wasi': 0.12.0
|
||||
'@wasmer/wasmfs': 0.12.0
|
||||
browser-or-node: 2.0.0
|
||||
buffer: 6.0.3
|
||||
threads: 1.7.0
|
||||
ts-jest: 27.1.5_cnngzrja2umb46xxazlucyx2qu
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@types/jest'
|
||||
- babel-jest
|
||||
- esbuild
|
||||
- jest
|
||||
- supports-color
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/@fluencelabs/marine-js/0.3.37_g4n3hsjlbmz4ag5o32ytojordu:
|
||||
resolution: {integrity: sha512-/Kpu3S+aDOfrOpKBAK1VeWSHKCoD36/dxtHEWHbj3Lsro0GB9zkoaZPHlFFL7rorCB+hyjAJqLDuBGI8f3l/qg==}
|
||||
dependencies:
|
||||
@ -3862,7 +3902,7 @@ packages:
|
||||
tweetnacl-util: 0.15.1
|
||||
dev: true
|
||||
|
||||
/@morgan-stanley/ts-mocking-bird/0.6.4_g4n3hsjlbmz4ag5o32ytojordu:
|
||||
/@morgan-stanley/ts-mocking-bird/0.6.4_cnngzrja2umb46xxazlucyx2qu:
|
||||
resolution: {integrity: sha512-57VJIflP8eR2xXa9cD1LUawh+Gh+BVQfVu0n6GALyg/AqV/Nz25kDRvws3i9kIe1PTrbsZZOYpsYp6bXPd6nVA==}
|
||||
peerDependencies:
|
||||
jasmine: 2.x || 3.x || 4.x
|
||||
@ -3876,7 +3916,7 @@ packages:
|
||||
dependencies:
|
||||
jest: 27.5.1_ts-node@10.9.1
|
||||
lodash: 4.17.21
|
||||
typescript: 4.7.4
|
||||
typescript: 4.9.5
|
||||
uuid: 7.0.3
|
||||
dev: true
|
||||
|
||||
@ -4131,7 +4171,7 @@ packages:
|
||||
chai-as-promised: 7.1.1_chai@4.3.7
|
||||
deep-eql: 4.1.3
|
||||
ethers: 5.7.2
|
||||
hardhat: 2.13.0_6oasmw356qmm23djlsjgkwvrtm
|
||||
hardhat: 2.13.0_6qtx7vkbdhwvdm4crzlegk4mvi
|
||||
ordinal: 1.0.3
|
||||
dev: true
|
||||
|
||||
@ -4141,10 +4181,10 @@ packages:
|
||||
hardhat: ^2.9.5
|
||||
dependencies:
|
||||
ethereumjs-util: 7.1.5
|
||||
hardhat: 2.13.0_6oasmw356qmm23djlsjgkwvrtm
|
||||
hardhat: 2.13.0_6qtx7vkbdhwvdm4crzlegk4mvi
|
||||
dev: true
|
||||
|
||||
/@nomicfoundation/hardhat-toolbox/1.0.2_oxd5x4wivhg5zjyucvh2su6sbi:
|
||||
/@nomicfoundation/hardhat-toolbox/1.0.2_gc7gatgk3jrcbnir3tz2izmvqi:
|
||||
resolution: {integrity: sha512-8CEgWSKUK2aMit+76Sez8n7UB0Ze1lwT+LcWxj4EFP30lQWOwOws048t6MTPfThH0BlSWjC6hJRr0LncIkc1Sw==}
|
||||
peerDependencies:
|
||||
'@ethersproject/abi': ^5.4.7
|
||||
@ -4173,19 +4213,19 @@ packages:
|
||||
'@nomicfoundation/hardhat-network-helpers': 1.0.8_hardhat@2.13.0
|
||||
'@nomiclabs/hardhat-ethers': 2.2.2_wknqauzjtp3mhprkntsmqpccee
|
||||
'@nomiclabs/hardhat-etherscan': 3.1.7_hardhat@2.13.0
|
||||
'@typechain/ethers-v5': 10.2.0_3j7r5nxxske4oddcicgvma5gkq
|
||||
'@typechain/ethers-v5': 10.2.0_kuvqdvnhbslgxdpi2awxjzdvhe
|
||||
'@typechain/hardhat': 6.1.5_i2fwc4p7n2iltkzqmr3qbux6yq
|
||||
'@types/chai': 4.3.4
|
||||
'@types/mocha': 9.1.1
|
||||
'@types/node': 18.13.0
|
||||
chai: 4.3.7
|
||||
ethers: 5.7.2
|
||||
hardhat: 2.13.0_6oasmw356qmm23djlsjgkwvrtm
|
||||
hardhat: 2.13.0_6qtx7vkbdhwvdm4crzlegk4mvi
|
||||
hardhat-gas-reporter: 1.0.9_hardhat@2.13.0
|
||||
solidity-coverage: 0.7.22
|
||||
ts-node: 10.9.1_j777nnsruz44drbtesvg2fqc7y
|
||||
typechain: 8.1.1_g4n3hsjlbmz4ag5o32ytojordu
|
||||
typescript: 4.7.4
|
||||
ts-node: 10.9.1_4bewfcp2iebiwuold25d6rgcsy
|
||||
typechain: 8.1.1_cnngzrja2umb46xxazlucyx2qu
|
||||
typescript: 4.9.5
|
||||
dev: true
|
||||
|
||||
/@nomicfoundation/solidity-analyzer-darwin-arm64/0.1.1:
|
||||
@ -4301,7 +4341,7 @@ packages:
|
||||
hardhat: ^2.0.0
|
||||
dependencies:
|
||||
ethers: 5.7.2
|
||||
hardhat: 2.13.0_6oasmw356qmm23djlsjgkwvrtm
|
||||
hardhat: 2.13.0_6qtx7vkbdhwvdm4crzlegk4mvi
|
||||
dev: true
|
||||
|
||||
/@nomiclabs/hardhat-etherscan/3.1.7_hardhat@2.13.0:
|
||||
@ -4315,7 +4355,7 @@ packages:
|
||||
chalk: 2.4.2
|
||||
debug: 4.3.4
|
||||
fs-extra: 7.0.1
|
||||
hardhat: 2.13.0_6oasmw356qmm23djlsjgkwvrtm
|
||||
hardhat: 2.13.0_6qtx7vkbdhwvdm4crzlegk4mvi
|
||||
lodash: 4.17.21
|
||||
semver: 6.3.0
|
||||
table: 6.8.1
|
||||
@ -4335,7 +4375,7 @@ packages:
|
||||
tslib: 2.5.0
|
||||
dev: true
|
||||
|
||||
/@oclif/core/2.6.2_j777nnsruz44drbtesvg2fqc7y:
|
||||
/@oclif/core/2.6.2_4bewfcp2iebiwuold25d6rgcsy:
|
||||
resolution: {integrity: sha512-roxcBLr4BuoOEDEkMQk4Yy0Tolr39n6i+A63qPLa19vrgxjZZJygh2HpThsn69/UPuEzMq051FnvJ9tNln3Y5g==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
dependencies:
|
||||
@ -4363,7 +4403,7 @@ packages:
|
||||
strip-ansi: 6.0.1
|
||||
supports-color: 8.1.1
|
||||
supports-hyperlinks: 2.3.0
|
||||
ts-node: 10.9.1_j777nnsruz44drbtesvg2fqc7y
|
||||
ts-node: 10.9.1_4bewfcp2iebiwuold25d6rgcsy
|
||||
tslib: 2.5.0
|
||||
widest-line: 3.1.0
|
||||
wordwrap: 1.0.0
|
||||
@ -4375,11 +4415,11 @@ packages:
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/@oclif/plugin-autocomplete/2.1.4_j777nnsruz44drbtesvg2fqc7y:
|
||||
/@oclif/plugin-autocomplete/2.1.4_4bewfcp2iebiwuold25d6rgcsy:
|
||||
resolution: {integrity: sha512-MwjXXE05fho7bMuilCbXt7DAc5jFKnKMzeE95VUOCCc390Ma9fmY32sNZH/COkJxnxBhFj7rUVy/PiCqimBiyQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
'@oclif/core': 2.6.2_j777nnsruz44drbtesvg2fqc7y
|
||||
'@oclif/core': 2.6.2_4bewfcp2iebiwuold25d6rgcsy
|
||||
chalk: 4.1.2
|
||||
debug: 4.3.4
|
||||
fs-extra: 9.1.0
|
||||
@ -4391,11 +4431,11 @@ packages:
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/@oclif/plugin-help/5.2.7_j777nnsruz44drbtesvg2fqc7y:
|
||||
/@oclif/plugin-help/5.2.7_4bewfcp2iebiwuold25d6rgcsy:
|
||||
resolution: {integrity: sha512-p6DJk0QMfzGvXGqrSZTQPitUGi68oGzr48tRKdrVxDDPMAELxHeXXFbkNRNdvjldPpTk44m0PbxV5tWhwurRwg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
'@oclif/core': 2.6.2_j777nnsruz44drbtesvg2fqc7y
|
||||
'@oclif/core': 2.6.2_4bewfcp2iebiwuold25d6rgcsy
|
||||
transitivePeerDependencies:
|
||||
- '@swc/core'
|
||||
- '@swc/wasm'
|
||||
@ -4403,12 +4443,12 @@ packages:
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/@oclif/plugin-not-found/2.3.21_j777nnsruz44drbtesvg2fqc7y:
|
||||
/@oclif/plugin-not-found/2.3.21_4bewfcp2iebiwuold25d6rgcsy:
|
||||
resolution: {integrity: sha512-6N0gTWQhl5nuE5bXipv5+8vH5cHyGgEd3MKJHQYxFnHIFwwb9jJnFl0BZ0fo7Jrjd9HZYCLT7rjnouS7p1Dl1w==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
'@oclif/color': 1.0.4
|
||||
'@oclif/core': 2.6.2_j777nnsruz44drbtesvg2fqc7y
|
||||
'@oclif/core': 2.6.2_4bewfcp2iebiwuold25d6rgcsy
|
||||
fast-levenshtein: 3.0.0
|
||||
lodash: 4.17.21
|
||||
transitivePeerDependencies:
|
||||
@ -5144,7 +5184,7 @@ packages:
|
||||
resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
|
||||
dev: true
|
||||
|
||||
/@typechain/ethers-v5/10.2.0_3j7r5nxxske4oddcicgvma5gkq:
|
||||
/@typechain/ethers-v5/10.2.0_kuvqdvnhbslgxdpi2awxjzdvhe:
|
||||
resolution: {integrity: sha512-ikaq0N/w9fABM+G01OFmU3U3dNnyRwEahkdvi9mqy1a3XwKiPZaF/lu54OcNaEWnpvEYyhhS0N7buCtLQqC92w==}
|
||||
peerDependencies:
|
||||
'@ethersproject/abi': ^5.0.0
|
||||
@ -5159,9 +5199,9 @@ packages:
|
||||
'@ethersproject/providers': 5.7.2
|
||||
ethers: 5.7.2
|
||||
lodash: 4.17.21
|
||||
ts-essentials: 7.0.3_typescript@4.7.4
|
||||
typechain: 8.1.1_g4n3hsjlbmz4ag5o32ytojordu
|
||||
typescript: 4.7.4
|
||||
ts-essentials: 7.0.3_typescript@4.9.5
|
||||
typechain: 8.1.1_cnngzrja2umb46xxazlucyx2qu
|
||||
typescript: 4.9.5
|
||||
dev: true
|
||||
|
||||
/@typechain/hardhat/6.1.5_i2fwc4p7n2iltkzqmr3qbux6yq:
|
||||
@ -5176,11 +5216,11 @@ packages:
|
||||
dependencies:
|
||||
'@ethersproject/abi': 5.7.0
|
||||
'@ethersproject/providers': 5.7.2
|
||||
'@typechain/ethers-v5': 10.2.0_3j7r5nxxske4oddcicgvma5gkq
|
||||
'@typechain/ethers-v5': 10.2.0_kuvqdvnhbslgxdpi2awxjzdvhe
|
||||
ethers: 5.7.2
|
||||
fs-extra: 9.1.0
|
||||
hardhat: 2.13.0_6oasmw356qmm23djlsjgkwvrtm
|
||||
typechain: 8.1.1_g4n3hsjlbmz4ag5o32ytojordu
|
||||
hardhat: 2.13.0_6qtx7vkbdhwvdm4crzlegk4mvi
|
||||
typechain: 8.1.1_cnngzrja2umb46xxazlucyx2qu
|
||||
dev: true
|
||||
|
||||
/@types/aria-query/5.0.1:
|
||||
@ -5795,10 +5835,10 @@ packages:
|
||||
pretty-format: 27.5.1
|
||||
dev: true
|
||||
|
||||
/@walletconnect/core/2.4.7_me47trx7umfhug4hptdagpeso4:
|
||||
/@walletconnect/core/2.4.7_mqijgjbnahaa3tnbkktxyzzb3a:
|
||||
resolution: {integrity: sha512-w92NrtziqrWs070HJICGh80Vp60PaXu06OjNvOnVZEorbTipCWx4xxgcC2NhsT4TCQ8r1FOut6ahLe1PILuRsg==}
|
||||
dependencies:
|
||||
'@walletconnect/heartbeat': 1.2.0_j777nnsruz44drbtesvg2fqc7y
|
||||
'@walletconnect/heartbeat': 1.2.0_4bewfcp2iebiwuold25d6rgcsy
|
||||
'@walletconnect/jsonrpc-provider': 1.0.8
|
||||
'@walletconnect/jsonrpc-utils': 1.0.6
|
||||
'@walletconnect/jsonrpc-ws-connection': 1.0.9
|
||||
@ -5808,8 +5848,8 @@ packages:
|
||||
'@walletconnect/relay-auth': 1.0.4
|
||||
'@walletconnect/safe-json': 1.0.1
|
||||
'@walletconnect/time': 1.0.2
|
||||
'@walletconnect/types': 2.4.7_me47trx7umfhug4hptdagpeso4
|
||||
'@walletconnect/utils': 2.4.7_me47trx7umfhug4hptdagpeso4
|
||||
'@walletconnect/types': 2.4.7_mqijgjbnahaa3tnbkktxyzzb3a
|
||||
'@walletconnect/utils': 2.4.7_mqijgjbnahaa3tnbkktxyzzb3a
|
||||
events: 3.3.0
|
||||
lodash.isequal: 4.5.0
|
||||
pino: 7.11.0
|
||||
@ -5838,14 +5878,14 @@ packages:
|
||||
tslib: 1.14.1
|
||||
dev: true
|
||||
|
||||
/@walletconnect/heartbeat/1.2.0_j777nnsruz44drbtesvg2fqc7y:
|
||||
/@walletconnect/heartbeat/1.2.0_4bewfcp2iebiwuold25d6rgcsy:
|
||||
resolution: {integrity: sha512-0vbzTa/ARrpmMmOD+bQMxPvFYKtOLQZObgZakrYr0aODiMOO71CmPVNV2eAqXnw9rMmcP+z91OybLeIFlwTjjA==}
|
||||
dependencies:
|
||||
'@walletconnect/events': 1.0.1
|
||||
'@walletconnect/time': 1.0.2
|
||||
chai: 4.3.7
|
||||
mocha: 10.2.0
|
||||
ts-node: 10.9.1_j777nnsruz44drbtesvg2fqc7y
|
||||
ts-node: 10.9.1_4bewfcp2iebiwuold25d6rgcsy
|
||||
tslib: 1.14.1
|
||||
transitivePeerDependencies:
|
||||
- '@swc/core'
|
||||
@ -5948,18 +5988,18 @@ packages:
|
||||
tslib: 1.14.1
|
||||
dev: true
|
||||
|
||||
/@walletconnect/sign-client/2.4.7_me47trx7umfhug4hptdagpeso4:
|
||||
/@walletconnect/sign-client/2.4.7_mqijgjbnahaa3tnbkktxyzzb3a:
|
||||
resolution: {integrity: sha512-x5uxnHQkNSn0QNXUdPEfwy4o1Vyi2QIWkDGUh+pfSP4s2vN0+IJAcwqBqkPn+zJ1X7eKYLs+v0ih1eieciYMPA==}
|
||||
dependencies:
|
||||
'@walletconnect/core': 2.4.7_me47trx7umfhug4hptdagpeso4
|
||||
'@walletconnect/core': 2.4.7_mqijgjbnahaa3tnbkktxyzzb3a
|
||||
'@walletconnect/events': 1.0.1
|
||||
'@walletconnect/heartbeat': 1.2.0_j777nnsruz44drbtesvg2fqc7y
|
||||
'@walletconnect/heartbeat': 1.2.0_4bewfcp2iebiwuold25d6rgcsy
|
||||
'@walletconnect/jsonrpc-provider': 1.0.8
|
||||
'@walletconnect/jsonrpc-utils': 1.0.6
|
||||
'@walletconnect/logger': 2.0.1
|
||||
'@walletconnect/time': 1.0.2
|
||||
'@walletconnect/types': 2.4.7_me47trx7umfhug4hptdagpeso4
|
||||
'@walletconnect/utils': 2.4.7_me47trx7umfhug4hptdagpeso4
|
||||
'@walletconnect/types': 2.4.7_mqijgjbnahaa3tnbkktxyzzb3a
|
||||
'@walletconnect/utils': 2.4.7_mqijgjbnahaa3tnbkktxyzzb3a
|
||||
events: 3.3.0
|
||||
pino: 7.11.0
|
||||
transitivePeerDependencies:
|
||||
@ -5979,11 +6019,11 @@ packages:
|
||||
tslib: 1.14.1
|
||||
dev: true
|
||||
|
||||
/@walletconnect/types/2.4.7_me47trx7umfhug4hptdagpeso4:
|
||||
/@walletconnect/types/2.4.7_mqijgjbnahaa3tnbkktxyzzb3a:
|
||||
resolution: {integrity: sha512-1VaPdPJrE+UrEjAhK5bdxq2+MTo3DvUMmQeNUsp3vUGhocQXB9hJQQ1rYBknYYSyDu2rTksGCQ4nv3ZOqfxvHw==}
|
||||
dependencies:
|
||||
'@walletconnect/events': 1.0.1
|
||||
'@walletconnect/heartbeat': 1.2.0_j777nnsruz44drbtesvg2fqc7y
|
||||
'@walletconnect/heartbeat': 1.2.0_4bewfcp2iebiwuold25d6rgcsy
|
||||
'@walletconnect/jsonrpc-types': 1.0.2
|
||||
'@walletconnect/keyvaluestorage': 1.0.2_lokijs@1.5.12
|
||||
'@walletconnect/logger': 2.0.1
|
||||
@ -5997,7 +6037,7 @@ packages:
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/@walletconnect/universal-provider/2.4.7_6ceuysb5murbu27s24v75mmoaq:
|
||||
/@walletconnect/universal-provider/2.4.7_mqijgjbnahaa3tnbkktxyzzb3a:
|
||||
resolution: {integrity: sha512-xlefq2ahAsH3SpcsofWQQ5JT3Tz9NLAViA8FW07PHhfuf9p7OLp+Mu1wKxQEoBilyvfYRF4R5MTyTPy1wqJiRA==}
|
||||
dependencies:
|
||||
'@walletconnect/jsonrpc-http-connection': 1.0.6
|
||||
@ -6005,36 +6045,9 @@ packages:
|
||||
'@walletconnect/jsonrpc-types': 1.0.2
|
||||
'@walletconnect/jsonrpc-utils': 1.0.6
|
||||
'@walletconnect/logger': 2.0.1
|
||||
'@walletconnect/sign-client': 2.4.7_me47trx7umfhug4hptdagpeso4
|
||||
'@walletconnect/types': 2.4.7_me47trx7umfhug4hptdagpeso4
|
||||
'@walletconnect/utils': 2.4.7_me47trx7umfhug4hptdagpeso4
|
||||
eip1193-provider: 1.0.1_debug@4.3.4
|
||||
events: 3.3.0
|
||||
pino: 7.11.0
|
||||
transitivePeerDependencies:
|
||||
- '@react-native-async-storage/async-storage'
|
||||
- '@swc/core'
|
||||
- '@swc/wasm'
|
||||
- '@types/node'
|
||||
- bufferutil
|
||||
- debug
|
||||
- encoding
|
||||
- lokijs
|
||||
- typescript
|
||||
- utf-8-validate
|
||||
dev: true
|
||||
|
||||
/@walletconnect/universal-provider/2.4.7_me47trx7umfhug4hptdagpeso4:
|
||||
resolution: {integrity: sha512-xlefq2ahAsH3SpcsofWQQ5JT3Tz9NLAViA8FW07PHhfuf9p7OLp+Mu1wKxQEoBilyvfYRF4R5MTyTPy1wqJiRA==}
|
||||
dependencies:
|
||||
'@walletconnect/jsonrpc-http-connection': 1.0.6
|
||||
'@walletconnect/jsonrpc-provider': 1.0.8
|
||||
'@walletconnect/jsonrpc-types': 1.0.2
|
||||
'@walletconnect/jsonrpc-utils': 1.0.6
|
||||
'@walletconnect/logger': 2.0.1
|
||||
'@walletconnect/sign-client': 2.4.7_me47trx7umfhug4hptdagpeso4
|
||||
'@walletconnect/types': 2.4.7_me47trx7umfhug4hptdagpeso4
|
||||
'@walletconnect/utils': 2.4.7_me47trx7umfhug4hptdagpeso4
|
||||
'@walletconnect/sign-client': 2.4.7_mqijgjbnahaa3tnbkktxyzzb3a
|
||||
'@walletconnect/types': 2.4.7_mqijgjbnahaa3tnbkktxyzzb3a
|
||||
'@walletconnect/utils': 2.4.7_mqijgjbnahaa3tnbkktxyzzb3a
|
||||
eip1193-provider: 1.0.1
|
||||
events: 3.3.0
|
||||
pino: 7.11.0
|
||||
@ -6051,7 +6064,34 @@ packages:
|
||||
- utf-8-validate
|
||||
dev: true
|
||||
|
||||
/@walletconnect/utils/2.4.7_me47trx7umfhug4hptdagpeso4:
|
||||
/@walletconnect/universal-provider/2.4.7_uz4bki5hahbpc2hwfvrjhsfeca:
|
||||
resolution: {integrity: sha512-xlefq2ahAsH3SpcsofWQQ5JT3Tz9NLAViA8FW07PHhfuf9p7OLp+Mu1wKxQEoBilyvfYRF4R5MTyTPy1wqJiRA==}
|
||||
dependencies:
|
||||
'@walletconnect/jsonrpc-http-connection': 1.0.6
|
||||
'@walletconnect/jsonrpc-provider': 1.0.8
|
||||
'@walletconnect/jsonrpc-types': 1.0.2
|
||||
'@walletconnect/jsonrpc-utils': 1.0.6
|
||||
'@walletconnect/logger': 2.0.1
|
||||
'@walletconnect/sign-client': 2.4.7_mqijgjbnahaa3tnbkktxyzzb3a
|
||||
'@walletconnect/types': 2.4.7_mqijgjbnahaa3tnbkktxyzzb3a
|
||||
'@walletconnect/utils': 2.4.7_mqijgjbnahaa3tnbkktxyzzb3a
|
||||
eip1193-provider: 1.0.1_debug@4.3.4
|
||||
events: 3.3.0
|
||||
pino: 7.11.0
|
||||
transitivePeerDependencies:
|
||||
- '@react-native-async-storage/async-storage'
|
||||
- '@swc/core'
|
||||
- '@swc/wasm'
|
||||
- '@types/node'
|
||||
- bufferutil
|
||||
- debug
|
||||
- encoding
|
||||
- lokijs
|
||||
- typescript
|
||||
- utf-8-validate
|
||||
dev: true
|
||||
|
||||
/@walletconnect/utils/2.4.7_mqijgjbnahaa3tnbkktxyzzb3a:
|
||||
resolution: {integrity: sha512-t3kW0qLClnejTTKg3y/o/MmJb5ZDGfD13YT9Nw56Up3qq/pwVfTtWjt8vJOQWMIm0hZgjgESivcf6/wuu3/Oqw==}
|
||||
dependencies:
|
||||
'@stablelib/chacha20poly1305': 1.0.1
|
||||
@ -6063,7 +6103,7 @@ packages:
|
||||
'@walletconnect/relay-api': 1.0.9
|
||||
'@walletconnect/safe-json': 1.0.1
|
||||
'@walletconnect/time': 1.0.2
|
||||
'@walletconnect/types': 2.4.7_me47trx7umfhug4hptdagpeso4
|
||||
'@walletconnect/types': 2.4.7_mqijgjbnahaa3tnbkktxyzzb3a
|
||||
'@walletconnect/window-getters': 1.0.1
|
||||
'@walletconnect/window-metadata': 1.0.1
|
||||
detect-browser: 5.3.0
|
||||
@ -10874,13 +10914,13 @@ packages:
|
||||
dependencies:
|
||||
array-uniq: 1.0.3
|
||||
eth-gas-reporter: 0.2.25
|
||||
hardhat: 2.13.0_6oasmw356qmm23djlsjgkwvrtm
|
||||
hardhat: 2.13.0_6qtx7vkbdhwvdm4crzlegk4mvi
|
||||
sha1: 1.1.1
|
||||
transitivePeerDependencies:
|
||||
- '@codechecks/client'
|
||||
dev: true
|
||||
|
||||
/hardhat/2.13.0_6oasmw356qmm23djlsjgkwvrtm:
|
||||
/hardhat/2.13.0_6qtx7vkbdhwvdm4crzlegk4mvi:
|
||||
resolution: {integrity: sha512-ZlzBOLML1QGlm6JWyVAG8lVTEAoOaVm1in/RU2zoGAnYEoD1Rp4T+ZMvrLNhHaaeS9hfjJ1gJUBfiDr4cx+htQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
@ -10939,9 +10979,9 @@ packages:
|
||||
solc: 0.7.3_debug@4.3.4
|
||||
source-map-support: 0.5.21
|
||||
stacktrace-parser: 0.1.10
|
||||
ts-node: 10.9.1_j777nnsruz44drbtesvg2fqc7y
|
||||
ts-node: 10.9.1_4bewfcp2iebiwuold25d6rgcsy
|
||||
tsort: 0.0.1
|
||||
typescript: 4.7.4
|
||||
typescript: 4.9.5
|
||||
undici: 5.18.0
|
||||
uuid: 8.3.2
|
||||
ws: 7.5.9
|
||||
@ -12646,7 +12686,7 @@ packages:
|
||||
pretty-format: 27.5.1
|
||||
slash: 3.0.0
|
||||
strip-json-comments: 3.1.1
|
||||
ts-node: 10.9.1_j777nnsruz44drbtesvg2fqc7y
|
||||
ts-node: 10.9.1_4bewfcp2iebiwuold25d6rgcsy
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- canvas
|
||||
@ -18208,7 +18248,7 @@ packages:
|
||||
dependencies:
|
||||
command-exists: 1.2.9
|
||||
commander: 3.0.2
|
||||
follow-redirects: 1.15.2_debug@4.3.4
|
||||
follow-redirects: 1.15.2
|
||||
fs-extra: 0.30.0
|
||||
js-sha3: 0.8.0
|
||||
memorystream: 0.3.1
|
||||
@ -19151,11 +19191,11 @@ packages:
|
||||
resolution: {integrity: sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==}
|
||||
dev: false
|
||||
|
||||
/ts-command-line-args/2.4.2_g4n3hsjlbmz4ag5o32ytojordu:
|
||||
/ts-command-line-args/2.4.2_cnngzrja2umb46xxazlucyx2qu:
|
||||
resolution: {integrity: sha512-mJLQQBOdyD4XI/ZWQY44PIdYde47JhV2xl380O7twPkTQ+Y5vFDHsk8LOeXKuz7dVY5aDCfAzRarNfSqtKOkQQ==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@morgan-stanley/ts-mocking-bird': 0.6.4_g4n3hsjlbmz4ag5o32ytojordu
|
||||
'@morgan-stanley/ts-mocking-bird': 0.6.4_cnngzrja2umb46xxazlucyx2qu
|
||||
chalk: 4.1.2
|
||||
command-line-args: 5.2.1
|
||||
command-line-usage: 6.1.3
|
||||
@ -19166,12 +19206,45 @@ packages:
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/ts-essentials/7.0.3_typescript@4.7.4:
|
||||
/ts-essentials/7.0.3_typescript@4.9.5:
|
||||
resolution: {integrity: sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==}
|
||||
peerDependencies:
|
||||
typescript: '>=3.7.0'
|
||||
dependencies:
|
||||
typescript: 4.7.4
|
||||
typescript: 4.9.5
|
||||
dev: true
|
||||
|
||||
/ts-jest/27.1.5_cnngzrja2umb46xxazlucyx2qu:
|
||||
resolution: {integrity: sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA==}
|
||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@babel/core': '>=7.0.0-beta.0 <8'
|
||||
'@types/jest': ^27.0.0
|
||||
babel-jest: '>=27.0.0 <28'
|
||||
esbuild: '*'
|
||||
jest: ^27.0.0
|
||||
typescript: '>=3.8 <5.0'
|
||||
peerDependenciesMeta:
|
||||
'@babel/core':
|
||||
optional: true
|
||||
'@types/jest':
|
||||
optional: true
|
||||
babel-jest:
|
||||
optional: true
|
||||
esbuild:
|
||||
optional: true
|
||||
dependencies:
|
||||
bs-logger: 0.2.6
|
||||
fast-json-stable-stringify: 2.1.0
|
||||
jest: 27.5.1_ts-node@10.9.1
|
||||
jest-util: 27.5.1
|
||||
json5: 2.2.3
|
||||
lodash.memoize: 4.1.2
|
||||
make-error: 1.3.6
|
||||
semver: 7.3.8
|
||||
typescript: 4.9.5
|
||||
yargs-parser: 20.2.9
|
||||
dev: true
|
||||
|
||||
/ts-jest/27.1.5_g4n3hsjlbmz4ag5o32ytojordu:
|
||||
@ -19207,7 +19280,7 @@ packages:
|
||||
yargs-parser: 20.2.9
|
||||
dev: true
|
||||
|
||||
/ts-jest/28.0.2_byf75w6xilfwy3ncjzlldwxox4:
|
||||
/ts-jest/28.0.2_m4pn7vsromlf5ffrouypoapnnq:
|
||||
resolution: {integrity: sha512-IOZMb3D0gx6IHO9ywPgiQxJ3Zl4ECylEFwoVpENB55aTn5sdO0Ptyx/7noNBxAaUff708RqQL4XBNxxOVjY0vQ==}
|
||||
engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
|
||||
hasBin: true
|
||||
@ -19238,10 +19311,41 @@ packages:
|
||||
lodash.memoize: 4.1.2
|
||||
make-error: 1.3.6
|
||||
semver: 7.3.8
|
||||
typescript: 4.7.4
|
||||
typescript: 4.9.5
|
||||
yargs-parser: 20.2.9
|
||||
dev: true
|
||||
|
||||
/ts-node/10.9.1_4bewfcp2iebiwuold25d6rgcsy:
|
||||
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@swc/core': '>=1.2.50'
|
||||
'@swc/wasm': '>=1.2.50'
|
||||
'@types/node': '*'
|
||||
typescript: '>=2.7'
|
||||
peerDependenciesMeta:
|
||||
'@swc/core':
|
||||
optional: true
|
||||
'@swc/wasm':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
'@tsconfig/node10': 1.0.9
|
||||
'@tsconfig/node12': 1.0.11
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.3
|
||||
'@types/node': 18.13.0
|
||||
acorn: 8.8.2
|
||||
acorn-walk: 8.2.0
|
||||
arg: 4.1.3
|
||||
create-require: 1.1.1
|
||||
diff: 4.0.2
|
||||
make-error: 1.3.6
|
||||
typescript: 4.9.5
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
dev: true
|
||||
|
||||
/ts-node/10.9.1_j777nnsruz44drbtesvg2fqc7y:
|
||||
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
|
||||
hasBin: true
|
||||
@ -19276,7 +19380,7 @@ packages:
|
||||
/ts-pattern/3.3.3:
|
||||
resolution: {integrity: sha512-Z5EFi6g6wyX3uDFHqxF5W5c5h663oZg9O6aOiAT7fqNu0HPSfCxtHzrQ7SblTy738Mrg2Ezorky8H5aUOm8Pvg==}
|
||||
|
||||
/tsconfck/2.0.2_typescript@4.7.4:
|
||||
/tsconfck/2.0.2_typescript@4.9.5:
|
||||
resolution: {integrity: sha512-H3DWlwKpow+GpVLm/2cpmok72pwRr1YFROV3YzAmvzfGFiC1zEM/mc9b7+1XnrxuXtEbhJ7xUSIqjPFbedp7aQ==}
|
||||
engines: {node: ^14.13.1 || ^16 || >=18, pnpm: ^7.18.0}
|
||||
hasBin: true
|
||||
@ -19286,7 +19390,7 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
typescript: 4.7.4
|
||||
typescript: 4.9.5
|
||||
dev: true
|
||||
|
||||
/tsconfig-paths/3.14.1:
|
||||
@ -19392,7 +19496,7 @@ packages:
|
||||
resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==}
|
||||
dev: true
|
||||
|
||||
/typechain/8.1.1_g4n3hsjlbmz4ag5o32ytojordu:
|
||||
/typechain/8.1.1_cnngzrja2umb46xxazlucyx2qu:
|
||||
resolution: {integrity: sha512-uF/sUvnXTOVF2FHKhQYnxHk4su4JjZR8vr4mA2mBaRwHTbwh0jIlqARz9XJr1tA0l7afJGvEa1dTSi4zt039LQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@ -19406,9 +19510,9 @@ packages:
|
||||
lodash: 4.17.21
|
||||
mkdirp: 1.0.4
|
||||
prettier: 2.8.4
|
||||
ts-command-line-args: 2.4.2_g4n3hsjlbmz4ag5o32ytojordu
|
||||
ts-essentials: 7.0.3_typescript@4.7.4
|
||||
typescript: 4.7.4
|
||||
ts-command-line-args: 2.4.2_cnngzrja2umb46xxazlucyx2qu
|
||||
ts-essentials: 7.0.3_typescript@4.9.5
|
||||
typescript: 4.9.5
|
||||
transitivePeerDependencies:
|
||||
- jasmine
|
||||
- jest
|
||||
@ -19777,14 +19881,14 @@ packages:
|
||||
vite: 4.0.4_@types+node@16.11.59
|
||||
dev: true
|
||||
|
||||
/vite-tsconfig-paths/4.0.3_trrwuuiz4f5khno7hdf3cjz2ky:
|
||||
/vite-tsconfig-paths/4.0.3_egung5nfepmolqa7uavvqho3gq:
|
||||
resolution: {integrity: sha512-gRO2Q/tOkV+9kMht5tz90+IaEKvW2zCnvwJV3tp2ruPNZOTM5rF+yXorJT4ggmAMYEaJ3nyXjx5P5jY5FwiZ+A==}
|
||||
peerDependencies:
|
||||
vite: '>2.0.0-0'
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
globrex: 0.1.2
|
||||
tsconfck: 2.0.2_typescript@4.7.4
|
||||
tsconfck: 2.0.2_typescript@4.9.5
|
||||
vite: 4.0.4_@types+node@16.11.59
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
Loading…
Reference in New Issue
Block a user