From 71f1d68860ecad0eeee1481e726642c92f15eb9c Mon Sep 17 00:00:00 2001 From: Pavel Murygin Date: Fri, 31 Mar 2023 12:03:03 +0400 Subject: [PATCH 01/32] Refactored connections. Ephemeral networks work --- .../api/src/compilerSupport/implementation.ts | 23 +- packages/client/api/src/index.ts | 31 +- packages/client/api/src/util.ts | 12 +- packages/client/js-client.node/package.json | 61 +-- packages/client/js-client.node/src/index.ts | 9 +- .../js-client.web.standalone/package.json | 69 +-- .../js-client.web.standalone/src/index.ts | 9 +- packages/core/interfaces/src/fluenceClient.ts | 39 +- packages/core/js-peer/package.json | 2 +- packages/core/js-peer/src/__test__/util.ts | 126 ++++++ .../core/js-peer/src/clientPeer/ClientPeer.ts | 94 ++++ .../src/clientPeer/__test__/client.spec.ts | 181 ++++++++ .../__test__/connection.ts | 2 + .../js-peer/src/clientPeer/checkConnection.ts | 102 +++++ .../src/compilerSupport/callFunction.ts | 11 +- .../js-peer/src/compilerSupport/services.ts | 12 +- packages/core/js-peer/src/connection/index.ts | 129 ++++-- .../src/ephemeral/__test__/ephemeral.spec.ts | 80 ++++ packages/core/js-peer/src/ephemeral/client.ts | 20 + .../ephemeral.ts => ephemeral/network.ts} | 244 +++++----- packages/core/js-peer/src/interfaces/index.ts | 13 +- .../core/js-peer/src/js-peer/FluencePeer.ts | 416 +++++------------ packages/core/js-peer/src/js-peer/Particle.ts | 10 +- .../js-peer/__test__/integration/avm.spec.ts | 9 +- .../__test__/integration/jsonBuiltin.spec.ts | 8 +- .../__test__/integration/marine-js.spec.ts | 2 +- .../js-peer/__test__/integration/peer.spec.ts | 417 ------------------ .../__test__/integration/sigService.spec.ts | 9 +- .../__test__/integration/smokeTest.spec.ts | 61 --- .../js-peer/__test__/integration/srv.spec.ts | 2 +- .../js-peer/src/js-peer/__test__/peer.spec.ts | 178 ++++++++ .../src/js-peer/__test__/unit/ast.spec.ts | 8 +- .../__test__/unit/ephemeral.spec.ts.skip | 84 ---- .../core/js-peer/src/js-peer/__test__/util.ts | 83 ---- .../js-peer/src/js-peer/_aqua/node-utils.ts | 14 +- .../js-peer/src/js-peer/_aqua/services.ts | 14 +- .../src/js-peer/_aqua/single-module-srv.ts | 14 +- .../core/js-peer/src/js-peer/_aqua/util.ts | 9 - .../core/js-peer/src/js-peer/builtins/Sig.ts | 17 +- .../src/js-peer/builtins/SingleModuleSrv.ts | 2 +- .../js-peer/src/js-peer/builtins/common.ts | 10 +- .../core/js-peer/src/js-peer/libp2pUtils.ts | 19 + .../core/js-peer/src/js-peer/serviceUtils.ts | 18 + packages/core/js-peer/src/js-peer/utils.ts | 133 +----- .../core/js-peer/src/js-peer/utilsForNode.ts | 5 - .../core/js-peer/src/marine/worker/index.ts | 2 +- pnpm-lock.yaml | 4 + 47 files changed, 1362 insertions(+), 1455 deletions(-) create mode 100644 packages/core/js-peer/src/__test__/util.ts create mode 100644 packages/core/js-peer/src/clientPeer/ClientPeer.ts create mode 100644 packages/core/js-peer/src/clientPeer/__test__/client.spec.ts rename packages/core/js-peer/src/{js-peer => clientPeer}/__test__/connection.ts (93%) create mode 100644 packages/core/js-peer/src/clientPeer/checkConnection.ts create mode 100644 packages/core/js-peer/src/ephemeral/__test__/ephemeral.spec.ts create mode 100644 packages/core/js-peer/src/ephemeral/client.ts rename packages/core/js-peer/src/{js-peer/ephemeral.ts => ephemeral/network.ts} (50%) delete mode 100644 packages/core/js-peer/src/js-peer/__test__/integration/peer.spec.ts delete mode 100644 packages/core/js-peer/src/js-peer/__test__/integration/smokeTest.spec.ts create mode 100644 packages/core/js-peer/src/js-peer/__test__/peer.spec.ts delete mode 100644 packages/core/js-peer/src/js-peer/__test__/unit/ephemeral.spec.ts.skip delete mode 100644 packages/core/js-peer/src/js-peer/__test__/util.ts delete mode 100644 packages/core/js-peer/src/js-peer/_aqua/util.ts create mode 100644 packages/core/js-peer/src/js-peer/libp2pUtils.ts create mode 100644 packages/core/js-peer/src/js-peer/serviceUtils.ts delete mode 100644 packages/core/js-peer/src/js-peer/utilsForNode.ts diff --git a/packages/client/api/src/compilerSupport/implementation.ts b/packages/client/api/src/compilerSupport/implementation.ts index d9442f18d..f684f4ea7 100644 --- a/packages/client/api/src/compilerSupport/implementation.ts +++ b/packages/client/api/src/compilerSupport/implementation.ts @@ -31,11 +31,6 @@ import { getFluenceInterface } from '../util.js'; */ export const callFunction = async (rawFnArgs: Array, def: FunctionCallDef, script: string): Promise => { 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({ @@ -56,14 +51,6 @@ export const callFunction = async (rawFnArgs: Array, def: FunctionCallDef, export const registerService = async (args: any[], def: ServiceDef): Promise => { 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 +91,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 +137,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; } diff --git a/packages/client/api/src/index.ts b/packages/client/api/src/index.ts index c2cd21527..bd451676d 100644 --- a/packages/client/api/src/index.ts +++ b/packages/client/api/src/index.ts @@ -1,12 +1,6 @@ import { getFluenceInterface, getFluenceInterfaceFromGlobalThis } from './util.js'; -import { - IFluenceClient, - ClientOptions, - RelayOptions, - ConnectionState, - ConnectionStates, -} from '@fluencelabs/interfaces'; -export type { IFluenceClient, ClientOptions, CallParams } from '@fluencelabs/interfaces'; +import { IFluenceClient, ClientConfig, RelayOptions, ConnectionState, ConnectionStates } from '@fluencelabs/interfaces'; +export type { IFluenceClient, ClientConfig as ClientOptions, CallParams } from '@fluencelabs/interfaces'; export { ArrayType, @@ -42,11 +36,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 => { + connect: async (relay: RelayOptions, config?: ClientConfig): Promise => { const fluence = await getFluenceInterface(); - return fluence.defaultClient.connect(relay, options); + const client = await fluence.clientFactory(relay, config); + fluence.defaultClient = client; }, /** @@ -54,7 +49,7 @@ export const Fluence = { */ disconnect: async (): Promise => { const fluence = await getFluenceInterface(); - return fluence.defaultClient.disconnect(); + return fluence.defaultClient?.disconnect(); }, /** @@ -62,11 +57,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,7 +74,7 @@ export const Fluence = { */ getClient: async (): Promise => { const fluence = await getFluenceInterface(); - return fluence.defaultClient; + return fluence.defaultClient!; }, }; @@ -85,7 +82,7 @@ export const Fluence = { * Low level API. Generally you need Fluence.connect() instead. * @returns IFluenceClient instance */ -export const createClient = async (): Promise => { +export const createClient = async (relay: RelayOptions, config?: ClientConfig): Promise => { const fluence = await getFluenceInterface(); - return fluence.clientFactory(); + return await fluence.clientFactory(relay, config); }; diff --git a/packages/client/api/src/util.ts b/packages/client/api/src/util.ts index d81498f0e..32033886b 100644 --- a/packages/client/api/src/util.ts +++ b/packages/client/api/src/util.ts @@ -1,8 +1,14 @@ -import type { CallAquaFunction, IFluenceClient, RegisterService } from '@fluencelabs/interfaces'; +import type { + CallAquaFunction, + ClientConfig, + IFluenceClient, + RegisterService, + RelayOptions, +} from '@fluencelabs/interfaces'; type PublicFluenceInterface = { - clientFactory: () => IFluenceClient; - defaultClient: IFluenceClient; + defaultClient: IFluenceClient | undefined; + clientFactory: (relay: RelayOptions, config?: ClientConfig) => Promise; callAquaFunction: CallAquaFunction; registerService: RegisterService; }; diff --git a/packages/client/js-client.node/package.json b/packages/client/js-client.node/package.json index 968b2faf0..4f88cb9cf 100644 --- a/packages/client/js-client.node/package.json +++ b/packages/client/js-client.node/package.json @@ -1,33 +1,34 @@ { - "name": "@fluencelabs/js-client.node", - "version": "0.6.6", - "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.6", + "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.5", + "@fluencelabs/interfaces": "0.7.3", + "@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.5", - "@fluencelabs/avm": "0.35.4", - "@fluencelabs/marine-js": "0.3.45", - "platform": "1.3.6" - }, - "devDependencies": { - "@types/platform": "1.3.4" - } } diff --git a/packages/client/js-client.node/src/index.ts b/packages/client/js-client.node/src/index.ts index 7e200bbec..1bdff1e87 100644 --- a/packages/client/js-client.node/src/index.ts +++ b/packages/client/js-client.node/src/index.ts @@ -1,6 +1,7 @@ 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'; @@ -21,19 +22,19 @@ export const defaultNames = { }, }; -export const createClient = () => { +const createClient = async (relay: RelayOptions, config: ClientConfig): Promise => { 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); + return new ClientPeer(peerConfig, relayConfig, keyPair, marine, avm); }; const publicFluenceInterface = { clientFactory: createClient, - defaultClient: createClient(), callAquaFunction, registerService, }; diff --git a/packages/client/js-client.web.standalone/package.json b/packages/client/js-client.web.standalone/package.json index 09638e9bb..14f2efd58 100644 --- a/packages/client/js-client.web.standalone/package.json +++ b/packages/client/js-client.web.standalone/package.json @@ -1,36 +1,37 @@ { - "name": "@fluencelabs/js-client.web.standalone", - "version": "0.13.5", - "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.5", - "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.5", + "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.5", + "@fluencelabs/interfaces": "0.7.3", + "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" + } } diff --git a/packages/client/js-client.web.standalone/src/index.ts b/packages/client/js-client.web.standalone/src/index.ts index 0ecd3a2a1..b44309c2a 100644 --- a/packages/client/js-client.web.standalone/src/index.ts +++ b/packages/client/js-client.web.standalone/src/index.ts @@ -1,23 +1,24 @@ -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 { 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 => { 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); + return new ClientPeer(peerConfig, relayConfig, keyPair, marine, avm); }; const publicFluenceInterface = { clientFactory: createClient, - defaultClient: createClient(), callAquaFunction, registerService, }; diff --git a/packages/core/interfaces/src/fluenceClient.ts b/packages/core/interfaces/src/fluenceClient.ts index 7b67c5fac..403daf568 100644 --- a/packages/core/interfaces/src/fluenceClient.ts +++ b/packages/core/interfaces/src/fluenceClient.ts @@ -1,6 +1,4 @@ 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'; /** @@ -52,16 +50,13 @@ type Node = { multiaddr: string; }; -// TODO: either drop support for this exact type or get it back - /** * 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; export type KeyTypes = 'RSA' | 'Ed25519' | 'secp256k1'; @@ -73,7 +68,7 @@ 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 @@ -113,18 +108,21 @@ export interface ClientOptions { }; } -export type FluenceStartConfig = ClientOptions & { relay: RelayOptions }; - export const ConnectionStates = ['disconnected', 'connecting', 'connected', 'disconnecting'] as const; -export type ConnectionState = typeof ConnectionStates[number]; +export type ConnectionState = (typeof ConnectionStates)[number]; -export interface IFluenceClient { +export interface IFluenceInternalApi { + /** + * Internal API + */ + internals: any; +} + +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; + connect: () => Promise; /** * Disconnect from the Fluence network @@ -150,29 +148,20 @@ 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 { + peer: IFluenceInternalApi; def: FunctionCallDef; script: string; config: FnConfig; - peer: IFluenceClient; args: { [key: string]: any }; } export type CallAquaFunction = (args: CallAquaFunctionArgs) => Promise; export interface RegisterServiceArgs { - peer: IFluenceClient; + peer: IFluenceInternalApi; def: ServiceDef; serviceId: string | undefined; service: any; diff --git a/packages/core/js-peer/package.json b/packages/core/js-peer/package.json index 32e6f0c8d..034f76a2b 100644 --- a/packages/core/js-peer/package.json +++ b/packages/core/js-peer/package.json @@ -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", diff --git a/packages/core/js-peer/src/__test__/util.ts b/packages/core/js-peer/src/__test__/util.ts new file mode 100644 index 000000000..02548dc0f --- /dev/null +++ b/packages/core/js-peer/src/__test__/util.ts @@ -0,0 +1,126 @@ +import * as api from '@fluencelabs/aqua-api/aqua-api.js'; + +import { promises as fs } from 'fs'; +import { FluencePeer, PeerConfig } from '../js-peer/FluencePeer.js'; +import { Particle } from '../js-peer/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 '../js-peer/avm.js'; +import { WorkerLoader } from '../marine/worker-script/workerLoader.js'; +import { KeyPair } from '../keypair/index.js'; +import { IConnection } from '../interfaces/index.js'; +import { Subject, Subscribable } from 'rxjs'; +import { WrapFnIntoServiceCall } from '../js-peer/serviceUtils.js'; +import { ClientPeer, makeClientPeerConfig } from '../clientPeer/ClientPeer.js'; +import { WasmLoaderFromNpm } from '../marine/deps-loader/node.js'; + +export const registerHandlersHelper = ( + peer: FluencePeer, + particle: Particle, + handlers: Record>, +) => { + 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; +export type CompiledFile = { + functions: { [key: string]: CompiledFnCall }; + services: { [key: string]: ServiceDef }; +}; + +export const compileAqua = async (aquaFile: string): Promise => { + 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 = new Subject(); + + sendParticle(nextPeerIds: string[], particle: Particle): Promise { + return Promise.resolve(); + } +} + +export class TestPeer extends FluencePeer { + constructor(keyPair: KeyPair, connection: IConnection) { + const config = {}; + 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); + super(config, keyPair, marine, 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) => { + 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, +) => { + 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(); + } +}; diff --git a/packages/core/js-peer/src/clientPeer/ClientPeer.ts b/packages/core/js-peer/src/clientPeer/ClientPeer.ts new file mode 100644 index 000000000..af9cd2ae1 --- /dev/null +++ b/packages/core/js-peer/src/clientPeer/ClientPeer.ts @@ -0,0 +1,94 @@ +import { ClientConfig, ConnectionState, IFluenceClient, PeerIdB58, RelayOptions } from '@fluencelabs/interfaces'; +import { RelayConnection, RelayConnectionConfig } from '../connection/index.js'; +import { IAvmRunner, IMarine } from '../interfaces/index.js'; +import { fromOpts, KeyPair } from '../keypair/index.js'; +import { FluencePeer, PeerConfig } from '../js-peer/FluencePeer.js'; +import { relayOptionToMultiaddr } from '../js-peer/libp2pUtils.js'; + +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: config?.debug, + defaultTtlMs: config?.defaultTtlMs, + }, + relayConfig: { + peerId: keyPair.getLibp2pPeerId(), + relayAddress: relayAddress, + dialTimeoutMs: config?.connectionOptions?.dialTimeoutMs, + }, + keyPair: keyPair, + }; +}; + +export class ClientPeer extends FluencePeer implements IFluenceClient { + private relayPeerId: PeerIdB58; + private relayConnection: RelayConnection; + + constructor( + peerConfig: PeerConfig, + relayConfig: RelayConnectionConfig, + keyPair: KeyPair, + marine: IMarine, + avmRunner: IAvmRunner, + ) { + const relayConnection = new RelayConnection(relayConfig); + + super(peerConfig, keyPair, marine, 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 { + this.changeConnectionState('connecting'); + await super.start(); + await this.relayConnection.start(); + // TODO: check connection here + this.changeConnectionState('connected'); + } + + /** + * Disconnect from the Fluence network + */ + async disconnect(): Promise { + this.changeConnectionState('disconnecting'); + await this.relayConnection.stop(); + await this.stop(); + this.changeConnectionState('disconnected'); + } +} diff --git a/packages/core/js-peer/src/clientPeer/__test__/client.spec.ts b/packages/core/js-peer/src/clientPeer/__test__/client.spec.ts new file mode 100644 index 000000000..e15e5eddb --- /dev/null +++ b/packages/core/js-peer/src/clientPeer/__test__/client.spec.ts @@ -0,0 +1,181 @@ +import { it, describe, expect } from 'vitest'; +import { CallServiceData } from '../../interfaces/commonTypes.js'; +import { handleTimeout } from '../../js-peer/Particle.js'; +import { doNothing } from '../../js-peer/serviceUtils.js'; +import { registerHandlersHelper, withClient } from '../../__test__/util.js'; +import { checkConnection } from '../checkConnection.js'; +import { nodes, RELAY } from './connection.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((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, doNothing); + }); + + await expect(promise).rejects.toMatch( + 'Particle is expected to be sent to only the single peer (relay which client is connected to)', + ); + }); + }); +}); diff --git a/packages/core/js-peer/src/js-peer/__test__/connection.ts b/packages/core/js-peer/src/clientPeer/__test__/connection.ts similarity index 93% rename from packages/core/js-peer/src/js-peer/__test__/connection.ts rename to packages/core/js-peer/src/clientPeer/__test__/connection.ts index f3fafa1d5..0afb0a120 100644 --- a/packages/core/js-peer/src/js-peer/__test__/connection.ts +++ b/packages/core/js-peer/src/clientPeer/__test__/connection.ts @@ -15,3 +15,5 @@ export const nodes = [ peerId: '12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3', }, ]; + +export const RELAY = nodes[0].multiaddr; diff --git a/packages/core/js-peer/src/clientPeer/checkConnection.ts b/packages/core/js-peer/src/clientPeer/checkConnection.ts new file mode 100644 index 000000000..e59229957 --- /dev/null +++ b/packages/core/js-peer/src/clientPeer/checkConnection.ts @@ -0,0 +1,102 @@ +import { ClientPeer } from './ClientPeer.js'; + +import { logger } from '../util/logger.js'; +import { WrapFnIntoServiceCall } from '../js-peer/serviceUtils.js'; +import { handleTimeout } from '../js-peer/Particle.js'; + +const log = logger('connection'); + +/** + * Checks the network connection by sending a ping-like request to relay node + * @param { ClientPeer } peer - The Fluence Client instance. + */ +export const checkConnection = async (peer: ClientPeer, ttl?: number): Promise => { + const msg = Math.random().toString(36).substring(7); + + const promise = new Promise((resolve, reject) => { + const script = ` + (xor + (seq + (call %init_peer_id% ("load" "relay") [] init_relay) + (seq + (call %init_peer_id% ("load" "msg") [] msg) + (seq + (call init_relay ("op" "identity") [msg] 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.createNewParticle(script, ttl); + + if (particle instanceof Error) { + return reject(particle.message); + } + + peer.internals.regHandler.forParticle( + particle.id, + 'load', + 'relay', + WrapFnIntoServiceCall(() => { + return peer.getRelayPeerId(); + }), + ); + + peer.internals.regHandler.forParticle( + particle.id, + 'load', + 'msg', + WrapFnIntoServiceCall(() => { + return msg; + }), + ); + + peer.internals.regHandler.forParticle( + particle.id, + 'callback', + 'callback', + WrapFnIntoServiceCall((args) => { + const [val] = args; + setTimeout(() => { + resolve(val); + }, 0); + return {}; + }), + ); + + peer.internals.regHandler.forParticle( + particle.id, + 'callback', + 'error', + WrapFnIntoServiceCall((args) => { + const [error] = args; + setTimeout(() => { + reject(error); + }, 0); + return {}; + }), + ); + + peer.internals.initiateParticle( + particle, + handleTimeout(() => { + reject('particle timed out'); + }), + ); + }); + + try { + const result = await promise; + if (result != msg) { + log.error("unexpected behavior. 'identity' must return the passed arguments."); + } + return true; + } catch (e) { + log.error('error on establishing connection. Relay: %s error: %j', e, peer.getRelayPeerId()); + return false; + } +}; diff --git a/packages/core/js-peer/src/compilerSupport/callFunction.ts b/packages/core/js-peer/src/compilerSupport/callFunction.ts index 945b61693..3ad78be2a 100644 --- a/packages/core/js-peer/src/compilerSupport/callFunction.ts +++ b/packages/core/js-peer/src/compilerSupport/callFunction.ts @@ -1,13 +1,4 @@ -import { - ArrowWithoutCallbacks, - FnConfig, - FunctionCallDef, - NonArrowType, - getArgumentTypes, - isReturnTypeVoid, - IFluenceClient, - CallAquaFunction, -} from '@fluencelabs/interfaces'; +import { getArgumentTypes, isReturnTypeVoid, CallAquaFunction } from '@fluencelabs/interfaces'; import { injectRelayService, diff --git a/packages/core/js-peer/src/compilerSupport/services.ts b/packages/core/js-peer/src/compilerSupport/services.ts index f71b506da..0d9072d58 100644 --- a/packages/core/js-peer/src/compilerSupport/services.ts +++ b/packages/core/js-peer/src/compilerSupport/services.ts @@ -6,12 +6,12 @@ import { CallServiceData, GenericCallServiceHandler, ResultCodes } from '../inte import { aquaArgs2Ts, responseServiceValue2ts, returnType2Aqua, ts2aqua } from './conversions.js'; import { - IFluenceClient, CallParams, ArrowWithoutCallbacks, FunctionCallConstants, FunctionCallDef, NonArrowType, + IFluenceInternalApi, } from '@fluencelabs/interfaces'; export interface ServiceDescription { @@ -23,7 +23,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 +168,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); }; diff --git a/packages/core/js-peer/src/connection/index.ts b/packages/core/js-peer/src/connection/index.ts index da601d1ed..7a7b8cc33 100644 --- a/packages/core/js-peer/src/connection/index.ts +++ b/packages/core/js-peer/src/connection/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { PeerIdB58 } from '@fluencelabs/interfaces'; -import { FluenceConnection, ParticleHandler } from '../interfaces/index.js'; +import { ParticleHandler, IModule, IConnection } 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 +25,25 @@ 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, Subscribable } from 'rxjs'; +import { Particle } from '../js-peer/Particle.js'; +import { throwIfHasNoPeerId } from '../js-peer/libp2pUtils.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 +52,65 @@ 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 IModule, IConnection { + private relayAddress: Multiaddr; + private lib2p2Peer: Libp2p | null = null; + private handleOptions: { + maxInboundStreams: number; + maxOutboundStreams: number; + }; + + constructor(private options: RelayConnectionConfig) { + this.relayAddress = multiaddr(this.options.relayAddress); + throwIfHasNoPeerId(this.relayAddress); + this.handleOptions = { + maxInboundStreams: this.options.maxInboundStreams ?? 1024, + maxOutboundStreams: this.options.maxOutboundStreams ?? 1024, + }; + } + + getRelayPeerId(): string { + // since we check for peer id in constructor, we can safely use ! here + return this.relayAddress.getPeerId()!; + } + + supportsRelay(): boolean { + return true; } - private _connection?: Connection; + particleSource = new Subject(); + + async start(): Promise { + // check if already started + if (this.lib2p2Peer !== null) { + return; + } - static async createConnection(options: FluenceConnectionOptions): Promise { const lib2p2Peer = await createLibp2p({ - peerId: options.peerId, + peerId: this.options.peerId, transports: [ webSockets({ filter: all, @@ -85,28 +120,27 @@ export class RelayConnection extends FluenceConnection { connectionEncryption: [noise()], }); - 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 { + // 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: Particle): Promise { + if (this.lib2p2Peer === null) { + throw new Error('Relay connection is not started'); + } - async sendParticle(nextPeerIds: PeerIdB58[], particle: string): Promise { - 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 +157,25 @@ 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(particle.toString())], // @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 +188,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 +200,17 @@ export class RelayConnection extends FluenceConnection { }, ); }, - handleOptions, + this.handleOptions, ); - 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; } diff --git a/packages/core/js-peer/src/ephemeral/__test__/ephemeral.spec.ts b/packages/core/js-peer/src/ephemeral/__test__/ephemeral.spec.ts new file mode 100644 index 000000000..37e8ef81a --- /dev/null +++ b/packages/core/js-peer/src/ephemeral/__test__/ephemeral.spec.ts @@ -0,0 +1,80 @@ +import { it, describe, expect, beforeEach, afterEach } from 'vitest'; +import { ResultCodes } from '../../interfaces/commonTypes.js'; +import { FluencePeer } from '../../js-peer/FluencePeer.js'; +import { KeyPair } from '../../keypair/index.js'; +import { TestPeer } from '../../__test__/util.js'; +import { EphemeralNetworkClient } from '../client.js'; + +import { EphemeralNetwork, defaultConfig } from '../network.js'; + +let en: EphemeralNetwork; +let client: FluencePeer; +const relay = defaultConfig.peers[0].peerId; + +describe('Ephemeral networks tests', () => { + beforeEach(async () => { + en = new EphemeralNetwork(defaultConfig); + await en.up(); + + const kp = await KeyPair.randomEd25519(); + client = new EphemeralNetworkClient({}, 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((resolve) => { + client.internals.regHandler.forParticle(particle.id, 'test', 'test', (req) => { + resolve('success'); + return { + result: 'test', + retCode: ResultCodes.success, + }; + }); + }); + + // act + client.internals.initiateParticle(particle, () => {}); + + // assert + await expect(promise).resolves.toBe('success'); + }, 20000); +}); diff --git a/packages/core/js-peer/src/ephemeral/client.ts b/packages/core/js-peer/src/ephemeral/client.ts new file mode 100644 index 000000000..c8d6bae44 --- /dev/null +++ b/packages/core/js-peer/src/ephemeral/client.ts @@ -0,0 +1,20 @@ +import { PeerIdB58 } from '@fluencelabs/interfaces'; +import { MarineBasedAvmRunner } from '../js-peer/avm.js'; +import { FluencePeer, PeerConfig } from '../js-peer/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'; + +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, avm, conn); + } +} diff --git a/packages/core/js-peer/src/js-peer/ephemeral.ts b/packages/core/js-peer/src/ephemeral/network.ts similarity index 50% rename from packages/core/js-peer/src/js-peer/ephemeral.ts rename to packages/core/js-peer/src/ephemeral/network.ts index 3718ce55d..0447be2a0 100644 --- a/packages/core/js-peer/src/js-peer/ephemeral.ts +++ b/packages/core/js-peer/src/ephemeral/network.ts @@ -1,14 +1,19 @@ import { PeerIdB58 } from '@fluencelabs/interfaces'; -import { FluenceConnection, ParticleHandler } from '../interfaces/index.js'; -import { fromBase64Sk } from '../keypair/index.js'; -import { FluencePeer } from './FluencePeer.js'; +import { ParticleHandler, IConnection, IAvmRunner, IMarine } from '../interfaces/index.js'; +import { fromBase64Sk, KeyPair } from '../keypair/index.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'; +import { Subject } from 'rxjs'; +import { Particle } from '../js-peer/Particle.js'; + +import { WasmLoaderFromNpm } from '../marine/deps-loader/node.js'; +import { MarineBasedAvmRunner } from '../js-peer/avm.js'; +import { FluencePeer } from '../js-peer/FluencePeer.js'; + +const log = logger('ephemeral'); interface EphemeralConfig { peers: Array<{ @@ -17,16 +22,6 @@ interface EphemeralConfig { }>; } -interface PeerAdapter { - isEphemeral: boolean; - peer: FluencePeer; - peerId: PeerIdB58; - onIncoming: ParticleHandler; - connections: Set; -} - -const log = logger('ephemeral'); - export const defaultConfig = { peers: [ { @@ -112,67 +107,138 @@ export const defaultConfig = { ], }; +export interface IEphemeralConnection extends IConnection { + readonly selfPeerId: PeerIdB58; + readonly connections: Map; + receiveParticle(particle: Particle): void; +} + +export class EphemeralConnection implements IConnection, IEphemeralConnection { + readonly selfPeerId: PeerIdB58; + readonly connections: Map = 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(); + + receiveParticle(particle: Particle): void { + this.particleSource.next(Particle.fromString(particle.toString())); + } + + async sendParticle(nextPeerIds: string[], particle: Particle): Promise { + 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: IMarine, avm: IAvmRunner) { + const conn = new EphemeralConnection(keyPair.getPeerId()); + super({}, keyPair, marine, 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 = new Map(); + private peers: Map = new Map(); - constructor(public readonly config: EphemeralConfig) {} + 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 { 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 => { - this._send(peer.getStatus().peerId!, nextPeerIds, particle); - }; const kp = await fromBase64Sk(x.sk); - if (kp.getPeerId() !== x.peerId) { + 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}`); } - await peer.init({}, kp); - let handler: ParticleHandler | null = null; - const connectionCtor = class extends FluenceConnection { - relayPeerId = null; + return new EphemeralPeer(kp, marine, avm); + }); - async connect(onIncomingParticle: ParticleHandler): Promise { - handler = onIncomingParticle; - } + const peers = await Promise.all(promises); - async disconnect(): Promise { - handler = null; + for (let i = 0; i < peers.length; i++) { + for (let j = 0; j < i; j++) { + if (i === j) { + continue; } - sendParticle = sendParticle; - }; + peers[i].ephemeralConnection.connectToOther(peers[j].ephemeralConnection); + } + } - await peer._connect(new connectionCtor()); + const startPromises = peers.map((x) => x.start()); + await Promise.all(startPromises); - 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...'); + for (let p of peers) { + this.peers.set(p.keyPair.getPeerId(), p); + } } /** @@ -180,73 +246,25 @@ export class EphemeralNetwork { */ async down(): Promise { 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(); + 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(); + 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`); + getRelayConnection(peerId: PeerIdB58, relayPeerId: PeerIdB58): IConnection { + const relay = this.peers.get(relayPeerId); + if (relay === undefined) { + throw new Error(`Peer ${relayPeerId} is not found`); } - const connectionCtor = class extends FluenceConnection { - relayPeerId = relay; - - async connect(onIncomingParticle: ParticleHandler): Promise { - 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 { - const peerId = peer.getStatus().peerId!; - relayPeer.connections.delete(peerId); - me._peers.delete(peerId); - } - async sendParticle(nextPeerIds: string[], particle: string): Promise { - 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); - } + const res = new EphemeralConnection(peerId); + res.connectToOther(relay.ephemeralConnection); + return res; } } diff --git a/packages/core/js-peer/src/interfaces/index.ts b/packages/core/js-peer/src/interfaces/index.ts index 1617f513f..cbc46e570 100644 --- a/packages/core/js-peer/src/interfaces/index.ts +++ b/packages/core/js-peer/src/interfaces/index.ts @@ -14,23 +14,34 @@ * limitations under the License. */ +import type { Observable, Subscribable } from 'rxjs'; import type { PeerIdB58 } from '@fluencelabs/interfaces'; -import type { JSONArray, JSONObject, LogLevel } from '@fluencelabs/marine-js/dist/types'; +import type { JSONArray, JSONObject } from '@fluencelabs/marine-js/dist/types'; import type { RunParameters, CallResultsArray, InterpreterResult } from '@fluencelabs/avm'; // @ts-ignore import type { WorkerImplementation } from 'threads/dist/types/master'; +import { Particle } from '../js-peer/Particle.js'; export type ParticleHandler = (particle: string) => void; /** * Base class for connectivity layer to Fluence Network */ +/* export abstract class FluenceConnection { abstract readonly relayPeerId: PeerIdB58 | null; abstract connect(onIncomingParticle: ParticleHandler): Promise; abstract disconnect(): Promise; abstract sendParticle(nextPeerIds: PeerIdB58[], particle: string): Promise; } +*/ + +export interface IConnection { + particleSource: Subscribable; + sendParticle(nextPeerIds: PeerIdB58[], particle: Particle): Promise; + getRelayPeerId(): PeerIdB58; + supportsRelay(): boolean; +} export interface IMarine extends IModule { createService(serviceModule: SharedArrayBuffer | Buffer, serviceId: string): Promise; diff --git a/packages/core/js-peer/src/js-peer/FluencePeer.ts b/packages/core/js-peer/src/js-peer/FluencePeer.ts index 4c5bb5043..7ff6ea7f3 100644 --- a/packages/core/js-peer/src/js-peer/FluencePeer.ts +++ b/packages/core/js-peer/src/js-peer/FluencePeer.ts @@ -15,26 +15,18 @@ */ import 'buffer'; -import { RelayConnection } from '../connection/index.js'; -import { FluenceConnection, IAvmRunner, IMarine } from '../interfaces/index.js'; -import { fromOpts, KeyPair } from '../keypair/index.js'; +import { IAvmRunner, IMarine, IConnection } from '../interfaces/index.js'; +import { 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 type { PeerIdB58 } 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 { jsonify, isString } from './utils.js'; +import { concatMap, filter, pipe, Subject, tap, Unsubscribable } from 'rxjs'; import { builtInServices } from './builtins/common.js'; import { defaultSigGuard, Sig } from './builtins/Sig.js'; import { registerSig } from './_aqua/services.js'; @@ -44,52 +36,48 @@ 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'; +import { ServiceError } from './serviceUtils.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; - }; +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; + }; +}; /** * 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) {} +export abstract class FluencePeer { + constructor( + protected readonly config: PeerConfig, + public readonly keyPair: KeyPair, + protected readonly marine: IMarine, + protected readonly avmRunner: IAvmRunner, + protected readonly connection: IConnection, + ) { + this.defaultTTL = this.config?.defaultTtlMs ?? DEFAULT_TTL; + } /** * Internal contract to cast unknown objects to IFluenceClient. @@ -99,110 +87,56 @@ export class FluencePeer implements IFluenceClient { */ __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, - }; - } + public readonly defaultTTL: number; - if (this.connection === null) { - return { - isInitialized: true, - peerId: this._keyPair.getPeerId(), - isConnected: false, - relayPeerId: null, - }; - } + async start(): Promise { + const peerId = this.keyPair.getPeerId(); - if (this.connection.relayPeerId === null) { - return { - isInitialized: true, - peerId: this._keyPair.getPeerId(), - isConnected: true, - isDirect: true, - relayPeerId: null, - }; + if (this.config?.debug?.printParticleId) { + this.printParticleId = true; } - 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"); - } + await this.marine.start(); + await this.avmRunner.start(); - return this._keyPair.toEd25519PrivateKey(); - } + registerDefaultServices(this); - onConnectionStateChange(handler: (state: ConnectionState) => void): ConnectionState { - this.connectionStateChangeHandler = handler; + 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); - return this.connectionState; - } + registerSrv(this, 'single_module_srv', this._classServices.srv); + registerNodeUtils(this, 'node_utils', new NodeUtils(this)); - /** - * Connect to the Fluence network - * @param relay - relay node to connect to - * @param options - client options - */ - async connect(relay: RelayOptions, options?: ClientOptions): Promise { - return this.start({ relay, ...options }); - } + this.particleSourceSubscription = this.connection.particleSource.subscribe({ + next: (p) => { + this._incomingParticles.next({ particle: p, onStageChange: () => {} }); + }, + }); - /** - * Disconnect from the Fluence network - */ - disconnect(): Promise { - return this.stop(); + this._startParticleProcessing(); + this.isInitialized = true; } /** - * 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 + * Un-initializes the peer: stops all the underlying workflows, stops the Aqua VM + * and disconnects from the Fluence network */ - async start(config: PeerConfig = {}): Promise { - 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'); - } + async stop() { + this.particleSourceSubscription?.unsubscribe(); + this._stopParticleProcessing(); + await this.marine.stop(); + await this.avmRunner.stop(); + this._classServices = undefined; - getServices() { - if (this._classServices === undefined) { - throw new Error(`Can't get services: peer is not initialized`); - } - return { - ...this._classServices, - }; + this._particleSpecificHandlers.clear(); + this._commonHandlers.clear(); + this._marineServices.clear(); + this.isInitialized = false; } /** @@ -235,22 +169,13 @@ export class FluencePeer implements IFluenceClient { } /** - * Un-initializes the peer: stops all the underlying workflows, stops the Aqua VM - * and disconnects from the Fluence network + * Creates a new particle originating from the local peer + * @param script - particle's air script + * @param ttl - particle's time to live + * @returns new particle */ - 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'); + createNewParticle(script: string, ttl: number = this.defaultTTL): Particle { + return Particle.createNew(script, this.keyPair.getPeerId(), ttl); } // internal api @@ -260,14 +185,18 @@ export class FluencePeer implements IFluenceClient { */ get internals() { return { - getConnectionState: () => this.connectionState, + getServices: () => this._classServices!, - getRelayPeerId: () => this.getStatus().relayPeerId, + getRelayPeerId: () => { + if (this.connection.supportsRelay()) { + return this.connection.getRelayPeerId(); + } - parseAst: async (air: string): Promise<{ success: boolean; data: any }> => { - const status = this.getStatus(); + throw new Error('Relay is not supported by the current connection'); + }, - if (!status.isInitialized) { + parseAst: async (air: string): Promise<{ success: boolean; data: any }> => { + if (!this.isInitialized) { new Error("Can't use avm: peer is not initialized"); } @@ -292,37 +221,24 @@ export class FluencePeer implements IFluenceClient { 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); + createNewParticle: (script: string, ttl: number = this.defaultTTL): Particle => { + 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: Particle, onStageChange: (stage: ParticleExecutionStage) => void): void => { - const status = this.getStatus(); - if (!status.isInitialized) { + if (!this.isInitialized) { throw new Error('Cannot initiate new particle: peer is not initialized'); } - if (this._printParticleId) { + 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, @@ -365,65 +281,6 @@ export class FluencePeer implements IFluenceClient { }; } - /** - * @private Subject to change. Do not use this method directly - */ - async init(config: Omit, 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 { - 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 { - 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(); @@ -446,17 +303,11 @@ export class FluencePeer implements IFluenceClient { // Internal peer state - private connection: FluenceConnection | null = null; - private _printParticleId = false; - private _defaultTTL: number = DEFAULT_TTL; - private _keyPair: KeyPair | undefined; - private _timeouts: Array = []; - private _particleQueues = new Map>(); - - private _onIncomingParticle(p: string) { - const particle = Particle.fromString(p); - this._incomingParticles.next({ particle, onStageChange: () => {} }); - } + private isInitialized = false; + private printParticleId = false; + private timeouts: Array = []; + private particleSourceSubscription?: Unsubscribable; + private particleQueues = new Map>(); private _startParticleProcessing() { this._incomingParticles @@ -477,17 +328,17 @@ export class FluencePeer implements IFluenceClient { ) .subscribe((item) => { const p = item.particle; - let particlesQueue = this._particleQueues.get(p.id); + let particlesQueue = this.particleQueues.get(p.id); if (!particlesQueue) { particlesQueue = this._createParticlesProcessingQueue(); - this._particleQueues.set(p.id, particlesQueue); + this.particleQueues.set(p.id, particlesQueue); const timeout = setTimeout(() => { this._expireParticle(item); }, p.actualTtl()); - this._timeouts.push(timeout); + this.timeouts.push(timeout); } particlesQueue.next(item); @@ -495,23 +346,24 @@ export class FluencePeer implements IFluenceClient { this._outgoingParticles.subscribe((item) => { // Do not send particle after the peer has been stopped - if (!this.getStatus().isInitialized) { + if (!this.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); + log.debug( + 'id %s. sending particle into network. Next peer ids: %s', + item.particle.id, + item.nextPeerIds.toString(), + ); + this.connection - ?.sendParticle(item.nextPeerIds, item.particle.toString()) + ?.sendParticle(item.nextPeerIds, item.particle) .then(() => { item.onStageChange({ stage: 'sent' }); }) .catch((e: any) => { log.error('id %s. send failed %j', item.particle.id, e); + item.onStageChange({ stage: 'sendingError' }); }); }); } @@ -524,7 +376,7 @@ export class FluencePeer implements IFluenceClient { item.particle.ttl, ); - this._particleQueues.delete(particleId); + this.particleQueues.delete(particleId); this._particleSpecificHandlers.delete(particleId); item.onStageChange({ stage: 'expired' }); @@ -539,8 +391,7 @@ export class FluencePeer implements IFluenceClient { filterExpiredParticles(this._expireParticle.bind(this)), concatMap(async (item) => { - const status = this.getStatus(); - if (!status.isInitialized || this.marine === undefined) { + if (!this.isInitialized || this.marine === undefined) { // If `.stop()` was called return null to stop particle processing immediately return null; } @@ -555,7 +406,7 @@ export class FluencePeer implements IFluenceClient { const avmCallResult = await this.avmRunner.run( { initPeerId: item.particle.initPeerId, - currentPeerId: status.peerId, + currentPeerId: this.keyPair.getPeerId(), timestamp: item.particle.timestamp, ttl: item.particle.ttl, }, @@ -577,8 +428,8 @@ export class FluencePeer implements IFluenceClient { }), ) .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) { + // If peer was stopped, do not proceed further + if (item === null || !this.isInitialized) { return; } @@ -734,45 +585,13 @@ export class FluencePeer implements IFluenceClient { private _stopParticleProcessing() { // do not hang if the peer has been stopped while some of the timeouts are still being executed - this._timeouts.forEach((timeout) => { + this.timeouts.forEach((timeout) => { clearTimeout(timeout); }); - this._particleQueues.clear(); + this.particleQueues.clear(); } } -async function configToConnection( - keyPair: KeyPair, - connection?: RelayOptions, - dialTimeoutMs?: number, -): Promise { - 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}`; } @@ -795,8 +614,3 @@ function filterExpiredParticles(onParticleExpiration: (item: ParticleQueueItem) filter((x: ParticleQueueItem) => !x.particle.hasExpired()), ); } - -async function makeKeyPair(opts?: KeyPairOptions) { - opts = opts || { type: 'Ed25519', source: 'random' }; - return fromOpts(opts); -} diff --git a/packages/core/js-peer/src/js-peer/Particle.ts b/packages/core/js-peer/src/js-peer/Particle.ts index b5ed88462..fbbd69fbc 100644 --- a/packages/core/js-peer/src/js-peer/Particle.ts +++ b/packages/core/js-peer/src/js-peer/Particle.ts @@ -15,7 +15,7 @@ */ import { fromUint8Array, toUint8Array } from 'js-base64'; -import { CallResultsArray, LogLevel } from '@fluencelabs/avm'; +import { CallResultsArray } from '@fluencelabs/avm'; import { v4 as uuidv4 } from 'uuid'; import { ParticleContext } from '../interfaces/commonTypes.js'; import { Buffer } from 'buffer'; @@ -34,7 +34,7 @@ export class Particle { public initPeerId: string, ) {} - static createNew(script: string, ttl: number, initPeerId: string): Particle { + static createNew(script: string, initPeerId: string, ttl: number): Particle { return new Particle(genUUID(), Date.now(), script, Buffer.from([]), ttl, initPeerId); } @@ -109,6 +109,12 @@ export interface ParticleQueueItem { onStageChange: (state: ParticleExecutionStage) => void; } +export const handleTimeout = (fn: () => void) => (stage: ParticleExecutionStage) => { + if (stage.stage === 'expired') { + fn(); + } +}; + function genUUID() { return uuidv4(); } diff --git a/packages/core/js-peer/src/js-peer/__test__/integration/avm.spec.ts b/packages/core/js-peer/src/js-peer/__test__/integration/avm.spec.ts index 27846db5a..1dda00e1c 100644 --- a/packages/core/js-peer/src/js-peer/__test__/integration/avm.spec.ts +++ b/packages/core/js-peer/src/js-peer/__test__/integration/avm.spec.ts @@ -1,7 +1,6 @@ import { it, describe, expect } from 'vitest'; - -import { handleTimeout } from '../../utils.js'; -import { registerHandlersHelper, withPeer } from '../util.js'; +import { registerHandlersHelper, withPeer } from '../../../__test__/util.js'; +import { handleTimeout } from '../../Particle.js'; describe('Avm spec', () => { it('Simple call', async () => { @@ -10,7 +9,7 @@ describe('Avm spec', () => { const script = ` (call %init_peer_id% ("print" "print") ["1"]) `; - const particle = peer.internals.createNewParticle(script); + const particle = peer.createNewParticle(script); if (particle instanceof Error) { return reject(particle.message); @@ -45,7 +44,7 @@ describe('Avm spec', () => { (call %init_peer_id% ("print" "print") ["2"]) ) `; - const particle = peer.internals.createNewParticle(script); + const particle = peer.createNewParticle(script); if (particle instanceof Error) { return reject(particle.message); diff --git a/packages/core/js-peer/src/js-peer/__test__/integration/jsonBuiltin.spec.ts b/packages/core/js-peer/src/js-peer/__test__/integration/jsonBuiltin.spec.ts index 1748526ff..c297624ec 100644 --- a/packages/core/js-peer/src/js-peer/__test__/integration/jsonBuiltin.spec.ts +++ b/packages/core/js-peer/src/js-peer/__test__/integration/jsonBuiltin.spec.ts @@ -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 { mkTestPeer } from '../../../__test__/util.js'; +import { doNothing } from '../../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 = Particle.createNew(script, peer.keyPair.getPeerId(), 7000); await peer.internals.initiateParticle(p, doNothing); const [nestedFirst, nestedSecond, outerFirst, outerSecond, outerFirstString, outerFirstParsed] = await promise; diff --git a/packages/core/js-peer/src/js-peer/__test__/integration/marine-js.spec.ts b/packages/core/js-peer/src/js-peer/__test__/integration/marine-js.spec.ts index f9ad801c1..d185bb069 100644 --- a/packages/core/js-peer/src/js-peer/__test__/integration/marine-js.spec.ts +++ b/packages/core/js-peer/src/js-peer/__test__/integration/marine-js.spec.ts @@ -3,7 +3,7 @@ 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 '../../../__test__/util.js'; let aqua: any; const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); diff --git a/packages/core/js-peer/src/js-peer/__test__/integration/peer.spec.ts b/packages/core/js-peer/src/js-peer/__test__/integration/peer.spec.ts deleted file mode 100644 index 197fade27..000000000 --- a/packages/core/js-peer/src/js-peer/__test__/integration/peer.spec.ts +++ /dev/null @@ -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((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((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((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((_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 { - return new Promise((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)); - }); -} diff --git a/packages/core/js-peer/src/js-peer/__test__/integration/sigService.spec.ts b/packages/core/js-peer/src/js-peer/__test__/integration/sigService.spec.ts index 21d35c7be..b054fed1b 100644 --- a/packages/core/js-peer/src/js-peer/__test__/integration/sigService.spec.ts +++ b/packages/core/js-peer/src/js-peer/__test__/integration/sigService.spec.ts @@ -5,8 +5,8 @@ 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 { compileAqua, withPeer } from '../../../__test__/util.js'; const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); @@ -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); diff --git a/packages/core/js-peer/src/js-peer/__test__/integration/smokeTest.spec.ts b/packages/core/js-peer/src/js-peer/__test__/integration/smokeTest.spec.ts deleted file mode 100644 index 7f93340c4..000000000 --- a/packages/core/js-peer/src/js-peer/__test__/integration/smokeTest.spec.ts +++ /dev/null @@ -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((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!'); - }); -}); diff --git a/packages/core/js-peer/src/js-peer/__test__/integration/srv.spec.ts b/packages/core/js-peer/src/js-peer/__test__/integration/srv.spec.ts index b9676298d..fe67940ff 100644 --- a/packages/core/js-peer/src/js-peer/__test__/integration/srv.spec.ts +++ b/packages/core/js-peer/src/js-peer/__test__/integration/srv.spec.ts @@ -1,7 +1,7 @@ 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 '../../../__test__/util.js'; const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); let aqua: any; diff --git a/packages/core/js-peer/src/js-peer/__test__/peer.spec.ts b/packages/core/js-peer/src/js-peer/__test__/peer.spec.ts new file mode 100644 index 000000000..ebceda3ee --- /dev/null +++ b/packages/core/js-peer/src/js-peer/__test__/peer.spec.ts @@ -0,0 +1,178 @@ +import { it, describe, expect } from 'vitest'; + +import { isFluencePeer } from '@fluencelabs/interfaces'; +import { mkTestPeer, registerHandlersHelper, withPeer } from '../../__test__/util.js'; +import { handleTimeout } from '../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((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 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((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((_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 { + return new Promise((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)); + }); +} diff --git a/packages/core/js-peer/src/js-peer/__test__/unit/ast.spec.ts b/packages/core/js-peer/src/js-peer/__test__/unit/ast.spec.ts index a8798d791..18abeae67 100644 --- a/packages/core/js-peer/src/js-peer/__test__/unit/ast.spec.ts +++ b/packages/core/js-peer/src/js-peer/__test__/unit/ast.spec.ts @@ -1,16 +1,18 @@ import { it, describe, expect, beforeAll, afterAll } from 'vitest'; -import { mkTestPeer } from '../util.js'; +import { mkTestPeer } from '../../../__test__/util.js'; +import { FluencePeer } from '../../FluencePeer.js'; -const peer = mkTestPeer(); +let peer: FluencePeer; describe('Parse ast tests', () => { beforeAll(async () => { + peer = await mkTestPeer(); await peer.start(); }); afterAll(async () => { - await peer.stop(); + await peer?.stop(); }); it('Correct ast should be parsed correctly', async function () { diff --git a/packages/core/js-peer/src/js-peer/__test__/unit/ephemeral.spec.ts.skip b/packages/core/js-peer/src/js-peer/__test__/unit/ephemeral.spec.ts.skip deleted file mode 100644 index 9005b6aae..000000000 --- a/packages/core/js-peer/src/js-peer/__test__/unit/ephemeral.spec.ts.skip +++ /dev/null @@ -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((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'); - }); -}); diff --git a/packages/core/js-peer/src/js-peer/__test__/util.ts b/packages/core/js-peer/src/js-peer/__test__/util.ts deleted file mode 100644 index 7054f551f..000000000 --- a/packages/core/js-peer/src/js-peer/__test__/util.ts +++ /dev/null @@ -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>, -) => { - 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; -export type CompiledFile = { - functions: { [key: string]: CompiledFnCall }; - services: { [key: string]: ServiceDef }; -}; - -export const compileAqua = async (aquaFile: string): Promise => { - 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, 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, config?: PeerConfig) => { - return withPeer(action, { relay: nodes[0] }); -}; diff --git a/packages/core/js-peer/src/js-peer/_aqua/node-utils.ts b/packages/core/js-peer/src/js-peer/_aqua/node-utils.ts index 0208f4365..38e459e7a 100644 --- a/packages/core/js-peer/src/js-peer/_aqua/node-utils.ts +++ b/packages/core/js-peer/src/js-peer/_aqua/node-utils.ts @@ -7,7 +7,7 @@ * */ import { CallParams } from '@fluencelabs/interfaces'; -import { registerServiceImpl } from './util.js'; +import { registerService } from '../../compilerSupport/registerService.js'; import { FluencePeer } from '../FluencePeer.js'; // Services @@ -22,9 +22,11 @@ export interface NodeUtilsDef { } export function registerNodeUtils(peer: FluencePeer, serviceId: string, service: any) { - registerServiceImpl( - peer, - { + registerService({ + peer: peer as any, + service: service, + serviceId: serviceId, + def: { defaultServiceId: 'node_utils', functions: { tag: 'labeledProduct', @@ -73,9 +75,7 @@ export function registerNodeUtils(peer: FluencePeer, serviceId: string, service: }, }, }, - serviceId, - service, - ); + }); } // Functions diff --git a/packages/core/js-peer/src/js-peer/_aqua/services.ts b/packages/core/js-peer/src/js-peer/_aqua/services.ts index 33b3e9616..569c609e8 100644 --- a/packages/core/js-peer/src/js-peer/_aqua/services.ts +++ b/packages/core/js-peer/src/js-peer/_aqua/services.ts @@ -7,7 +7,7 @@ * */ import { CallParams } from '@fluencelabs/interfaces'; -import { registerServiceImpl } from './util.js'; +import { registerService } from '../../compilerSupport/registerService.js'; import { FluencePeer } from '../FluencePeer.js'; // Services @@ -28,9 +28,11 @@ export interface SigDef { } export function registerSig(peer: FluencePeer, serviceId: string, service: any) { - registerServiceImpl( - peer, - { + registerService({ + peer: peer as any, + service: service, + serviceId: serviceId, + def: { defaultServiceId: 'sig', functions: { tag: 'labeledProduct', @@ -131,9 +133,7 @@ export function registerSig(peer: FluencePeer, serviceId: string, service: any) }, }, }, - serviceId, - service, - ); + }); } // Functions diff --git a/packages/core/js-peer/src/js-peer/_aqua/single-module-srv.ts b/packages/core/js-peer/src/js-peer/_aqua/single-module-srv.ts index f08240432..146907e41 100644 --- a/packages/core/js-peer/src/js-peer/_aqua/single-module-srv.ts +++ b/packages/core/js-peer/src/js-peer/_aqua/single-module-srv.ts @@ -7,7 +7,7 @@ * */ import { CallParams } from '@fluencelabs/interfaces'; -import { registerServiceImpl } from './util.js'; +import { registerService } from '../../compilerSupport/registerService.js'; import { FluencePeer } from '../FluencePeer.js'; // Services @@ -27,9 +27,11 @@ export interface SrvDef { } export function registerSrv(peer: FluencePeer, serviceId: string, service: any) { - registerServiceImpl( - peer, - { + registerService({ + peer: peer as any, + serviceId, + service, + def: { defaultServiceId: 'single_module_srv', functions: { tag: 'labeledProduct', @@ -130,9 +132,7 @@ export function registerSrv(peer: FluencePeer, serviceId: string, service: any) }, }, }, - serviceId, - service, - ); + }); } // Functions diff --git a/packages/core/js-peer/src/js-peer/_aqua/util.ts b/packages/core/js-peer/src/js-peer/_aqua/util.ts deleted file mode 100644 index 492882739..000000000 --- a/packages/core/js-peer/src/js-peer/_aqua/util.ts +++ /dev/null @@ -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 }); diff --git a/packages/core/js-peer/src/js-peer/builtins/Sig.ts b/packages/core/js-peer/src/js-peer/builtins/Sig.ts index b3a90c8be..3d6fa0225 100644 --- a/packages/core/js-peer/src/js-peer/builtins/Sig.ts +++ b/packages/core/js-peer/src/js-peer/builtins/Sig.ts @@ -1,5 +1,6 @@ import { CallParams, PeerIdB58 } from '@fluencelabs/interfaces'; import { KeyPair } from '../../keypair/index.js'; +import { FluencePeer } from '../FluencePeer.js'; import { SigDef } from '../_aqua/services.js'; import { allowOnlyParticleOriginatedAt, allowServiceFn, and, or, SecurityGuard } from './securityGuard.js'; @@ -18,11 +19,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 +32,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 +50,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 +63,10 @@ export class Sig implements SigDef { * Verifies the signature. Required by aqua */ verify(signature: number[], data: number[]): Promise { - 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; +}; diff --git a/packages/core/js-peer/src/js-peer/builtins/SingleModuleSrv.ts b/packages/core/js-peer/src/js-peer/builtins/SingleModuleSrv.ts index 1eccb7dc0..03c0d56ba 100644 --- a/packages/core/js-peer/src/js-peer/builtins/SingleModuleSrv.ts +++ b/packages/core/js-peer/src/js-peer/builtins/SingleModuleSrv.ts @@ -7,7 +7,7 @@ import { Buffer } from 'buffer'; import { allowOnlyParticleOriginatedAt, SecurityGuard } from './securityGuard.js'; export const defaultGuard = (peer: FluencePeer) => { - return allowOnlyParticleOriginatedAt(peer.getStatus().peerId!); + return allowOnlyParticleOriginatedAt(peer.keyPair.getPeerId()); }; export class Srv implements SrvDef { diff --git a/packages/core/js-peer/src/js-peer/builtins/common.ts b/packages/core/js-peer/src/js-peer/builtins/common.ts index 8be42d7e6..74b700c1b 100644 --- a/packages/core/js-peer/src/js-peer/builtins/common.ts +++ b/packages/core/js-peer/src/js-peer/builtins/common.ts @@ -20,7 +20,7 @@ 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 '../utils.js'; import { Buffer } from 'buffer'; //@ts-ignore @@ -595,11 +595,3 @@ const checkForArgumentType = (req: { args: Array }, 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'; -}; diff --git a/packages/core/js-peer/src/js-peer/libp2pUtils.ts b/packages/core/js-peer/src/js-peer/libp2pUtils.ts new file mode 100644 index 000000000..6262a1773 --- /dev/null +++ b/packages/core/js-peer/src/js-peer/libp2pUtils.ts @@ -0,0 +1,19 @@ +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()); + } +} diff --git a/packages/core/js-peer/src/js-peer/serviceUtils.ts b/packages/core/js-peer/src/js-peer/serviceUtils.ts new file mode 100644 index 000000000..18baa65bd --- /dev/null +++ b/packages/core/js-peer/src/js-peer/serviceUtils.ts @@ -0,0 +1,18 @@ +import { CallServiceData, CallServiceResult, CallServiceResultType, ResultCodes } from '../interfaces/commonTypes.js'; + +export const doNothing = (..._args: Array) => 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); + } +} diff --git a/packages/core/js-peer/src/js-peer/utils.ts b/packages/core/js-peer/src/js-peer/utils.ts index 4b8387490..e76e7c5b5 100644 --- a/packages/core/js-peer/src/js-peer/utils.ts +++ b/packages/core/js-peer/src/js-peer/utils.ts @@ -14,140 +14,17 @@ * 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 { logger } from '../util/logger.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) => undefined; - -/** - * Checks the network connection by sending a ping-like request to relay node - * @param { FluenceClient } peer - The Fluence Client instance. - */ -export const checkConnection = async (peer: FluencePeer, ttl?: number): Promise => { - if (!peer.getStatus().isConnected) { - return false; - } - - const msg = Math.random().toString(36).substring(7); - - const promise = new Promise((resolve, reject) => { - const script = ` - (xor - (seq - (call %init_peer_id% ("load" "relay") [] init_relay) - (seq - (call %init_peer_id% ("load" "msg") [] msg) - (seq - (call init_relay ("op" "identity") [msg] 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, ttl); - - if (particle instanceof Error) { - return reject(particle.message); - } - - peer.internals.regHandler.forParticle( - particle.id, - 'load', - 'relay', - MakeServiceCall(() => { - return peer.getStatus().relayPeerId; - }), - ); - - peer.internals.regHandler.forParticle( - particle.id, - 'load', - 'msg', - MakeServiceCall(() => { - return msg; - }), - ); - - peer.internals.regHandler.forParticle( - particle.id, - 'callback', - 'callback', - MakeServiceCall((args) => { - const [val] = args; - setTimeout(() => { - resolve(val); - }, 0); - return {}; - }), - ); - - peer.internals.regHandler.forParticle( - particle.id, - 'callback', - 'error', - MakeServiceCall((args) => { - const [error] = args; - setTimeout(() => { - reject(error); - }, 0); - return {}; - }), - ); - - peer.internals.initiateParticle( - particle, - handleTimeout(() => { - reject('particle timed out'); - }), - ); - }); - - try { - const result = await promise; - if (result != msg) { - log.error("unexpected behavior. 'identity' must return the passed arguments."); - } - return true; - } catch (e) { - log.error('error on establishing connection. Relay: %s error: %j', e, peer.getStatus().relayPeerId); - 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 const isString = (unknown: unknown): unknown is string => { + return unknown !== null && typeof unknown === 'string'; }; -export class ServiceError extends Error { - constructor(message: string) { - super(message); - - Object.setPrototypeOf(this, ServiceError.prototype); - } -} +export const isObject = (unknown: unknown): unknown is object => { + return unknown !== null && typeof unknown === 'object'; +}; diff --git a/packages/core/js-peer/src/js-peer/utilsForNode.ts b/packages/core/js-peer/src/js-peer/utilsForNode.ts deleted file mode 100644 index 22b3018fa..000000000 --- a/packages/core/js-peer/src/js-peer/utilsForNode.ts +++ /dev/null @@ -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'); diff --git a/packages/core/js-peer/src/marine/worker/index.ts b/packages/core/js-peer/src/marine/worker/index.ts index 4ac9cf9ea..2f2fd2a95 100644 --- a/packages/core/js-peer/src/marine/worker/index.ts +++ b/packages/core/js-peer/src/marine/worker/index.ts @@ -14,7 +14,7 @@ * 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'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf2a2cf5c..efb2c17dc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,12 +123,14 @@ importers: packages/client/js-client.node: specifiers: '@fluencelabs/avm': 0.35.4 + '@fluencelabs/interfaces': 0.7.3 '@fluencelabs/js-peer': 0.8.5 '@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 @@ -138,6 +140,7 @@ importers: packages/client/js-client.web.standalone: specifiers: '@fluencelabs/avm': 0.35.4 + '@fluencelabs/interfaces': 0.7.3 '@fluencelabs/js-peer': 0.8.5 '@fluencelabs/marine-js': 0.3.45 '@rollup/plugin-inject': 5.0.3 @@ -152,6 +155,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 From 8cb357daba5a65ec9f6152fe396777c5b2576b90 Mon Sep 17 00:00:00 2001 From: Pavel Murygin Date: Fri, 31 Mar 2023 13:26:58 +0400 Subject: [PATCH 02/32] Rearrange files in js-peer package --- .../data => data_for_test}/greeting-record.wasm | Bin .../data => data_for_test}/greeting.wasm | Bin .../core/js-peer/src/clientPeer/ClientPeer.ts | 4 ++-- .../src/clientPeer/__test__/client.spec.ts | 6 +++--- .../js-peer/src/clientPeer/checkConnection.ts | 4 ++-- .../js-peer/src/compilerSupport/conversions.ts | 2 +- .../js-peer/src/compilerSupport/services.ts | 2 +- packages/core/js-peer/src/connection/index.ts | 4 ++-- .../src/ephemeral/__test__/ephemeral.spec.ts | 4 ++-- packages/core/js-peer/src/ephemeral/client.ts | 4 ++-- packages/core/js-peer/src/ephemeral/network.ts | 6 +++--- packages/core/js-peer/src/interfaces/index.ts | 2 +- .../src/{js-peer => jsPeer}/FluencePeer.ts | 16 ++++++++-------- .../integration => jsPeer/__test__}/avm.spec.ts | 6 +++--- .../__test__/parseAst.spec.ts} | 4 ++-- .../{js-peer => jsPeer}/__test__/peer.spec.ts | 4 ++-- .../core/js-peer/src/{js-peer => jsPeer}/avm.ts | 0 .../src/{js-peer => jsPeer}/serviceUtils.ts | 0 .../__test__}/marine-js.spec.ts | 6 +++--- .../src/{js-peer => particle}/Particle.ts | 0 .../src/{js-peer/builtins => services}/Sig.ts | 6 +++--- .../builtins => services}/SingleModuleSrv.ts | 6 +++--- .../__test__}/builtInHandler.spec.ts | 10 +++++----- .../__test__}/jsonBuiltin.spec.ts | 8 ++++---- .../__test__}/sigService.spec.ts | 12 ++++++------ .../__test__}/srv.spec.ts | 10 +++++----- .../{js-peer => services}/_aqua/node-utils.ts | 2 +- .../src/{js-peer => services}/_aqua/services.ts | 2 +- .../_aqua/single-module-srv.ts | 2 +- .../builtins/common.ts => services/builtins.ts} | 4 ++-- .../builtins => services}/securityGuard.ts | 0 .../src/{js-peer => util}/libp2pUtils.ts | 0 packages/core/js-peer/src/util/logger.ts | 2 +- .../src/{__test__/util.ts => util/testUtils.ts} | 8 ++++---- .../core/js-peer/src/{js-peer => util}/utils.ts | 3 --- 35 files changed, 73 insertions(+), 76 deletions(-) rename packages/core/js-peer/{src/js-peer/__test__/data => data_for_test}/greeting-record.wasm (100%) rename packages/core/js-peer/{src/js-peer/__test__/data => data_for_test}/greeting.wasm (100%) rename packages/core/js-peer/src/{js-peer => jsPeer}/FluencePeer.ts (98%) rename packages/core/js-peer/src/{js-peer/__test__/integration => jsPeer/__test__}/avm.spec.ts (96%) rename packages/core/js-peer/src/{js-peer/__test__/unit/ast.spec.ts => jsPeer/__test__/parseAst.spec.ts} (89%) rename packages/core/js-peer/src/{js-peer => jsPeer}/__test__/peer.spec.ts (98%) rename packages/core/js-peer/src/{js-peer => jsPeer}/avm.ts (100%) rename packages/core/js-peer/src/{js-peer => jsPeer}/serviceUtils.ts (100%) rename packages/core/js-peer/src/{js-peer/__test__/integration => marine/__test__}/marine-js.spec.ts (80%) rename packages/core/js-peer/src/{js-peer => particle}/Particle.ts (100%) rename packages/core/js-peer/src/{js-peer/builtins => services}/Sig.ts (93%) rename packages/core/js-peer/src/{js-peer/builtins => services}/SingleModuleSrv.ts (95%) rename packages/core/js-peer/src/{js-peer/__test__/unit => services/__test__}/builtInHandler.spec.ts (98%) rename packages/core/js-peer/src/{js-peer/__test__/integration => services/__test__}/jsonBuiltin.spec.ts (92%) rename packages/core/js-peer/src/{js-peer/__test__/integration => services/__test__}/sigService.spec.ts (90%) rename packages/core/js-peer/src/{js-peer/__test__/integration => services/__test__}/srv.spec.ts (83%) rename packages/core/js-peer/src/{js-peer => services}/_aqua/node-utils.ts (98%) rename packages/core/js-peer/src/{js-peer => services}/_aqua/services.ts (98%) rename packages/core/js-peer/src/{js-peer => services}/_aqua/single-module-srv.ts (98%) rename packages/core/js-peer/src/{js-peer/builtins/common.ts => services/builtins.ts} (99%) rename packages/core/js-peer/src/{js-peer/builtins => services}/securityGuard.ts (100%) rename packages/core/js-peer/src/{js-peer => util}/libp2pUtils.ts (100%) rename packages/core/js-peer/src/{__test__/util.ts => util/testUtils.ts} (94%) rename packages/core/js-peer/src/{js-peer => util}/utils.ts (84%) diff --git a/packages/core/js-peer/src/js-peer/__test__/data/greeting-record.wasm b/packages/core/js-peer/data_for_test/greeting-record.wasm similarity index 100% rename from packages/core/js-peer/src/js-peer/__test__/data/greeting-record.wasm rename to packages/core/js-peer/data_for_test/greeting-record.wasm diff --git a/packages/core/js-peer/src/js-peer/__test__/data/greeting.wasm b/packages/core/js-peer/data_for_test/greeting.wasm similarity index 100% rename from packages/core/js-peer/src/js-peer/__test__/data/greeting.wasm rename to packages/core/js-peer/data_for_test/greeting.wasm diff --git a/packages/core/js-peer/src/clientPeer/ClientPeer.ts b/packages/core/js-peer/src/clientPeer/ClientPeer.ts index af9cd2ae1..99845cb00 100644 --- a/packages/core/js-peer/src/clientPeer/ClientPeer.ts +++ b/packages/core/js-peer/src/clientPeer/ClientPeer.ts @@ -2,8 +2,8 @@ import { ClientConfig, ConnectionState, IFluenceClient, PeerIdB58, RelayOptions import { RelayConnection, RelayConnectionConfig } from '../connection/index.js'; import { IAvmRunner, IMarine } from '../interfaces/index.js'; import { fromOpts, KeyPair } from '../keypair/index.js'; -import { FluencePeer, PeerConfig } from '../js-peer/FluencePeer.js'; -import { relayOptionToMultiaddr } from '../js-peer/libp2pUtils.js'; +import { FluencePeer, PeerConfig } from '../jsPeer/FluencePeer.js'; +import { relayOptionToMultiaddr } from '../util/libp2pUtils.js'; export const makeClientPeerConfig = async ( relay: RelayOptions, diff --git a/packages/core/js-peer/src/clientPeer/__test__/client.spec.ts b/packages/core/js-peer/src/clientPeer/__test__/client.spec.ts index e15e5eddb..e1fa9a9ce 100644 --- a/packages/core/js-peer/src/clientPeer/__test__/client.spec.ts +++ b/packages/core/js-peer/src/clientPeer/__test__/client.spec.ts @@ -1,8 +1,8 @@ import { it, describe, expect } from 'vitest'; import { CallServiceData } from '../../interfaces/commonTypes.js'; -import { handleTimeout } from '../../js-peer/Particle.js'; -import { doNothing } from '../../js-peer/serviceUtils.js'; -import { registerHandlersHelper, withClient } from '../../__test__/util.js'; +import { handleTimeout } from '../../particle/Particle.js'; +import { doNothing } from '../../jsPeer/serviceUtils.js'; +import { registerHandlersHelper, withClient } from '../../util/testUtils.js'; import { checkConnection } from '../checkConnection.js'; import { nodes, RELAY } from './connection.js'; diff --git a/packages/core/js-peer/src/clientPeer/checkConnection.ts b/packages/core/js-peer/src/clientPeer/checkConnection.ts index e59229957..5bbafc991 100644 --- a/packages/core/js-peer/src/clientPeer/checkConnection.ts +++ b/packages/core/js-peer/src/clientPeer/checkConnection.ts @@ -1,8 +1,8 @@ import { ClientPeer } from './ClientPeer.js'; import { logger } from '../util/logger.js'; -import { WrapFnIntoServiceCall } from '../js-peer/serviceUtils.js'; -import { handleTimeout } from '../js-peer/Particle.js'; +import { WrapFnIntoServiceCall } from '../jsPeer/serviceUtils.js'; +import { handleTimeout } from '../particle/Particle.js'; const log = logger('connection'); diff --git a/packages/core/js-peer/src/compilerSupport/conversions.ts b/packages/core/js-peer/src/compilerSupport/conversions.ts index 08af1ad92..e32db6b91 100644 --- a/packages/core/js-peer/src/compilerSupport/conversions.ts +++ b/packages/core/js-peer/src/compilerSupport/conversions.ts @@ -1,4 +1,4 @@ -import { jsonify } from '../js-peer/utils.js'; +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'; diff --git a/packages/core/js-peer/src/compilerSupport/services.ts b/packages/core/js-peer/src/compilerSupport/services.ts index 0d9072d58..21a3c9136 100644 --- a/packages/core/js-peer/src/compilerSupport/services.ts +++ b/packages/core/js-peer/src/compilerSupport/services.ts @@ -1,7 +1,7 @@ import { SecurityTetraplet } from '@fluencelabs/avm'; import { match } from 'ts-pattern'; -import { Particle } from '../js-peer/Particle.js'; +import { Particle } from '../particle/Particle.js'; import { CallServiceData, GenericCallServiceHandler, ResultCodes } from '../interfaces/commonTypes.js'; import { aquaArgs2Ts, responseServiceValue2ts, returnType2Aqua, ts2aqua } from './conversions.js'; diff --git a/packages/core/js-peer/src/connection/index.ts b/packages/core/js-peer/src/connection/index.ts index 7a7b8cc33..51af4305b 100644 --- a/packages/core/js-peer/src/connection/index.ts +++ b/packages/core/js-peer/src/connection/index.ts @@ -33,8 +33,8 @@ import { toString } from 'uint8arrays/to-string'; import { logger } from '../util/logger.js'; import { Subject, Subscribable } from 'rxjs'; -import { Particle } from '../js-peer/Particle.js'; -import { throwIfHasNoPeerId } from '../js-peer/libp2pUtils.js'; +import { Particle } from '../particle/Particle.js'; +import { throwIfHasNoPeerId } from '../util/libp2pUtils.js'; const log = logger('connection'); diff --git a/packages/core/js-peer/src/ephemeral/__test__/ephemeral.spec.ts b/packages/core/js-peer/src/ephemeral/__test__/ephemeral.spec.ts index 37e8ef81a..54ad12698 100644 --- a/packages/core/js-peer/src/ephemeral/__test__/ephemeral.spec.ts +++ b/packages/core/js-peer/src/ephemeral/__test__/ephemeral.spec.ts @@ -1,8 +1,8 @@ import { it, describe, expect, beforeEach, afterEach } from 'vitest'; import { ResultCodes } from '../../interfaces/commonTypes.js'; -import { FluencePeer } from '../../js-peer/FluencePeer.js'; +import { FluencePeer } from '../../jsPeer/FluencePeer.js'; import { KeyPair } from '../../keypair/index.js'; -import { TestPeer } from '../../__test__/util.js'; +import { TestPeer } from '../../util/testUtils.js'; import { EphemeralNetworkClient } from '../client.js'; import { EphemeralNetwork, defaultConfig } from '../network.js'; diff --git a/packages/core/js-peer/src/ephemeral/client.ts b/packages/core/js-peer/src/ephemeral/client.ts index c8d6bae44..4ea9fdc92 100644 --- a/packages/core/js-peer/src/ephemeral/client.ts +++ b/packages/core/js-peer/src/ephemeral/client.ts @@ -1,6 +1,6 @@ import { PeerIdB58 } from '@fluencelabs/interfaces'; -import { MarineBasedAvmRunner } from '../js-peer/avm.js'; -import { FluencePeer, PeerConfig } from '../js-peer/FluencePeer.js'; +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'; diff --git a/packages/core/js-peer/src/ephemeral/network.ts b/packages/core/js-peer/src/ephemeral/network.ts index 0447be2a0..9bbed1f81 100644 --- a/packages/core/js-peer/src/ephemeral/network.ts +++ b/packages/core/js-peer/src/ephemeral/network.ts @@ -7,11 +7,11 @@ import { WorkerLoaderFromFs } from '../marine/deps-loader/node.js'; import { logger } from '../util/logger.js'; import { Subject } from 'rxjs'; -import { Particle } from '../js-peer/Particle.js'; +import { Particle } from '../particle/Particle.js'; import { WasmLoaderFromNpm } from '../marine/deps-loader/node.js'; -import { MarineBasedAvmRunner } from '../js-peer/avm.js'; -import { FluencePeer } from '../js-peer/FluencePeer.js'; +import { MarineBasedAvmRunner } from '../jsPeer/avm.js'; +import { FluencePeer } from '../jsPeer/FluencePeer.js'; const log = logger('ephemeral'); diff --git a/packages/core/js-peer/src/interfaces/index.ts b/packages/core/js-peer/src/interfaces/index.ts index cbc46e570..5ce4d650e 100644 --- a/packages/core/js-peer/src/interfaces/index.ts +++ b/packages/core/js-peer/src/interfaces/index.ts @@ -20,7 +20,7 @@ import type { JSONArray, JSONObject } from '@fluencelabs/marine-js/dist/types'; import type { RunParameters, CallResultsArray, InterpreterResult } from '@fluencelabs/avm'; // @ts-ignore import type { WorkerImplementation } from 'threads/dist/types/master'; -import { Particle } from '../js-peer/Particle.js'; +import { Particle } from '../particle/Particle.js'; export type ParticleHandler = (particle: string) => void; diff --git a/packages/core/js-peer/src/js-peer/FluencePeer.ts b/packages/core/js-peer/src/jsPeer/FluencePeer.ts similarity index 98% rename from packages/core/js-peer/src/js-peer/FluencePeer.ts rename to packages/core/js-peer/src/jsPeer/FluencePeer.ts index 7ff6ea7f3..48e2bba3c 100644 --- a/packages/core/js-peer/src/js-peer/FluencePeer.ts +++ b/packages/core/js-peer/src/jsPeer/FluencePeer.ts @@ -24,18 +24,18 @@ import { ResultCodes, } from '../interfaces/commonTypes.js'; import type { PeerIdB58 } from '@fluencelabs/interfaces/dist/fluenceClient'; -import { Particle, ParticleExecutionStage, ParticleQueueItem } from './Particle.js'; -import { jsonify, isString } from './utils.js'; +import { Particle, ParticleExecutionStage, ParticleQueueItem } from '../particle/Particle.js'; +import { jsonify, isString } from '../util/utils.js'; import { concatMap, filter, pipe, Subject, tap, Unsubscribable } 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 { builtInServices } from '../services/builtins.js'; +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 { JSONValue } from '@fluencelabs/avm'; -import { NodeUtils, Srv } from './builtins/SingleModuleSrv.js'; -import { registerNodeUtils } from './_aqua/node-utils.js'; +import { NodeUtils, Srv } from '../services/SingleModuleSrv.js'; +import { registerNodeUtils } from '../services/_aqua/node-utils.js'; import { logger } from '../util/logger.js'; import { ServiceError } from './serviceUtils.js'; diff --git a/packages/core/js-peer/src/js-peer/__test__/integration/avm.spec.ts b/packages/core/js-peer/src/jsPeer/__test__/avm.spec.ts similarity index 96% rename from packages/core/js-peer/src/js-peer/__test__/integration/avm.spec.ts rename to packages/core/js-peer/src/jsPeer/__test__/avm.spec.ts index 1dda00e1c..774fb40c2 100644 --- a/packages/core/js-peer/src/js-peer/__test__/integration/avm.spec.ts +++ b/packages/core/js-peer/src/jsPeer/__test__/avm.spec.ts @@ -1,8 +1,8 @@ import { it, describe, expect } from 'vitest'; -import { registerHandlersHelper, withPeer } from '../../../__test__/util.js'; -import { handleTimeout } from '../../Particle.js'; +import { registerHandlersHelper, withPeer } from '../../util/testUtils.js'; +import { handleTimeout } from '../../particle/Particle.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((resolve, reject) => { diff --git a/packages/core/js-peer/src/js-peer/__test__/unit/ast.spec.ts b/packages/core/js-peer/src/jsPeer/__test__/parseAst.spec.ts similarity index 89% rename from packages/core/js-peer/src/js-peer/__test__/unit/ast.spec.ts rename to packages/core/js-peer/src/jsPeer/__test__/parseAst.spec.ts index 18abeae67..5c608714c 100644 --- a/packages/core/js-peer/src/js-peer/__test__/unit/ast.spec.ts +++ b/packages/core/js-peer/src/jsPeer/__test__/parseAst.spec.ts @@ -1,7 +1,7 @@ import { it, describe, expect, beforeAll, afterAll } from 'vitest'; -import { mkTestPeer } from '../../../__test__/util.js'; -import { FluencePeer } from '../../FluencePeer.js'; +import { mkTestPeer } from '../../util/testUtils.js'; +import { FluencePeer } from '../FluencePeer.js'; let peer: FluencePeer; diff --git a/packages/core/js-peer/src/js-peer/__test__/peer.spec.ts b/packages/core/js-peer/src/jsPeer/__test__/peer.spec.ts similarity index 98% rename from packages/core/js-peer/src/js-peer/__test__/peer.spec.ts rename to packages/core/js-peer/src/jsPeer/__test__/peer.spec.ts index ebceda3ee..7cd8785a8 100644 --- a/packages/core/js-peer/src/js-peer/__test__/peer.spec.ts +++ b/packages/core/js-peer/src/jsPeer/__test__/peer.spec.ts @@ -1,8 +1,8 @@ import { it, describe, expect } from 'vitest'; import { isFluencePeer } from '@fluencelabs/interfaces'; -import { mkTestPeer, registerHandlersHelper, withPeer } from '../../__test__/util.js'; -import { handleTimeout } from '../Particle.js'; +import { mkTestPeer, registerHandlersHelper, withPeer } from '../../util/testUtils.js'; +import { handleTimeout } from '../../particle/Particle.js'; import { FluencePeer } from '../FluencePeer.js'; describe('FluencePeer usage test suite', () => { diff --git a/packages/core/js-peer/src/js-peer/avm.ts b/packages/core/js-peer/src/jsPeer/avm.ts similarity index 100% rename from packages/core/js-peer/src/js-peer/avm.ts rename to packages/core/js-peer/src/jsPeer/avm.ts diff --git a/packages/core/js-peer/src/js-peer/serviceUtils.ts b/packages/core/js-peer/src/jsPeer/serviceUtils.ts similarity index 100% rename from packages/core/js-peer/src/js-peer/serviceUtils.ts rename to packages/core/js-peer/src/jsPeer/serviceUtils.ts diff --git a/packages/core/js-peer/src/js-peer/__test__/integration/marine-js.spec.ts b/packages/core/js-peer/src/marine/__test__/marine-js.spec.ts similarity index 80% rename from packages/core/js-peer/src/js-peer/__test__/integration/marine-js.spec.ts rename to packages/core/js-peer/src/marine/__test__/marine-js.spec.ts index d185bb069..22ecc6e03 100644 --- a/packages/core/js-peer/src/js-peer/__test__/integration/marine-js.spec.ts +++ b/packages/core/js-peer/src/marine/__test__/marine-js.spec.ts @@ -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 '../../../__test__/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 diff --git a/packages/core/js-peer/src/js-peer/Particle.ts b/packages/core/js-peer/src/particle/Particle.ts similarity index 100% rename from packages/core/js-peer/src/js-peer/Particle.ts rename to packages/core/js-peer/src/particle/Particle.ts diff --git a/packages/core/js-peer/src/js-peer/builtins/Sig.ts b/packages/core/js-peer/src/services/Sig.ts similarity index 93% rename from packages/core/js-peer/src/js-peer/builtins/Sig.ts rename to packages/core/js-peer/src/services/Sig.ts index 3d6fa0225..ca74698c7 100644 --- a/packages/core/js-peer/src/js-peer/builtins/Sig.ts +++ b/packages/core/js-peer/src/services/Sig.ts @@ -1,7 +1,7 @@ import { CallParams, PeerIdB58 } from '@fluencelabs/interfaces'; -import { KeyPair } from '../../keypair/index.js'; -import { FluencePeer } from '../FluencePeer.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) => { diff --git a/packages/core/js-peer/src/js-peer/builtins/SingleModuleSrv.ts b/packages/core/js-peer/src/services/SingleModuleSrv.ts similarity index 95% rename from packages/core/js-peer/src/js-peer/builtins/SingleModuleSrv.ts rename to packages/core/js-peer/src/services/SingleModuleSrv.ts index 03c0d56ba..bc705719b 100644 --- a/packages/core/js-peer/src/js-peer/builtins/SingleModuleSrv.ts +++ b/packages/core/js-peer/src/services/SingleModuleSrv.ts @@ -1,7 +1,7 @@ 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 { NodeUtilsDef } from './_aqua/node-utils.js'; +import { FluencePeer } from '../jsPeer/FluencePeer.js'; import { CallParams } from '@fluencelabs/interfaces'; import { Buffer } from 'buffer'; import { allowOnlyParticleOriginatedAt, SecurityGuard } from './securityGuard.js'; diff --git a/packages/core/js-peer/src/js-peer/__test__/unit/builtInHandler.spec.ts b/packages/core/js-peer/src/services/__test__/builtInHandler.spec.ts similarity index 98% rename from packages/core/js-peer/src/js-peer/__test__/unit/builtInHandler.spec.ts rename to packages/core/js-peer/src/services/__test__/builtInHandler.spec.ts index f4259dee7..879a2b6cf 100644 --- a/packages/core/js-peer/src/js-peer/__test__/unit/builtInHandler.spec.ts +++ b/packages/core/js-peer/src/services/__test__/builtInHandler.spec.ts @@ -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 { CallServiceData } from '../../interfaces/commonTypes.js'; +import { KeyPair } from '../../keypair/index.js'; +import { Sig, defaultSigGuard } from '../Sig.js'; +import { allowServiceFn } from '../securityGuard.js'; +import { builtInServices } from '../builtins.js'; const a10b20 = `{ "a": 10, diff --git a/packages/core/js-peer/src/js-peer/__test__/integration/jsonBuiltin.spec.ts b/packages/core/js-peer/src/services/__test__/jsonBuiltin.spec.ts similarity index 92% rename from packages/core/js-peer/src/js-peer/__test__/integration/jsonBuiltin.spec.ts rename to packages/core/js-peer/src/services/__test__/jsonBuiltin.spec.ts index c297624ec..6bfa714ab 100644 --- a/packages/core/js-peer/src/js-peer/__test__/integration/jsonBuiltin.spec.ts +++ b/packages/core/js-peer/src/services/__test__/jsonBuiltin.spec.ts @@ -1,9 +1,9 @@ import { it, describe, expect, beforeEach, afterEach } from 'vitest'; -import { Particle } from '../../Particle.js'; -import { FluencePeer } from '../../FluencePeer.js'; -import { mkTestPeer } from '../../../__test__/util.js'; -import { doNothing } from '../../serviceUtils.js'; +import { Particle } from '../../particle/Particle.js'; +import { FluencePeer } from '../../jsPeer/FluencePeer.js'; +import { mkTestPeer } from '../../util/testUtils.js'; +import { doNothing } from '../../jsPeer/serviceUtils.js'; let peer: FluencePeer; diff --git a/packages/core/js-peer/src/js-peer/__test__/integration/sigService.spec.ts b/packages/core/js-peer/src/services/__test__/sigService.spec.ts similarity index 90% rename from packages/core/js-peer/src/js-peer/__test__/integration/sigService.spec.ts rename to packages/core/js-peer/src/services/__test__/sigService.spec.ts index b054fed1b..c3a40b0f0 100644 --- a/packages/core/js-peer/src/js-peer/__test__/integration/sigService.spec.ts +++ b/packages/core/js-peer/src/services/__test__/sigService.spec.ts @@ -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 { registerService } from '../../../compilerSupport/registerService.js'; -import { compileAqua, withPeer } from '../../../__test__/util.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; diff --git a/packages/core/js-peer/src/js-peer/__test__/integration/srv.spec.ts b/packages/core/js-peer/src/services/__test__/srv.spec.ts similarity index 83% rename from packages/core/js-peer/src/js-peer/__test__/integration/srv.spec.ts rename to packages/core/js-peer/src/services/__test__/srv.spec.ts index fe67940ff..0b4fbfe27 100644 --- a/packages/core/js-peer/src/js-peer/__test__/integration/srv.spec.ts +++ b/packages/core/js-peer/src/services/__test__/srv.spec.ts @@ -1,14 +1,14 @@ import { it, describe, expect, beforeAll } from 'vitest'; import * as path from 'path'; import * as url from 'url'; -import { compileAqua, withPeer } from '../../../__test__/util.js'; +import { compileAqua, withPeer } from '../../util/testUtils.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 +16,7 @@ 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'); + const wasm = path.join(__dirname, '../../../data_for_test/greeting.wasm'); // act const res = await aqua.happy_path(peer, { file_path: wasm }); @@ -29,7 +29,7 @@ describe('Srv service test suite', () => { it('List deployed services', async () => { await withPeer(async (peer) => { // arrange - const wasm = path.join(__dirname, '../data/greeting.wasm'); + const wasm = path.join(__dirname, '../../../data_for_test/greeting.wasm'); // act const res = await aqua.list_services(peer, { file_path: wasm }); @@ -42,7 +42,7 @@ 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'); + const wasm = path.join(__dirname, '../../../data_for_test/greeting.wasm'); // act const res = await aqua.service_removed(peer, { file_path: wasm }); diff --git a/packages/core/js-peer/src/js-peer/_aqua/node-utils.ts b/packages/core/js-peer/src/services/_aqua/node-utils.ts similarity index 98% rename from packages/core/js-peer/src/js-peer/_aqua/node-utils.ts rename to packages/core/js-peer/src/services/_aqua/node-utils.ts index 38e459e7a..802d48c56 100644 --- a/packages/core/js-peer/src/js-peer/_aqua/node-utils.ts +++ b/packages/core/js-peer/src/services/_aqua/node-utils.ts @@ -8,7 +8,7 @@ */ import { CallParams } from '@fluencelabs/interfaces'; import { registerService } from '../../compilerSupport/registerService.js'; -import { FluencePeer } from '../FluencePeer.js'; +import { FluencePeer } from '../../jsPeer/FluencePeer.js'; // Services diff --git a/packages/core/js-peer/src/js-peer/_aqua/services.ts b/packages/core/js-peer/src/services/_aqua/services.ts similarity index 98% rename from packages/core/js-peer/src/js-peer/_aqua/services.ts rename to packages/core/js-peer/src/services/_aqua/services.ts index 569c609e8..ec5e69313 100644 --- a/packages/core/js-peer/src/js-peer/_aqua/services.ts +++ b/packages/core/js-peer/src/services/_aqua/services.ts @@ -8,7 +8,7 @@ */ import { CallParams } from '@fluencelabs/interfaces'; import { registerService } from '../../compilerSupport/registerService.js'; -import { FluencePeer } from '../FluencePeer.js'; +import { FluencePeer } from '../../jsPeer/FluencePeer.js'; // Services diff --git a/packages/core/js-peer/src/js-peer/_aqua/single-module-srv.ts b/packages/core/js-peer/src/services/_aqua/single-module-srv.ts similarity index 98% rename from packages/core/js-peer/src/js-peer/_aqua/single-module-srv.ts rename to packages/core/js-peer/src/services/_aqua/single-module-srv.ts index 146907e41..d3b69606e 100644 --- a/packages/core/js-peer/src/js-peer/_aqua/single-module-srv.ts +++ b/packages/core/js-peer/src/services/_aqua/single-module-srv.ts @@ -8,7 +8,7 @@ */ import { CallParams } from '@fluencelabs/interfaces'; import { registerService } from '../../compilerSupport/registerService.js'; -import { FluencePeer } from '../FluencePeer.js'; +import { FluencePeer } from '../../jsPeer/FluencePeer.js'; // Services diff --git a/packages/core/js-peer/src/js-peer/builtins/common.ts b/packages/core/js-peer/src/services/builtins.ts similarity index 99% rename from packages/core/js-peer/src/js-peer/builtins/common.ts rename to packages/core/js-peer/src/services/builtins.ts index 74b700c1b..292a1c424 100644 --- a/packages/core/js-peer/src/js-peer/builtins/common.ts +++ b/packages/core/js-peer/src/services/builtins.ts @@ -19,8 +19,8 @@ import * as bs58 from 'bs58'; import { sha256 } from 'multiformats/hashes/sha2'; import { CallServiceResult } from '@fluencelabs/avm'; -import { GenericCallServiceHandler, ResultCodes } from '../../interfaces/commonTypes.js'; -import { isString, jsonify } from '../utils.js'; +import { GenericCallServiceHandler, ResultCodes } from '../interfaces/commonTypes.js'; +import { isString, jsonify } from '../util/utils.js'; import { Buffer } from 'buffer'; //@ts-ignore diff --git a/packages/core/js-peer/src/js-peer/builtins/securityGuard.ts b/packages/core/js-peer/src/services/securityGuard.ts similarity index 100% rename from packages/core/js-peer/src/js-peer/builtins/securityGuard.ts rename to packages/core/js-peer/src/services/securityGuard.ts diff --git a/packages/core/js-peer/src/js-peer/libp2pUtils.ts b/packages/core/js-peer/src/util/libp2pUtils.ts similarity index 100% rename from packages/core/js-peer/src/js-peer/libp2pUtils.ts rename to packages/core/js-peer/src/util/libp2pUtils.ts diff --git a/packages/core/js-peer/src/util/logger.ts b/packages/core/js-peer/src/util/logger.ts index 7ae9bedb2..7b93bddd0 100644 --- a/packages/core/js-peer/src/util/logger.ts +++ b/packages/core/js-peer/src/util/logger.ts @@ -1,5 +1,5 @@ import debug from 'debug'; -import { Particle } from '../js-peer/Particle.js'; +import { Particle } from '../particle/Particle.js'; // Format avm data as a string debug.formatters.a = (avmData: Uint8Array) => { diff --git a/packages/core/js-peer/src/__test__/util.ts b/packages/core/js-peer/src/util/testUtils.ts similarity index 94% rename from packages/core/js-peer/src/__test__/util.ts rename to packages/core/js-peer/src/util/testUtils.ts index 02548dc0f..30cae996f 100644 --- a/packages/core/js-peer/src/__test__/util.ts +++ b/packages/core/js-peer/src/util/testUtils.ts @@ -1,18 +1,18 @@ import * as api from '@fluencelabs/aqua-api/aqua-api.js'; import { promises as fs } from 'fs'; -import { FluencePeer, PeerConfig } from '../js-peer/FluencePeer.js'; -import { Particle } from '../js-peer/Particle.js'; +import { 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 '../js-peer/avm.js'; +import { MarineBasedAvmRunner } from '../jsPeer/avm.js'; import { WorkerLoader } from '../marine/worker-script/workerLoader.js'; import { KeyPair } from '../keypair/index.js'; import { IConnection } from '../interfaces/index.js'; import { Subject, Subscribable } from 'rxjs'; -import { WrapFnIntoServiceCall } from '../js-peer/serviceUtils.js'; +import { WrapFnIntoServiceCall } from '../jsPeer/serviceUtils.js'; import { ClientPeer, makeClientPeerConfig } from '../clientPeer/ClientPeer.js'; import { WasmLoaderFromNpm } from '../marine/deps-loader/node.js'; diff --git a/packages/core/js-peer/src/js-peer/utils.ts b/packages/core/js-peer/src/util/utils.ts similarity index 84% rename from packages/core/js-peer/src/js-peer/utils.ts rename to packages/core/js-peer/src/util/utils.ts index e76e7c5b5..3633b3a6e 100644 --- a/packages/core/js-peer/src/js-peer/utils.ts +++ b/packages/core/js-peer/src/util/utils.ts @@ -14,9 +14,6 @@ * limitations under the License. */ -import { CallServiceData, CallServiceResult, CallServiceResultType, ResultCodes } from '../interfaces/commonTypes.js'; -import { ParticleExecutionStage } from './Particle.js'; - export function jsonify(obj: unknown) { return JSON.stringify(obj, null, 4); } From b788ebf064923e1b38f0be2c1637bbc461a7b35b Mon Sep 17 00:00:00 2001 From: Pavel Murygin Date: Fri, 31 Mar 2023 17:15:38 +0400 Subject: [PATCH 03/32] Better split stuff in interfaces package --- packages/core/interfaces/src/commonTypes.ts | 50 ++++++++++++ .../aquaTypeDefinitions.ts} | 0 .../compilerSupportInterface.ts | 21 +++++ packages/core/interfaces/src/fluenceClient.ts | 77 ++----------------- packages/core/interfaces/src/index.ts | 6 +- .../core/js-peer/src/jsPeer/FluencePeer.ts | 2 +- 6 files changed, 83 insertions(+), 73 deletions(-) create mode 100644 packages/core/interfaces/src/commonTypes.ts rename packages/core/interfaces/src/{compilerSupport.ts => compilerSupport/aquaTypeDefinitions.ts} (100%) create mode 100644 packages/core/interfaces/src/compilerSupport/compilerSupportInterface.ts diff --git a/packages/core/interfaces/src/commonTypes.ts b/packages/core/interfaces/src/commonTypes.ts new file mode 100644 index 000000000..96d9152e7 --- /dev/null +++ b/packages/core/interfaces/src/commonTypes.ts @@ -0,0 +1,50 @@ +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 { + /** + * 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 : Record; +} diff --git a/packages/core/interfaces/src/compilerSupport.ts b/packages/core/interfaces/src/compilerSupport/aquaTypeDefinitions.ts similarity index 100% rename from packages/core/interfaces/src/compilerSupport.ts rename to packages/core/interfaces/src/compilerSupport/aquaTypeDefinitions.ts diff --git a/packages/core/interfaces/src/compilerSupport/compilerSupportInterface.ts b/packages/core/interfaces/src/compilerSupport/compilerSupportInterface.ts new file mode 100644 index 000000000..f9c9f4b1e --- /dev/null +++ b/packages/core/interfaces/src/compilerSupport/compilerSupportInterface.ts @@ -0,0 +1,21 @@ +import { IFluenceInternalApi } from '../fluenceClient.js'; +import { FnConfig, FunctionCallDef, ServiceDef } from './aquaTypeDefinitions.js'; + +export interface CallAquaFunctionArgs { + peer: IFluenceInternalApi; + def: FunctionCallDef; + script: string; + config: FnConfig; + args: { [key: string]: any }; +} + +export type CallAquaFunction = (args: CallAquaFunctionArgs) => Promise; + +export interface RegisterServiceArgs { + peer: IFluenceInternalApi; + def: ServiceDef; + serviceId: string | undefined; + service: any; +} + +export type RegisterService = (args: RegisterServiceArgs) => void; diff --git a/packages/core/interfaces/src/fluenceClient.ts b/packages/core/interfaces/src/fluenceClient.ts index 403daf568..c4e907424 100644 --- a/packages/core/interfaces/src/fluenceClient.ts +++ b/packages/core/interfaces/src/fluenceClient.ts @@ -1,54 +1,4 @@ -import type { SecurityTetraplet } from '@fluencelabs/avm'; -import type { FnConfig, FunctionCallDef, ServiceDef } from './compilerSupport.js'; - -/** - * Peer ID's id as a base58 string (multihash/CIDv0). - */ -export type PeerIdB58 = string; - -/** - * Additional information about a service call - * @typeparam ArgName - */ -export interface CallParams { - /** - * 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 : Record; -} - -/** - * Node of the Fluence network specified as a pair of node's multiaddr and it's peer id - */ -type Node = { - peerId: PeerIdB58; - multiaddr: string; -}; +import type { Node } from './commonTypes.js'; /** * A node in Fluence network a client can connect to. @@ -58,8 +8,14 @@ type 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; @@ -150,25 +106,6 @@ export interface IFluenceClient extends IFluenceInternalApi { getRelayPeerId(): string; } -export interface CallAquaFunctionArgs { - peer: IFluenceInternalApi; - def: FunctionCallDef; - script: string; - config: FnConfig; - args: { [key: string]: any }; -} - -export type CallAquaFunction = (args: CallAquaFunctionArgs) => Promise; - -export interface RegisterServiceArgs { - peer: IFluenceInternalApi; - def: ServiceDef; - serviceId: string | undefined; - service: any; -} - -export type RegisterService = (args: RegisterServiceArgs) => void; - export const asFluencePeer = (fluencePeerCandidate: unknown): IFluenceClient => { if (isFluencePeer(fluencePeerCandidate)) { return fluencePeerCandidate; diff --git a/packages/core/interfaces/src/index.ts b/packages/core/interfaces/src/index.ts index e4241e03d..87ef1c842 100644 --- a/packages/core/interfaces/src/index.ts +++ b/packages/core/interfaces/src/index.ts @@ -1,2 +1,4 @@ -export * from './compilerSupport.js' -export * from './fluenceClient.js' \ No newline at end of file +export * from './compilerSupport/aquaTypeDefinitions.js'; +export * from './compilerSupport/compilerSupportInterface.js'; +export * from './commonTypes.js'; +export * from './fluenceClient.js'; diff --git a/packages/core/js-peer/src/jsPeer/FluencePeer.ts b/packages/core/js-peer/src/jsPeer/FluencePeer.ts index 48e2bba3c..074f03075 100644 --- a/packages/core/js-peer/src/jsPeer/FluencePeer.ts +++ b/packages/core/js-peer/src/jsPeer/FluencePeer.ts @@ -23,7 +23,7 @@ import { GenericCallServiceHandler, ResultCodes, } from '../interfaces/commonTypes.js'; -import type { PeerIdB58 } from '@fluencelabs/interfaces/dist/fluenceClient'; +import type { PeerIdB58 } from '@fluencelabs/interfaces'; import { Particle, ParticleExecutionStage, ParticleQueueItem } from '../particle/Particle.js'; import { jsonify, isString } from '../util/utils.js'; import { concatMap, filter, pipe, Subject, tap, Unsubscribable } from 'rxjs'; From e1169f9a524324ea76926319ff444a822ccd1db2 Mon Sep 17 00:00:00 2001 From: Pavel Murygin Date: Fri, 31 Mar 2023 20:08:10 +0400 Subject: [PATCH 04/32] Interface segregation principle :) --- .../api/src/compilerSupport/implementation.ts | 8 +- packages/client/api/src/index.ts | 37 +++++- packages/client/api/src/util.ts | 8 +- .../compilerSupportInterface.ts | 57 ++++++++- .../core/js-peer/src/clientPeer/ClientPeer.ts | 6 +- .../src/clientPeer/__test__/client.spec.ts | 4 +- .../js-peer/src/clientPeer/checkConnection.ts | 2 +- .../src/compilerSupport/callFunction.ts | 4 +- .../src/compilerSupport/conversions.ts | 2 +- .../src/compilerSupport/registerService.ts | 4 +- .../js-peer/src/compilerSupport/services.ts | 2 +- .../{index.ts => RelayConnection.ts} | 16 +-- .../core/js-peer/src/connection/interfaces.ts | 30 +++++ .../src/ephemeral/__test__/ephemeral.spec.ts | 5 +- .../core/js-peer/src/ephemeral/network.ts | 5 +- packages/core/js-peer/src/interfaces/index.ts | 102 ---------------- .../core/js-peer/src/jsPeer/FluencePeer.ts | 86 +++++++------ packages/core/js-peer/src/jsPeer/avm.ts | 4 +- .../core/js-peer/src/jsPeer/serviceUtils.ts | 18 --- .../interface.ts} | 21 +--- .../js-peer/src/jsServiceHost/serviceUtils.ts | 35 ++++++ .../js-peer/src/marine/deps-loader/common.ts | 2 +- .../js-peer/src/marine/deps-loader/node.ts | 2 +- .../js-peer/src/marine/deps-loader/web.ts | 2 +- .../core/js-peer/src/marine/interfaces.ts | 57 +++++++++ .../src/marine/worker-script/workerLoader.ts | 3 +- .../core/js-peer/src/marine/worker/index.ts | 4 +- .../core/js-peer/src/particle/Particle.ts | 113 +++++++++--------- .../core/js-peer/src/particle/interfaces.ts | 44 +++++++ .../services/__test__/builtInHandler.spec.ts | 2 +- .../src/services/__test__/jsonBuiltin.spec.ts | 2 +- .../core/js-peer/src/services/builtins.ts | 2 +- packages/core/js-peer/src/util/commonTypes.ts | 8 ++ packages/core/js-peer/src/util/testUtils.ts | 4 +- 34 files changed, 413 insertions(+), 288 deletions(-) rename packages/core/js-peer/src/connection/{index.ts => RelayConnection.ts} (93%) create mode 100644 packages/core/js-peer/src/connection/interfaces.ts delete mode 100644 packages/core/js-peer/src/interfaces/index.ts delete mode 100644 packages/core/js-peer/src/jsPeer/serviceUtils.ts rename packages/core/js-peer/src/{interfaces/commonTypes.ts => jsServiceHost/interface.ts} (71%) create mode 100644 packages/core/js-peer/src/jsServiceHost/serviceUtils.ts create mode 100644 packages/core/js-peer/src/marine/interfaces.ts create mode 100644 packages/core/js-peer/src/particle/interfaces.ts create mode 100644 packages/core/js-peer/src/util/commonTypes.ts diff --git a/packages/client/api/src/compilerSupport/implementation.ts b/packages/client/api/src/compilerSupport/implementation.ts index f684f4ea7..ecb4d7ecd 100644 --- a/packages/client/api/src/compilerSupport/implementation.ts +++ b/packages/client/api/src/compilerSupport/implementation.ts @@ -29,7 +29,11 @@ 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, def: FunctionCallDef, script: string): Promise => { +export const v5_callFunction = async ( + rawFnArgs: Array, + def: FunctionCallDef, + script: string, +): Promise => { const { args, client: peer, config } = await extractFunctionArgs(rawFnArgs, def); const fluence = await getFluenceInterface(); @@ -48,7 +52,7 @@ export const callFunction = async (rawFnArgs: Array, 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 => { +export const v5_registerService = async (args: any[], def: ServiceDef): Promise => { const { peer, service, serviceId } = await extractServiceArgs(args, def.defaultServiceId); const fluence = await getFluenceInterface(); diff --git a/packages/client/api/src/index.ts b/packages/client/api/src/index.ts index bd451676d..cd0c35675 100644 --- a/packages/client/api/src/index.ts +++ b/packages/client/api/src/index.ts @@ -1,5 +1,12 @@ import { getFluenceInterface, getFluenceInterfaceFromGlobalThis } from './util.js'; -import { IFluenceClient, ClientConfig, RelayOptions, ConnectionState, ConnectionStates } from '@fluencelabs/interfaces'; +import { + IFluenceClient, + ClientConfig, + RelayOptions, + ConnectionState, + CallAquaFunctionType, + RegisterServiceType, +} from '@fluencelabs/interfaces'; export type { IFluenceClient, ClientConfig as ClientOptions, CallParams } from '@fluencelabs/interfaces'; export { @@ -8,7 +15,6 @@ export { ArrowWithCallbacks, ArrowWithoutCallbacks, BottomType, - FnConfig, FunctionCallConstants, FunctionCallDef, LabeledProductType, @@ -22,12 +28,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 @@ -86,3 +95,19 @@ export const createClient = async (relay: RelayOptions, config?: ClientConfig): const fluence = await getFluenceInterface(); 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); +}; diff --git a/packages/client/api/src/util.ts b/packages/client/api/src/util.ts index 32033886b..67d368f02 100644 --- a/packages/client/api/src/util.ts +++ b/packages/client/api/src/util.ts @@ -1,16 +1,16 @@ import type { - CallAquaFunction, + CallAquaFunctionType, ClientConfig, IFluenceClient, - RegisterService, + RegisterServiceType, RelayOptions, } from '@fluencelabs/interfaces'; type PublicFluenceInterface = { defaultClient: IFluenceClient | undefined; clientFactory: (relay: RelayOptions, config?: ClientConfig) => Promise; - callAquaFunction: CallAquaFunction; - registerService: RegisterService; + callAquaFunction: CallAquaFunctionType; + registerService: RegisterServiceType; }; export const getFluenceInterfaceFromGlobalThis = (): PublicFluenceInterface | undefined => { diff --git a/packages/core/interfaces/src/compilerSupport/compilerSupportInterface.ts b/packages/core/interfaces/src/compilerSupport/compilerSupportInterface.ts index f9c9f4b1e..639e4d151 100644 --- a/packages/core/interfaces/src/compilerSupport/compilerSupportInterface.ts +++ b/packages/core/interfaces/src/compilerSupport/compilerSupportInterface.ts @@ -1,21 +1,72 @@ 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; - args: { [key: string]: any }; + + /** + * Arguments to pass to the function + */ + args: PassedArgs; } -export type CallAquaFunction = (args: CallAquaFunctionArgs) => Promise; +/** + * Call a function from Aqua script + */ +export type CallAquaFunctionType = (args: CallAquaFunctionArgs) => Promise; +/** + * 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; } -export type RegisterService = (args: RegisterServiceArgs) => void; +/** + * Register a service defined in Aqua on a Fluence peer + */ +export type RegisterServiceType = (args: RegisterServiceArgs) => void; diff --git a/packages/core/js-peer/src/clientPeer/ClientPeer.ts b/packages/core/js-peer/src/clientPeer/ClientPeer.ts index 99845cb00..f099bd5be 100644 --- a/packages/core/js-peer/src/clientPeer/ClientPeer.ts +++ b/packages/core/js-peer/src/clientPeer/ClientPeer.ts @@ -1,9 +1,9 @@ import { ClientConfig, ConnectionState, IFluenceClient, PeerIdB58, RelayOptions } from '@fluencelabs/interfaces'; -import { RelayConnection, RelayConnectionConfig } from '../connection/index.js'; -import { IAvmRunner, IMarine } from '../interfaces/index.js'; +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'; export const makeClientPeerConfig = async ( relay: RelayOptions, @@ -35,7 +35,7 @@ export class ClientPeer extends FluencePeer implements IFluenceClient { peerConfig: PeerConfig, relayConfig: RelayConnectionConfig, keyPair: KeyPair, - marine: IMarine, + marine: IMarineHost, avmRunner: IAvmRunner, ) { const relayConnection = new RelayConnection(relayConfig); diff --git a/packages/core/js-peer/src/clientPeer/__test__/client.spec.ts b/packages/core/js-peer/src/clientPeer/__test__/client.spec.ts index e1fa9a9ce..fface35f5 100644 --- a/packages/core/js-peer/src/clientPeer/__test__/client.spec.ts +++ b/packages/core/js-peer/src/clientPeer/__test__/client.spec.ts @@ -1,10 +1,10 @@ import { it, describe, expect } from 'vitest'; -import { CallServiceData } from '../../interfaces/commonTypes.js'; import { handleTimeout } from '../../particle/Particle.js'; -import { doNothing } from '../../jsPeer/serviceUtils.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/interface.js'; describe('FluenceClient usage test suite', () => { it('should make a call through network', async () => { diff --git a/packages/core/js-peer/src/clientPeer/checkConnection.ts b/packages/core/js-peer/src/clientPeer/checkConnection.ts index 5bbafc991..23e022ab9 100644 --- a/packages/core/js-peer/src/clientPeer/checkConnection.ts +++ b/packages/core/js-peer/src/clientPeer/checkConnection.ts @@ -1,7 +1,7 @@ import { ClientPeer } from './ClientPeer.js'; import { logger } from '../util/logger.js'; -import { WrapFnIntoServiceCall } from '../jsPeer/serviceUtils.js'; +import { WrapFnIntoServiceCall } from '../jsServiceHost/serviceUtils.js'; import { handleTimeout } from '../particle/Particle.js'; const log = logger('connection'); diff --git a/packages/core/js-peer/src/compilerSupport/callFunction.ts b/packages/core/js-peer/src/compilerSupport/callFunction.ts index 3ad78be2a..1aa057e23 100644 --- a/packages/core/js-peer/src/compilerSupport/callFunction.ts +++ b/packages/core/js-peer/src/compilerSupport/callFunction.ts @@ -1,4 +1,4 @@ -import { getArgumentTypes, isReturnTypeVoid, CallAquaFunction } from '@fluencelabs/interfaces'; +import { getArgumentTypes, isReturnTypeVoid, CallAquaFunctionType } from '@fluencelabs/interfaces'; import { injectRelayService, @@ -25,7 +25,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); diff --git a/packages/core/js-peer/src/compilerSupport/conversions.ts b/packages/core/js-peer/src/compilerSupport/conversions.ts index e32db6b91..d125ddf13 100644 --- a/packages/core/js-peer/src/compilerSupport/conversions.ts +++ b/packages/core/js-peer/src/compilerSupport/conversions.ts @@ -1,7 +1,7 @@ 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/interface.js'; /** * Convert value from its representation in aqua language to representation in typescript diff --git a/packages/core/js-peer/src/compilerSupport/registerService.ts b/packages/core/js-peer/src/compilerSupport/registerService.ts index a9786c886..d8d10c5a9 100644 --- a/packages/core/js-peer/src/compilerSupport/registerService.ts +++ b/packages/core/js-peer/src/compilerSupport/registerService.ts @@ -1,11 +1,11 @@ -import type { RegisterService } from '@fluencelabs/interfaces'; +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 diff --git a/packages/core/js-peer/src/compilerSupport/services.ts b/packages/core/js-peer/src/compilerSupport/services.ts index 21a3c9136..6c9467c12 100644 --- a/packages/core/js-peer/src/compilerSupport/services.ts +++ b/packages/core/js-peer/src/compilerSupport/services.ts @@ -2,7 +2,6 @@ import { SecurityTetraplet } from '@fluencelabs/avm'; import { match } from 'ts-pattern'; import { Particle } from '../particle/Particle.js'; -import { CallServiceData, GenericCallServiceHandler, ResultCodes } from '../interfaces/commonTypes.js'; import { aquaArgs2Ts, responseServiceValue2ts, returnType2Aqua, ts2aqua } from './conversions.js'; import { @@ -13,6 +12,7 @@ import { NonArrowType, IFluenceInternalApi, } from '@fluencelabs/interfaces'; +import { CallServiceData, GenericCallServiceHandler, ResultCodes } from '../jsServiceHost/interface.js'; export interface ServiceDescription { serviceId: string; diff --git a/packages/core/js-peer/src/connection/index.ts b/packages/core/js-peer/src/connection/RelayConnection.ts similarity index 93% rename from packages/core/js-peer/src/connection/index.ts rename to packages/core/js-peer/src/connection/RelayConnection.ts index 51af4305b..7439f2676 100644 --- a/packages/core/js-peer/src/connection/index.ts +++ b/packages/core/js-peer/src/connection/RelayConnection.ts @@ -14,7 +14,6 @@ * limitations under the License. */ import { PeerIdB58 } from '@fluencelabs/interfaces'; -import { ParticleHandler, IModule, IConnection } from '../interfaces/index.js'; import { pipe } from 'it-pipe'; import { encode, decode } from 'it-length-prefixed'; import type { PeerId } from '@libp2p/interface-peer-id'; @@ -32,9 +31,12 @@ import { fromString } from 'uint8arrays/from-string'; import { toString } from 'uint8arrays/to-string'; import { logger } from '../util/logger.js'; -import { Subject, Subscribable } from 'rxjs'; -import { Particle } from '../particle/Particle.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 { IModule } from '../util/commonTypes.js'; const log = logger('connection'); @@ -101,7 +103,7 @@ export class RelayConnection implements IModule, IConnection { return true; } - particleSource = new Subject(); + particleSource = new Subject(); async start(): Promise { // check if already started @@ -135,7 +137,7 @@ export class RelayConnection implements IModule, IConnection { await this.lib2p2Peer.stop(); } - async sendParticle(nextPeerIds: PeerIdB58[], particle: Particle): Promise { + async sendParticle(nextPeerIds: PeerIdB58[], particle: IParticle): Promise { if (this.lib2p2Peer === null) { throw new Error('Relay connection is not started'); } @@ -161,7 +163,7 @@ export class RelayConnection implements IModule, IConnection { const sink = stream.sink; pipe( - [fromString(particle.toString())], + [fromString(serializeToString(particle))], // @ts-ignore encode(), sink, @@ -173,8 +175,6 @@ export class RelayConnection implements IModule, IConnection { throw new Error('Relay connection is not started'); } - // TODO: make it configurable - this.lib2p2Peer.handle( [PROTOCOL_NAME], async ({ connection, stream }) => { diff --git a/packages/core/js-peer/src/connection/interfaces.ts b/packages/core/js-peer/src/connection/interfaces.ts new file mode 100644 index 000000000..cad443e7a --- /dev/null +++ b/packages/core/js-peer/src/connection/interfaces.ts @@ -0,0 +1,30 @@ +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; + + /** + * 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; + + /** + * 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; +} diff --git a/packages/core/js-peer/src/ephemeral/__test__/ephemeral.spec.ts b/packages/core/js-peer/src/ephemeral/__test__/ephemeral.spec.ts index 54ad12698..036c45a1d 100644 --- a/packages/core/js-peer/src/ephemeral/__test__/ephemeral.spec.ts +++ b/packages/core/js-peer/src/ephemeral/__test__/ephemeral.spec.ts @@ -1,8 +1,7 @@ import { it, describe, expect, beforeEach, afterEach } from 'vitest'; -import { ResultCodes } from '../../interfaces/commonTypes.js'; import { FluencePeer } from '../../jsPeer/FluencePeer.js'; +import { CallServiceData, ResultCodes } from '../../jsServiceHost/interface.js'; import { KeyPair } from '../../keypair/index.js'; -import { TestPeer } from '../../util/testUtils.js'; import { EphemeralNetworkClient } from '../client.js'; import { EphemeralNetwork, defaultConfig } from '../network.js'; @@ -62,7 +61,7 @@ describe('Ephemeral networks tests', () => { const particle = client.internals.createNewParticle(script); const promise = new Promise((resolve) => { - client.internals.regHandler.forParticle(particle.id, 'test', 'test', (req) => { + client.internals.regHandler.forParticle(particle.id, 'test', 'test', (req: CallServiceData) => { resolve('success'); return { result: 'test', diff --git a/packages/core/js-peer/src/ephemeral/network.ts b/packages/core/js-peer/src/ephemeral/network.ts index 9bbed1f81..3a6488bdd 100644 --- a/packages/core/js-peer/src/ephemeral/network.ts +++ b/packages/core/js-peer/src/ephemeral/network.ts @@ -1,5 +1,4 @@ import { PeerIdB58 } from '@fluencelabs/interfaces'; -import { ParticleHandler, IConnection, IAvmRunner, IMarine } from '../interfaces/index.js'; import { fromBase64Sk, KeyPair } from '../keypair/index.js'; import { MarineBackgroundRunner } from '../marine/worker/index.js'; @@ -12,6 +11,8 @@ import { Particle } from '../particle/Particle.js'; import { WasmLoaderFromNpm } from '../marine/deps-loader/node.js'; import { MarineBasedAvmRunner } from '../jsPeer/avm.js'; import { FluencePeer } from '../jsPeer/FluencePeer.js'; +import { IConnection } from '../connection/interfaces.js'; +import { IAvmRunner, IMarineHost } from '../marine/interfaces.js'; const log = logger('ephemeral'); @@ -177,7 +178,7 @@ export class EphemeralConnection implements IConnection, IEphemeralConnection { class EphemeralPeer extends FluencePeer { ephemeralConnection: EphemeralConnection; - constructor(keyPair: KeyPair, marine: IMarine, avm: IAvmRunner) { + constructor(keyPair: KeyPair, marine: IMarineHost, avm: IAvmRunner) { const conn = new EphemeralConnection(keyPair.getPeerId()); super({}, keyPair, marine, avm, conn); diff --git a/packages/core/js-peer/src/interfaces/index.ts b/packages/core/js-peer/src/interfaces/index.ts deleted file mode 100644 index 5ce4d650e..000000000 --- a/packages/core/js-peer/src/interfaces/index.ts +++ /dev/null @@ -1,102 +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 type { Observable, Subscribable } from 'rxjs'; -import type { PeerIdB58 } from '@fluencelabs/interfaces'; -import type { JSONArray, JSONObject } from '@fluencelabs/marine-js/dist/types'; -import type { RunParameters, CallResultsArray, InterpreterResult } from '@fluencelabs/avm'; -// @ts-ignore -import type { WorkerImplementation } from 'threads/dist/types/master'; -import { Particle } from '../particle/Particle.js'; - -export type ParticleHandler = (particle: string) => void; - -/** - * Base class for connectivity layer to Fluence Network - */ -/* -export abstract class FluenceConnection { - abstract readonly relayPeerId: PeerIdB58 | null; - abstract connect(onIncomingParticle: ParticleHandler): Promise; - abstract disconnect(): Promise; - abstract sendParticle(nextPeerIds: PeerIdB58[], particle: string): Promise; -} -*/ - -export interface IConnection { - particleSource: Subscribable; - sendParticle(nextPeerIds: PeerIdB58[], particle: Particle): Promise; - getRelayPeerId(): PeerIdB58; - supportsRelay(): boolean; -} - -export interface IMarine extends IModule { - createService(serviceModule: SharedArrayBuffer | Buffer, serviceId: string): Promise; - - callService( - serviceId: string, - functionName: string, - args: JSONArray | JSONObject, - callParams: any, - ): Promise; -} - -export interface IAvmRunner extends IModule { - run( - runParams: RunParameters, - air: string, - prevData: Uint8Array, - data: Uint8Array, - callResults: CallResultsArray, - ): Promise; -} - -export interface IModule { - start(): Promise; - stop(): Promise; -} - -export interface IValueLoader { - getValue(): T; -} - -export interface IWasmLoader extends IValueLoader, IModule {} - -export interface IWorkerLoader extends IValueLoader, IModule {} - -export class LazyLoader implements IModule, IValueLoader { - private value: T | null = null; - - constructor(private loadValue: () => Promise | T) {} - - getValue(): T { - if (this.value == null) { - throw new Error('Value has not been loaded. Call `start` method to load the value.'); - } - - return this.value; - } - - async start() { - if (this.value !== null) { - return; - } - - this.value = await this.loadValue(); - } - - async stop() {} -} diff --git a/packages/core/js-peer/src/jsPeer/FluencePeer.ts b/packages/core/js-peer/src/jsPeer/FluencePeer.ts index 074f03075..3c8218bb1 100644 --- a/packages/core/js-peer/src/jsPeer/FluencePeer.ts +++ b/packages/core/js-peer/src/jsPeer/FluencePeer.ts @@ -15,16 +15,17 @@ */ import 'buffer'; -import { IAvmRunner, IMarine, IConnection } from '../interfaces/index.js'; import { KeyPair } from '../keypair/index.js'; -import { - CallServiceData, - CallServiceResult, - GenericCallServiceHandler, - ResultCodes, -} from '../interfaces/commonTypes.js'; + import type { PeerIdB58 } from '@fluencelabs/interfaces'; -import { Particle, ParticleExecutionStage, ParticleQueueItem } from '../particle/Particle.js'; +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 { builtInServices } from '../services/builtins.js'; @@ -33,12 +34,21 @@ import { registerSig } from '../services/_aqua/services.js'; import { registerSrv } from '../services/_aqua/single-module-srv.js'; import { Buffer } from 'buffer'; -import { JSONValue } from '@fluencelabs/avm'; import { NodeUtils, Srv } from '../services/SingleModuleSrv.js'; import { registerNodeUtils } from '../services/_aqua/node-utils.js'; import { logger } from '../util/logger.js'; -import { ServiceError } from './serviceUtils.js'; +import { getParticleContext, 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, + ResultCodes, +} from '../jsServiceHost/interface.js'; +import { JSONValue } from '../util/commonTypes.js'; const log = logger('particle'); @@ -72,7 +82,7 @@ export abstract class FluencePeer { constructor( protected readonly config: PeerConfig, public readonly keyPair: KeyPair, - protected readonly marine: IMarine, + protected readonly marine: IMarineHost, protected readonly avmRunner: IAvmRunner, protected readonly connection: IConnection, ) { @@ -114,7 +124,7 @@ export abstract class FluencePeer { this.particleSourceSubscription = this.connection.particleSource.subscribe({ next: (p) => { - this._incomingParticles.next({ particle: p, onStageChange: () => {} }); + this._incomingParticles.next({ particle: p, callResults: [], onStageChange: () => {} }); }, }); @@ -222,7 +232,7 @@ export abstract class FluencePeer { } }, - createNewParticle: (script: string, ttl: number = this.defaultTTL): Particle => { + createNewParticle: (script: string, ttl: number = this.defaultTTL): IParticle => { return Particle.createNew(script, this.keyPair.getPeerId(), ttl); }, @@ -230,7 +240,7 @@ export abstract class FluencePeer { * Initiates a new particle execution starting from local peer * @param particle - particle to start execution of */ - initiateParticle: (particle: Particle, onStageChange: (stage: ParticleExecutionStage) => void): void => { + initiateParticle: (particle: IParticle, onStageChange: (stage: ParticleExecutionStage) => void): void => { if (!this.isInitialized) { throw new Error('Cannot initiate new particle: peer is not initialized'); } @@ -241,6 +251,7 @@ export abstract class FluencePeer { this._incomingParticles.next({ particle: particle, + callResults: [], onStageChange: onStageChange, }); }, @@ -312,17 +323,17 @@ export abstract class FluencePeer { 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, + tap((item) => { + log.debug('id %s. received:', item.particle.id); + log.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.trace('id %s. script: %s', x.particle.id, x.particle.script); - log.trace('id %s. call results: %j', x.particle.id, x.particle.callResults); + log.trace('id %s. script: %s', item.particle.id, item.particle.script); + log.trace('id %s. call results: %j', item.particle.id, item.callResults); }), filterExpiredParticles(this._expireParticle.bind(this)), ) @@ -336,7 +347,7 @@ export abstract class FluencePeer { const timeout = setTimeout(() => { this._expireParticle(item); - }, p.actualTtl()); + }, getActualTTL(p)); this.timeouts.push(timeout); } @@ -413,7 +424,7 @@ export abstract class FluencePeer { item.particle.script, prevData, item.particle.data, - item.particle.callResults, + item.callResults, ); if (!(avmCallResult instanceof Error) && avmCallResult.retCode === 0) { @@ -434,7 +445,7 @@ export abstract class FluencePeer { } // Do not proceed further if the particle is expired - if (item.particle.hasExpired()) { + if (hasExpired(item.particle)) { return; } @@ -470,9 +481,7 @@ export abstract class FluencePeer { // 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; + const newParticle = cloneWithNewData(item.particle, Buffer.from(item.result.data)); this._outgoingParticles.next({ ...item, particle: newParticle, @@ -489,10 +498,10 @@ export abstract class FluencePeer { args: cr.arguments, serviceId: cr.serviceId, tetraplets: cr.tetraplets, - particleContext: item.particle.getParticleContext(), + particleContext: getParticleContext(item.particle), }; - if (item.particle.hasExpired()) { + if (hasExpired(item.particle)) { // just in case do not call any services if the particle is already expired return; } @@ -518,11 +527,12 @@ export abstract class FluencePeer { retCode: res.retCode, }; - const newParticle = item.particle.clone(); - newParticle.callResults = [[key, serviceResult]]; - newParticle.data = Buffer.from([]); - - particlesQueue.next({ ...item, particle: newParticle }); + const newParticle = cloneWithNewData(item.particle, Buffer.from([])); + particlesQueue.next({ + ...item, + particle: newParticle, + callResults: [[key, serviceResult]], + }); }); } } else { @@ -607,10 +617,10 @@ function registerDefaultServices(peer: FluencePeer) { function filterExpiredParticles(onParticleExpiration: (item: ParticleQueueItem) => void) { return pipe( tap((item: ParticleQueueItem) => { - if (item.particle.hasExpired()) { + if (hasExpired(item.particle)) { onParticleExpiration(item); } }), - filter((x: ParticleQueueItem) => !x.particle.hasExpired()), + filter((x: ParticleQueueItem) => !hasExpired(x.particle)), ); } diff --git a/packages/core/js-peer/src/jsPeer/avm.ts b/packages/core/js-peer/src/jsPeer/avm.ts index b154130b4..2578ebb10 100644 --- a/packages/core/js-peer/src/jsPeer/avm.ts +++ b/packages/core/js-peer/src/jsPeer/avm.ts @@ -1,9 +1,9 @@ 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, diff --git a/packages/core/js-peer/src/jsPeer/serviceUtils.ts b/packages/core/js-peer/src/jsPeer/serviceUtils.ts deleted file mode 100644 index 18baa65bd..000000000 --- a/packages/core/js-peer/src/jsPeer/serviceUtils.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CallServiceData, CallServiceResult, CallServiceResultType, ResultCodes } from '../interfaces/commonTypes.js'; - -export const doNothing = (..._args: Array) => 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); - } -} diff --git a/packages/core/js-peer/src/interfaces/commonTypes.ts b/packages/core/js-peer/src/jsServiceHost/interface.ts similarity index 71% rename from packages/core/js-peer/src/interfaces/commonTypes.ts rename to packages/core/js-peer/src/jsServiceHost/interface.ts index 878337c6c..366bdad85 100644 --- a/packages/core/js-peer/src/interfaces/commonTypes.ts +++ b/packages/core/js-peer/src/jsServiceHost/interface.ts @@ -1,21 +1,6 @@ -/* - * 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 type { PeerIdB58 } from '@fluencelabs/interfaces'; import type { SecurityTetraplet } from '@fluencelabs/avm'; +import { JSONValue } from '../util/commonTypes.js'; export enum ResultCodes { success = 0, @@ -106,7 +91,3 @@ export interface CallServiceResult { */ result: CallServiceResultType; } - -export type JSONValue = string | number | boolean | null | { [x: string]: JSONValue } | Array; -export type JSONArray = Array; -export type JSONObject = { [x: string]: JSONValue }; diff --git a/packages/core/js-peer/src/jsServiceHost/serviceUtils.ts b/packages/core/js-peer/src/jsServiceHost/serviceUtils.ts new file mode 100644 index 000000000..327972b86 --- /dev/null +++ b/packages/core/js-peer/src/jsServiceHost/serviceUtils.ts @@ -0,0 +1,35 @@ +import { IParticle } from '../particle/interfaces.js'; +import { + CallServiceData, + CallServiceResult, + CallServiceResultType, + ParticleContext, + ResultCodes, +} from './interface.js'; + +export const doNothing = (..._args: Array) => 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, + }; +}; diff --git a/packages/core/js-peer/src/marine/deps-loader/common.ts b/packages/core/js-peer/src/marine/deps-loader/common.ts index c290ad45e..258e9aab4 100644 --- a/packages/core/js-peer/src/marine/deps-loader/common.ts +++ b/packages/core/js-peer/src/marine/deps-loader/common.ts @@ -3,8 +3,8 @@ 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 { constructor(b64script: string) { diff --git a/packages/core/js-peer/src/marine/deps-loader/node.ts b/packages/core/js-peer/src/marine/deps-loader/node.ts index 38b68c47c..230dda25a 100644 --- a/packages/core/js-peer/src/marine/deps-loader/node.ts +++ b/packages/core/js-peer/src/marine/deps-loader/node.ts @@ -1,5 +1,4 @@ import { createRequire } from 'module'; -import { LazyLoader } from '../../interfaces/index.js'; // @ts-ignore import type { WorkerImplementation } from 'threads/dist/types/master'; @@ -8,6 +7,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); diff --git a/packages/core/js-peer/src/marine/deps-loader/web.ts b/packages/core/js-peer/src/marine/deps-loader/web.ts index 6beb6162a..e554083c8 100644 --- a/packages/core/js-peer/src/marine/deps-loader/web.ts +++ b/packages/core/js-peer/src/marine/deps-loader/web.ts @@ -1,5 +1,5 @@ import { Buffer } from 'buffer'; -import { LazyLoader } from '../../interfaces/index.js'; +import { LazyLoader } from '../interfaces.js'; const bufferToSharedArrayBuffer = (buffer: Buffer): SharedArrayBuffer => { const sab = new SharedArrayBuffer(buffer.length); diff --git a/packages/core/js-peer/src/marine/interfaces.ts b/packages/core/js-peer/src/marine/interfaces.ts new file mode 100644 index 000000000..f9264b209 --- /dev/null +++ b/packages/core/js-peer/src/marine/interfaces.ts @@ -0,0 +1,57 @@ +import { CallResultsArray, InterpreterResult, RunParameters } from '@fluencelabs/avm'; +import { IModule, JSONArray, JSONObject } from '../util/commonTypes.js'; +// @ts-ignore +import type { WorkerImplementation } from 'threads/dist/types/master'; + +export interface IMarineHost extends IModule { + createService(serviceModule: SharedArrayBuffer | Buffer, serviceId: string): Promise; + + callService( + serviceId: string, + functionName: string, + args: JSONArray | JSONObject, + callParams: any, + ): Promise; +} + +export interface IAvmRunner extends IModule { + run( + runParams: RunParameters, + air: string, + prevData: Uint8Array, + data: Uint8Array, + callResults: CallResultsArray, + ): Promise; +} + +export interface IValueLoader { + getValue(): T; +} + +export interface IWasmLoader extends IValueLoader, IModule {} + +export interface IWorkerLoader extends IValueLoader, IModule {} + +export class LazyLoader implements IModule, IValueLoader { + private value: T | null = null; + + constructor(private loadValue: () => Promise | T) {} + + getValue(): T { + if (this.value == null) { + throw new Error('Value has not been loaded. Call `start` method to load the value.'); + } + + return this.value; + } + + async start() { + if (this.value !== null) { + return; + } + + this.value = await this.loadValue(); + } + + async stop() {} +} diff --git a/packages/core/js-peer/src/marine/worker-script/workerLoader.ts b/packages/core/js-peer/src/marine/worker-script/workerLoader.ts index 5c26226d4..61207d56f 100644 --- a/packages/core/js-peer/src/marine/worker-script/workerLoader.ts +++ b/packages/core/js-peer/src/marine/worker-script/workerLoader.ts @@ -1,9 +1,8 @@ -import { LazyLoader } from '../../interfaces/index.js'; - // @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 { constructor() { diff --git a/packages/core/js-peer/src/marine/worker/index.ts b/packages/core/js-peer/src/marine/worker/index.ts index 2f2fd2a95..cb8fed976 100644 --- a/packages/core/js-peer/src/marine/worker/index.ts +++ b/packages/core/js-peer/src/marine/worker/index.ts @@ -16,7 +16,6 @@ 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'; @@ -24,8 +23,9 @@ import { spawn, Thread } from 'threads'; import type { ModuleThread } from 'threads'; 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 workerThread?: ModuleThread; private loggers: Map = new Map(); diff --git a/packages/core/js-peer/src/particle/Particle.ts b/packages/core/js-peer/src/particle/Particle.ts index fbbd69fbc..759215d87 100644 --- a/packages/core/js-peer/src/particle/Particle.ts +++ b/packages/core/js-peer/src/particle/Particle.ts @@ -17,25 +17,26 @@ import { fromUint8Array, toUint8Array } from 'js-base64'; import { CallResultsArray } from '@fluencelabs/avm'; import { v4 as uuidv4 } from 'uuid'; -import { ParticleContext } from '../interfaces/commonTypes.js'; import { Buffer } from 'buffer'; +import { IParticle } from './interfaces.js'; +import { ParticleContext } from '../jsServiceHost/interface.js'; -export class Particle { - // TODO: make it not optional (should be added to the constructor) - signature?: string; - callResults: CallResultsArray = []; +export class Particle implements IParticle { + readonly signature: undefined; constructor( - public id: string, - public timestamp: number, - public script: string, - public data: Uint8Array, - public ttl: number, - public initPeerId: string, - ) {} + 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(genUUID(), Date.now(), script, Buffer.from([]), ttl, initPeerId); + return new Particle(uuidv4(), Date.now(), script, Buffer.from([]), ttl, initPeerId); } static fromString(str: string): Particle { @@ -49,51 +50,54 @@ export class Particle { 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(); - } +/** + * 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(); +}; - hasExpired(): boolean { - return this.actualTtl() <= 0; - } +/** + * Returns true if particle has expired + */ +export const hasExpired = (particle: IParticle): boolean => { + return getActualTTL(particle) <= 0; +}; - clone(): Particle { - const res = new Particle(this.id, this.timestamp, this.script, this.data, this.ttl, this.initPeerId); +/** + * 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); +}; - res.signature = this.signature; - res.callResults = this.callResults; - return res; - } +/** + * Creates a deep copy of a particle + */ +export const fullClone = (particle: IParticle): IParticle => { + return JSON.parse(JSON.stringify(particle)); +}; - 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), - }); - } -} +/** + * 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), + }); +}; export type ParticleExecutionStage = | { stage: 'received' } @@ -105,7 +109,8 @@ export type ParticleExecutionStage = | { stage: 'expired' }; export interface ParticleQueueItem { - particle: Particle; + particle: IParticle; + callResults: CallResultsArray; onStageChange: (state: ParticleExecutionStage) => void; } @@ -114,7 +119,3 @@ export const handleTimeout = (fn: () => void) => (stage: ParticleExecutionStage) fn(); } }; - -function genUUID() { - return uuidv4(); -} diff --git a/packages/core/js-peer/src/particle/interfaces.ts b/packages/core/js-peer/src/particle/interfaces.ts new file mode 100644 index 000000000..05475bebc --- /dev/null +++ b/packages/core/js-peer/src/particle/interfaces.ts @@ -0,0 +1,44 @@ +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; +} diff --git a/packages/core/js-peer/src/services/__test__/builtInHandler.spec.ts b/packages/core/js-peer/src/services/__test__/builtInHandler.spec.ts index 879a2b6cf..9d75391f7 100644 --- a/packages/core/js-peer/src/services/__test__/builtInHandler.spec.ts +++ b/packages/core/js-peer/src/services/__test__/builtInHandler.spec.ts @@ -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 '../Sig.js'; import { allowServiceFn } from '../securityGuard.js'; import { builtInServices } from '../builtins.js'; +import { CallServiceData } from '../../jsServiceHost/interface.js'; const a10b20 = `{ "a": 10, diff --git a/packages/core/js-peer/src/services/__test__/jsonBuiltin.spec.ts b/packages/core/js-peer/src/services/__test__/jsonBuiltin.spec.ts index 6bfa714ab..07228091c 100644 --- a/packages/core/js-peer/src/services/__test__/jsonBuiltin.spec.ts +++ b/packages/core/js-peer/src/services/__test__/jsonBuiltin.spec.ts @@ -3,7 +3,7 @@ import { it, describe, expect, beforeEach, afterEach } from 'vitest'; import { Particle } from '../../particle/Particle.js'; import { FluencePeer } from '../../jsPeer/FluencePeer.js'; import { mkTestPeer } from '../../util/testUtils.js'; -import { doNothing } from '../../jsPeer/serviceUtils.js'; +import { doNothing } from '../../jsServiceHost/serviceUtils.js'; let peer: FluencePeer; diff --git a/packages/core/js-peer/src/services/builtins.ts b/packages/core/js-peer/src/services/builtins.ts index 292a1c424..93c35e92d 100644 --- a/packages/core/js-peer/src/services/builtins.ts +++ b/packages/core/js-peer/src/services/builtins.ts @@ -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 { isString, jsonify } from '../util/utils.js'; import { Buffer } from 'buffer'; +import { GenericCallServiceHandler, ResultCodes } from '../jsServiceHost/interface.js'; //@ts-ignore const { encode, decode } = bs58.default; diff --git a/packages/core/js-peer/src/util/commonTypes.ts b/packages/core/js-peer/src/util/commonTypes.ts new file mode 100644 index 000000000..f9e9667c0 --- /dev/null +++ b/packages/core/js-peer/src/util/commonTypes.ts @@ -0,0 +1,8 @@ +export interface IModule { + start(): Promise; + stop(): Promise; +} + +export type JSONValue = string | number | boolean | null | { [x: string]: JSONValue } | Array; +export type JSONArray = Array; +export type JSONObject = { [x: string]: JSONValue }; diff --git a/packages/core/js-peer/src/util/testUtils.ts b/packages/core/js-peer/src/util/testUtils.ts index 30cae996f..16f5b30da 100644 --- a/packages/core/js-peer/src/util/testUtils.ts +++ b/packages/core/js-peer/src/util/testUtils.ts @@ -10,11 +10,11 @@ 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 { IConnection } from '../interfaces/index.js'; import { Subject, Subscribable } from 'rxjs'; -import { WrapFnIntoServiceCall } from '../jsPeer/serviceUtils.js'; +import { WrapFnIntoServiceCall } from '../jsServiceHost/serviceUtils.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, From d0b4c004449088dfd7aa5bef32ece7fee7bdb6e7 Mon Sep 17 00:00:00 2001 From: Pavel Murygin Date: Fri, 31 Mar 2023 20:11:37 +0400 Subject: [PATCH 05/32] IModule - > IStartable --- .../core/js-peer/src/connection/RelayConnection.ts | 4 ++-- packages/core/js-peer/src/marine/interfaces.ts | 12 ++++++------ packages/core/js-peer/src/util/commonTypes.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/core/js-peer/src/connection/RelayConnection.ts b/packages/core/js-peer/src/connection/RelayConnection.ts index 7439f2676..cb0641db5 100644 --- a/packages/core/js-peer/src/connection/RelayConnection.ts +++ b/packages/core/js-peer/src/connection/RelayConnection.ts @@ -36,7 +36,7 @@ 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 { IModule } from '../util/commonTypes.js'; +import { IStartable } from '../util/commonTypes.js'; const log = logger('connection'); @@ -77,7 +77,7 @@ export interface RelayConnectionConfig { /** * Implementation for JS peers which connects to Fluence through relay node */ -export class RelayConnection implements IModule, IConnection { +export class RelayConnection implements IStartable, IConnection { private relayAddress: Multiaddr; private lib2p2Peer: Libp2p | null = null; private handleOptions: { diff --git a/packages/core/js-peer/src/marine/interfaces.ts b/packages/core/js-peer/src/marine/interfaces.ts index f9264b209..93e3b6bab 100644 --- a/packages/core/js-peer/src/marine/interfaces.ts +++ b/packages/core/js-peer/src/marine/interfaces.ts @@ -1,9 +1,9 @@ import { CallResultsArray, InterpreterResult, RunParameters } from '@fluencelabs/avm'; -import { IModule, JSONArray, JSONObject } from '../util/commonTypes.js'; +import { IStartable, JSONArray, JSONObject } from '../util/commonTypes.js'; // @ts-ignore import type { WorkerImplementation } from 'threads/dist/types/master'; -export interface IMarineHost extends IModule { +export interface IMarineHost extends IStartable { createService(serviceModule: SharedArrayBuffer | Buffer, serviceId: string): Promise; callService( @@ -14,7 +14,7 @@ export interface IMarineHost extends IModule { ): Promise; } -export interface IAvmRunner extends IModule { +export interface IAvmRunner extends IStartable { run( runParams: RunParameters, air: string, @@ -28,11 +28,11 @@ export interface IValueLoader { getValue(): T; } -export interface IWasmLoader extends IValueLoader, IModule {} +export interface IWasmLoader extends IValueLoader, IStartable {} -export interface IWorkerLoader extends IValueLoader, IModule {} +export interface IWorkerLoader extends IValueLoader, IStartable {} -export class LazyLoader implements IModule, IValueLoader { +export class LazyLoader implements IStartable, IValueLoader { private value: T | null = null; constructor(private loadValue: () => Promise | T) {} diff --git a/packages/core/js-peer/src/util/commonTypes.ts b/packages/core/js-peer/src/util/commonTypes.ts index f9e9667c0..73f34c084 100644 --- a/packages/core/js-peer/src/util/commonTypes.ts +++ b/packages/core/js-peer/src/util/commonTypes.ts @@ -1,4 +1,4 @@ -export interface IModule { +export interface IStartable { start(): Promise; stop(): Promise; } From ae255d72ac43ff7dd2a2fc00812494676300af7f Mon Sep 17 00:00:00 2001 From: Pavel Murygin Date: Sat, 1 Apr 2023 21:54:35 +0400 Subject: [PATCH 06/32] Refactored configs a little --- package.json | 2 +- .../@tests/smoke/web-cra-ts/public/index.html | 2 +- packages/core/interfaces/src/fluenceClient.ts | 12 +++ .../core/js-peer/src/clientPeer/ClientPeer.ts | 12 ++- .../js-peer/src/clientPeer/checkConnection.ts | 2 +- .../js-peer/src/connection/RelayConnection.ts | 26 +++--- .../src/ephemeral/__test__/ephemeral.spec.ts | 4 +- .../core/js-peer/src/ephemeral/network.ts | 4 +- .../core/js-peer/src/jsPeer/FluencePeer.ts | 82 +++++++++---------- .../js-peer/src/jsPeer/__test__/avm.spec.ts | 4 +- packages/core/js-peer/src/util/testUtils.ts | 5 +- 11 files changed, 84 insertions(+), 71 deletions(-) diff --git a/package.json b/package.json index 38f96612b..9c9ae45f8 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "pnpm": ">=3" }, "scripts": { - "simulate-cdn": "http-server -p 8765 ./packages/client/js-client.web.standalone/dist" + "simulate-cdn": "http-server -p 8766 ./packages/client/js-client.web.standalone/dist" }, "author": "Fluence Labs", "license": "Apache-2.0", diff --git a/packages/@tests/smoke/web-cra-ts/public/index.html b/packages/@tests/smoke/web-cra-ts/public/index.html index 5e5194445..262651b92 100644 --- a/packages/@tests/smoke/web-cra-ts/public/index.html +++ b/packages/@tests/smoke/web-cra-ts/public/index.html @@ -7,7 +7,7 @@ - +