diff --git a/packages/core/interfaces/src/commonTypes.ts b/packages/core/interfaces/src/commonTypes.ts index 34773820..3ff3722f 100644 --- a/packages/core/interfaces/src/commonTypes.ts +++ b/packages/core/interfaces/src/commonTypes.ts @@ -14,60 +14,11 @@ * limitations under the License. */ -import type { SecurityTetraplet } from "@fluencelabs/avm"; - -import { InterfaceToType, MaybePromise } from "./utils.js"; - /** * Peer ID's id as a base58 string (multihash/CIDv0). */ export type PeerIdB58 = string; -/** - * Additional information about a service call - * @typeparam ArgName - */ -export type 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; -}; - -export type ServiceImpl = Record< - string, - ( - ...args: [...JSONArray, CallParams] - ) => MaybePromise ->; - export type JSONValue = | string | number diff --git a/packages/core/js-client/src/clientPeer/__test__/client.spec.ts b/packages/core/js-client/src/clientPeer/__test__/client.spec.ts index 41806e9a..fb544cb6 100644 --- a/packages/core/js-client/src/clientPeer/__test__/client.spec.ts +++ b/packages/core/js-client/src/clientPeer/__test__/client.spec.ts @@ -17,8 +17,8 @@ import { JSONValue } from "@fluencelabs/interfaces"; import { it, describe, expect } from "vitest"; +import { SendError } from "../../jsPeer/errors.js"; import { CallServiceData } from "../../jsServiceHost/interfaces.js"; -import { doNothing } from "../../jsServiceHost/serviceUtils.js"; import { handleTimeout } from "../../particle/Particle.js"; import { registerHandlersHelper, withClient } from "../../util/testUtils.js"; import { checkConnection } from "../checkConnection.js"; @@ -71,7 +71,11 @@ describe("FluenceClient usage test suite", () => { }, }); - peer.internals.initiateParticle(particle, handleTimeout(reject)); + peer.internals.initiateParticle( + particle, + () => {}, + handleTimeout(reject), + ); }); expect(result).toBe("hello world!"); @@ -124,7 +128,11 @@ describe("FluenceClient usage test suite", () => { throw particle; } - peer1.internals.initiateParticle(particle, doNothing); + peer1.internals.initiateParticle( + particle, + () => {}, + () => {}, + ); expect(await res).toEqual("test"); }); @@ -172,13 +180,13 @@ describe("FluenceClient usage test suite", () => { ); }); - it("With connection options: defaultTTL", async () => { + it.only("With connection options: defaultTTL", async () => { await withClient(RELAY, { defaultTtlMs: 1 }, async (peer) => { const isConnected = await checkConnection(peer); expect(isConnected).toBeFalsy(); }); - }); + }, 1000); }); it.skip("Should throw correct error when the client tries to send a particle not to the relay", async () => { @@ -206,11 +214,15 @@ describe("FluenceClient usage test suite", () => { }, }); - peer.internals.initiateParticle(particle, (stage) => { - if (stage.stage === "sendingError") { - reject(stage.errorMessage); - } - }); + peer.internals.initiateParticle( + particle, + () => {}, + (error: Error) => { + if (error instanceof SendError) { + reject(error.message); + } + }, + ); }); await promise; diff --git a/packages/core/js-client/src/clientPeer/checkConnection.ts b/packages/core/js-client/src/clientPeer/checkConnection.ts index e2f39824..387dbc42 100644 --- a/packages/core/js-client/src/clientPeer/checkConnection.ts +++ b/packages/core/js-client/src/clientPeer/checkConnection.ts @@ -110,6 +110,7 @@ export const checkConnection = async ( peer.internals.initiateParticle( particle, + () => {}, handleTimeout(() => { reject("particle timed out"); }), diff --git a/packages/core/js-client/src/compilerSupport/callFunction.ts b/packages/core/js-client/src/compilerSupport/callFunction.ts index 7786cee8..a5d7ebd4 100644 --- a/packages/core/js-client/src/compilerSupport/callFunction.ts +++ b/packages/core/js-client/src/compilerSupport/callFunction.ts @@ -16,13 +16,7 @@ import assert from "assert"; -import { - FnConfig, - FunctionCallDef, - getArgumentTypes, - isReturnTypeVoid, - PassedArgs, -} from "@fluencelabs/interfaces"; +import { FnConfig, JSONValue } from "@fluencelabs/interfaces"; import { FluencePeer } from "../jsPeer/FluencePeer.js"; import { logger } from "../util/logger.js"; @@ -36,6 +30,7 @@ import { ServiceDescription, userHandlerService, } from "./services.js"; +import { ServiceImpl } from "./types.js"; const log = logger("aqua"); @@ -52,43 +47,35 @@ const log = logger("aqua"); */ type CallAquaFunctionArgs = { - def: FunctionCallDef; script: string; config: FnConfig; peer: FluencePeer; - args: PassedArgs; + args: { [key: string]: JSONValue | ServiceImpl[string] }; }; export const callAquaFunction = async ({ - def, script, config, peer, args, }: CallAquaFunctionArgs) => { // TODO: this function should be rewritten. We can remove asserts if we wont check definition there - log.trace("calling aqua function %j", { def, script, config, args }); - const argumentTypes = getArgumentTypes(def); + log.trace("calling aqua function %j", { script, config, args }); const particle = await peer.internals.createNewParticle(script, config.ttl); return new Promise((resolve, reject) => { for (const [name, argVal] of Object.entries(args)) { - const type = argumentTypes[name]; let service: ServiceDescription; - if (type.tag === "arrow") { + if (typeof argVal === "function") { // TODO: Add validation here assert( typeof argVal === "function", "Should not be possible, bad types", ); - service = userHandlerService( - def.names.callbackSrv, - [name, type], - argVal, - ); + service = userHandlerService("callbackSrv", name, argVal); } else { // TODO: Add validation here assert( @@ -96,50 +83,31 @@ export const callAquaFunction = async ({ "Should not be possible, bad types", ); - service = injectValueService(def.names.getDataSrv, name, type, argVal); + console.log("inject service", name, argVal); + service = injectValueService("getDataSrv", name, argVal); } registerParticleScopeService(peer, particle, service); } - registerParticleScopeService(peer, particle, responseService(def, resolve)); + registerParticleScopeService(peer, particle, responseService(resolve)); - registerParticleScopeService(peer, particle, injectRelayService(def, peer)); + registerParticleScopeService(peer, particle, injectRelayService(peer)); - registerParticleScopeService( - peer, - particle, - errorHandlingService(def, reject), - ); + registerParticleScopeService(peer, particle, errorHandlingService(reject)); + // If function is void, then it's completed when one of the two conditions is met: + // 1. The particle is sent to the network (state 'sent') + // 2. All CallRequests are executed, e.g., all variable loading and local function calls are completed (state 'localWorkDone') - peer.internals.initiateParticle(particle, (stage) => { - // If function is void, then it's completed when one of the two conditions is met: - // 1. The particle is sent to the network (state 'sent') - // 2. All CallRequests are executed, e.g., all variable loading and local function calls are completed (state 'localWorkDone') - if ( - isReturnTypeVoid(def) && - (stage.stage === "sent" || stage.stage === "localWorkDone") - ) { - resolve(undefined); - } + // TODO: make test + // if ( + // isReturnTypeVoid(def) && + // (stage.stage === "sent" || stage.stage === "localWorkDone") + // ) { + // resolve(undefined); + // } + // }, - if (stage.stage === "sendingError") { - reject( - `Could not send particle for ${def.functionName}: not connected (particle id: ${particle.id})`, - ); - } - - if (stage.stage === "expired") { - reject( - `Particle expired after ttl of ${particle.ttl}ms for function ${def.functionName} (particle id: ${particle.id})`, - ); - } - - if (stage.stage === "interpreterError") { - reject( - `Script interpretation failed for ${def.functionName}: ${stage.errorMessage} (particle id: ${particle.id})`, - ); - } - }); + peer.internals.initiateParticle(particle, resolve, reject); }); }; diff --git a/packages/core/js-client/src/compilerSupport/registerService.ts b/packages/core/js-client/src/compilerSupport/registerService.ts index 8f651e66..ea566425 100644 --- a/packages/core/js-client/src/compilerSupport/registerService.ts +++ b/packages/core/js-client/src/compilerSupport/registerService.ts @@ -14,66 +14,64 @@ * limitations under the License. */ -import type { ServiceDef, ServiceImpl } from "@fluencelabs/interfaces"; - import { FluencePeer } from "../jsPeer/FluencePeer.js"; import { logger } from "../util/logger.js"; import { registerGlobalService, userHandlerService } from "./services.js"; +import { ServiceImpl } from "./types.js"; const log = logger("aqua"); interface RegisterServiceArgs { peer: FluencePeer; - def: ServiceDef; serviceId: string | undefined; service: ServiceImpl; } +const findAllPossibleServiceMethods = (service: ServiceImpl): Set => { + let prototype: Record = service; + const serviceMethods = new Set(); + + do { + Object.getOwnPropertyNames(prototype) + .filter((prop) => { + return typeof prototype[prop] === "function" && prop !== "constructor"; + }) + .forEach((prop) => { + return serviceMethods.add(prop); + }); + + // Satisfying typescript here + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + prototype = Object.getPrototypeOf(prototype) as Record; + } while (prototype.constructor !== Object); + + return serviceMethods; +}; + export const registerService = ({ peer, - def, - serviceId = def.defaultServiceId, + serviceId, service, }: RegisterServiceArgs) => { // TODO: Need to refactor this. We can compute function types from service implementation, making func more type safe - log.trace("registering aqua service %o", { def, serviceId, service }); - - // Checking for missing keys - const requiredKeys = - def.functions.tag === "nil" ? [] : Object.keys(def.functions.fields); - - const incorrectServiceDefinitions = requiredKeys.filter((f) => { - return !(f in service); - }); + log.trace("registering aqua service %o", { serviceId, service }); if (serviceId == null) { throw new Error("Service ID must be specified"); } - if (incorrectServiceDefinitions.length > 0) { - throw new Error( - `Error registering service ${serviceId}: missing functions: ` + - incorrectServiceDefinitions - .map((d) => { - return "'" + d + "'"; - }) - .join(", "), - ); - } + const serviceMethods = findAllPossibleServiceMethods(service); - const singleFunctions = - def.functions.tag === "nil" ? [] : Object.entries(def.functions.fields); - - for (const singleFunction of singleFunctions) { - const [name] = singleFunction; - // The function has type of (arg1, arg2, arg3, ... , callParams) => CallServiceResultType | void + for (const method of serviceMethods) { + // The function has type of (arg1, arg2, arg3, ... , ParticleContext) => CallServiceResultType | void // Account for the fact that user service might be defined as a class - .bind(...) - const userDefinedHandler = service[name].bind(service); + const handler = service[method]; + const userDefinedHandler = handler.bind(service); const serviceDescription = userHandlerService( serviceId, - singleFunction, + method, userDefinedHandler, ); diff --git a/packages/core/js-client/src/compilerSupport/services.ts b/packages/core/js-client/src/compilerSupport/services.ts index dd1b16e4..65dc596c 100644 --- a/packages/core/js-client/src/compilerSupport/services.ts +++ b/packages/core/js-client/src/compilerSupport/services.ts @@ -14,32 +14,18 @@ * limitations under the License. */ -import { SecurityTetraplet } from "@fluencelabs/avm"; -import { - CallParams, - ArrowWithoutCallbacks, - FunctionCallDef, - NonArrowType, - ServiceImpl, - JSONValue, -} from "@fluencelabs/interfaces"; -import { fromUint8Array } from "js-base64"; -import { match } from "ts-pattern"; +import { JSONValue } from "@fluencelabs/interfaces"; import { FluencePeer } from "../jsPeer/FluencePeer.js"; import { CallServiceData, GenericCallServiceHandler, + ParticleContext, ResultCodes, } from "../jsServiceHost/interfaces.js"; import { Particle } from "../particle/Particle.js"; -import { - aquaArgs2Ts, - responseServiceValue2ts, - returnType2Aqua, - ts2aqua, -} from "./conversions.js"; +import { ServiceImpl } from "./types.js"; export interface ServiceDescription { serviceId: string; @@ -50,10 +36,10 @@ export interface ServiceDescription { /** * Creates a service which injects relay's peer id into aqua space */ -export const injectRelayService = (def: FunctionCallDef, peer: FluencePeer) => { +export const injectRelayService = (peer: FluencePeer) => { return { - serviceId: def.names.getDataSrv, - fnName: def.names.relay, + serviceId: "getDataSrv", + fnName: "-relay-", handler: () => { return { retCode: ResultCodes.success, @@ -69,7 +55,6 @@ export const injectRelayService = (def: FunctionCallDef, peer: FluencePeer) => { export const injectValueService = ( serviceId: string, fnName: string, - valueType: NonArrowType, value: JSONValue, ) => { return { @@ -78,7 +63,7 @@ export const injectValueService = ( handler: () => { return { retCode: ResultCodes.success, - result: ts2aqua(value, valueType), + result: value, }; }, }; @@ -87,15 +72,17 @@ export const injectValueService = ( /** * Creates a service which is used to return value from aqua function into typescript space */ -export const responseService = ( - def: FunctionCallDef, - resolveCallback: (val: JSONValue) => void, -) => { +export const responseService = (resolveCallback: (val: JSONValue) => void) => { return { - serviceId: def.names.responseSrv, - fnName: def.names.responseFnName, + serviceId: "callbackSrv", + fnName: "response", handler: (req: CallServiceData) => { - const userFunctionReturn = responseServiceValue2ts(req, def.arrow); + const userFunctionReturn = + req.args.length === 0 + ? null + : req.args.length === 1 + ? req.args[0] + : req.args; setTimeout(() => { resolveCallback(userFunctionReturn); @@ -113,12 +100,11 @@ export const responseService = ( * Creates a service which is used to return errors from aqua function into typescript space */ export const errorHandlingService = ( - def: FunctionCallDef, rejectCallback: (err: JSONValue) => void, ) => { return { - serviceId: def.names.errorHandlingSrv, - fnName: def.names.errorFnName, + serviceId: "errorHandlingSrv", + fnName: "error", handler: (req: CallServiceData) => { const [err] = req.args; @@ -139,21 +125,21 @@ export const errorHandlingService = ( */ export const userHandlerService = ( serviceId: string, - arrowType: [string, ArrowWithoutCallbacks], + fnName: string, userHandler: ServiceImpl[string], ) => { - const [fnName, type] = arrowType; return { serviceId, fnName, handler: async (req: CallServiceData) => { - const args: [...JSONValue[], CallParams] = [ - ...aquaArgs2Ts(req, type), - extractCallParams(req, type), + const args: [...JSONValue[], ParticleContext] = [ + ...req.args, + req.particleContext, ]; - const rawResult = await userHandler.bind(null)(...args); - const result = returnType2Aqua(rawResult, type); + const result = await userHandler.bind(null)(...args); + + console.log(result, "userHandlerService result", serviceId, fnName); return { retCode: ResultCodes.success, @@ -163,46 +149,6 @@ export const userHandlerService = ( }; }; -/** - * Extracts call params from from call service data according to aqua type definition - */ -const extractCallParams = ( - req: CallServiceData, - arrow: ArrowWithoutCallbacks, -): CallParams => { - const names: (string | undefined)[] = match(arrow.domain) - .with({ tag: "nil" }, () => { - return []; - }) - .with({ tag: "unlabeledProduct" }, (x) => { - return x.items.map((_, index) => { - return "arg" + index; - }); - }) - .with({ tag: "labeledProduct" }, (x) => { - return Object.keys(x.fields); - }) - .exhaustive(); - - const tetraplets: Record = {}; - - for (let i = 0; i < req.args.length; i++) { - const name = names[i]; - - if (name != null) { - tetraplets[name] = req.tetraplets[i]; - } - } - - const callParams = { - ...req.particleContext, - signature: fromUint8Array(req.particleContext.signature), - tetraplets, - }; - - return callParams; -}; - export const registerParticleScopeService = ( peer: FluencePeer, particle: Particle, diff --git a/packages/core/js-client/src/compilerSupport/types.ts b/packages/core/js-client/src/compilerSupport/types.ts index bcac8e79..fd0361d5 100644 --- a/packages/core/js-client/src/compilerSupport/types.ts +++ b/packages/core/js-client/src/compilerSupport/types.ts @@ -22,7 +22,5 @@ export type MaybePromise = T | Promise; export type ServiceImpl = Record< string, - ( - ...args: [...JSONArray, ParticleContext] - ) => MaybePromise + (...args: [...JSONArray, ParticleContext]) => MaybePromise >; diff --git a/packages/core/js-client/src/jsPeer/FluencePeer.ts b/packages/core/js-client/src/jsPeer/FluencePeer.ts index 13886cdf..67a195ac 100644 --- a/packages/core/js-client/src/jsPeer/FluencePeer.ts +++ b/packages/core/js-client/src/jsPeer/FluencePeer.ts @@ -22,6 +22,7 @@ import { KeyPairFormat, serializeAvmArgs, } from "@fluencelabs/avm"; +import { JSONValue } from "@fluencelabs/interfaces"; import { fromUint8Array } from "js-base64"; import { concatMap, @@ -55,7 +56,6 @@ import { getActualTTL, hasExpired, Particle, - ParticleExecutionStage, ParticleQueueItem, } from "../particle/Particle.js"; import { registerSig } from "../services/_aqua/services.js"; @@ -67,6 +67,8 @@ import { Tracing } from "../services/Tracing.js"; import { logger } from "../util/logger.js"; import { jsonify, isString, getErrorMessage } from "../util/utils.js"; +import { ExpirationError, InterpreterError, SendError } from "./errors.js"; + const log_particle = logger("particle"); const log_peer = logger("peer"); @@ -247,11 +249,13 @@ export abstract class FluencePeer { /** * Initiates a new particle execution starting from local peer * @param particle - particle to start execution of - * @param onStageChange - callback for reacting on particle state changes + * @param onSuccess - callback which is called when particle execution succeed + * @param onError - callback which is called when particle execution fails */ initiateParticle: ( particle: IParticle, - onStageChange: (stage: ParticleExecutionStage) => void, + onSuccess: (result: JSONValue) => void, + onError: (error: Error) => void, ): void => { if (!this.isInitialized) { throw new Error( @@ -268,7 +272,8 @@ export abstract class FluencePeer { this._incomingParticles.next({ particle: particle, callResults: [], - onStageChange: onStageChange, + onSuccess, + onError, }); }, @@ -336,7 +341,8 @@ export abstract class FluencePeer { this._incomingParticles.next({ particle: p, callResults: [], - onStageChange: () => {}, + onSuccess: () => {}, + onError: () => {}, }); }, }, @@ -471,10 +477,11 @@ export abstract class FluencePeer { item.result.message, ); - item.onStageChange({ - stage: "interpreterError", - errorMessage: item.result.message, - }); + item.onError( + new InterpreterError( + `Script interpretation failed: ${item.result.message} (particle id: ${item.particle.id})`, + ), + ); return; } @@ -493,10 +500,11 @@ export abstract class FluencePeer { this.decodeAvmData(item.result.data), ); - item.onStageChange({ - stage: "interpreterError", - errorMessage: item.result.errorMessage, - }); + item.onError( + new InterpreterError( + `Script interpretation failed: ${item.result.errorMessage} (particle id: ${item.particle.id})`, + ), + ); return; } @@ -508,10 +516,6 @@ export abstract class FluencePeer { this.decodeAvmData(item.result.data), ); - setTimeout(() => { - item.onStageChange({ stage: "interpreted" }); - }, 0); - let connectionPromise: Promise = Promise.resolve(); // send particle further if requested @@ -527,6 +531,8 @@ export abstract class FluencePeer { item.result.nextPeerPks.toString(), ); + const interpreterResult = item.result; + connectionPromise = this.connection .sendParticle(item.result.nextPeerPks, newParticle) .then(() => { @@ -535,7 +541,10 @@ export abstract class FluencePeer { newParticle.id, ); - item.onStageChange({ stage: "sent" }); + if (interpreterResult.callRequests.length === 0) { + // Nothing to call, just fire-and-forget behavior + item.onSuccess(null); + } }) .catch((e: unknown) => { log_particle.error( @@ -544,10 +553,13 @@ export abstract class FluencePeer { e, ); - item.onStageChange({ - stage: "sendingError", - errorMessage: getErrorMessage(e), - }); + const message = getErrorMessage(e); + + item.onError( + new SendError( + `Could not send particle: (particle id: ${item.particle.id}, message: ${message})`, + ), + ); }); } @@ -560,7 +572,10 @@ export abstract class FluencePeer { args: cr.arguments, serviceId: cr.serviceId, tetraplets: cr.tetraplets, - particleContext: getParticleContext(item.particle), + particleContext: getParticleContext( + item.particle, + cr.tetraplets, + ), }; void this._execSingleCallRequest(req) @@ -582,6 +597,14 @@ export abstract class FluencePeer { }; }) .then((res) => { + if ( + req.serviceId === "callbackSrv" && + req.fnName === "response" + ) { + // Particle already processed + return; + } + const serviceResult = { result: jsonify(res.result), retCode: res.retCode, @@ -600,7 +623,8 @@ export abstract class FluencePeer { }); } } else { - item.onStageChange({ stage: "localWorkDone" }); + // Every air instruction executed or particle will go to relay + item.onSuccess(null); } return connectionPromise; @@ -613,6 +637,7 @@ export abstract class FluencePeer { } private _expireParticle(item: ParticleQueueItem) { + console.log(item); const particleId = item.particle.id; log_particle.debug( @@ -623,7 +648,11 @@ export abstract class FluencePeer { this.jsServiceHost.removeParticleScopeHandlers(particleId); - item.onStageChange({ stage: "expired" }); + item.onError( + new ExpirationError( + `Particle expired after ttl of ${item.particle.ttl}ms (particle id: ${item.particle.id})`, + ), + ); } private decodeAvmData(data: Uint8Array) { diff --git a/packages/core/js-client/src/jsPeer/__test__/avm.spec.ts b/packages/core/js-client/src/jsPeer/__test__/avm.spec.ts index 565749d8..189de1c3 100644 --- a/packages/core/js-client/src/jsPeer/__test__/avm.spec.ts +++ b/packages/core/js-client/src/jsPeer/__test__/avm.spec.ts @@ -44,7 +44,11 @@ describe("Basic AVM functionality in Fluence Peer tests", () => { }, }); - peer.internals.initiateParticle(particle, handleTimeout(reject)); + peer.internals.initiateParticle( + particle, + () => {}, + handleTimeout(reject), + ); }); expect(res).toBe("1"); @@ -85,7 +89,11 @@ describe("Basic AVM functionality in Fluence Peer tests", () => { }, }); - peer.internals.initiateParticle(particle, handleTimeout(reject)); + peer.internals.initiateParticle( + particle, + () => {}, + handleTimeout(reject), + ); }); expect(res).toStrictEqual(["1", "2"]); @@ -126,7 +134,11 @@ describe("Basic AVM functionality in Fluence Peer tests", () => { }, }); - peer.internals.initiateParticle(particle, handleTimeout(reject)); + peer.internals.initiateParticle( + particle, + () => {}, + handleTimeout(reject), + ); }); expect(res).toBe("fast_result"); @@ -178,7 +190,11 @@ describe("Basic AVM functionality in Fluence Peer tests", () => { }, }); - peer.internals.initiateParticle(particle, handleTimeout(reject)); + peer.internals.initiateParticle( + particle, + () => {}, + handleTimeout(reject), + ); }); expect(res).toBe("failed_with_timeout"); diff --git a/packages/core/js-client/src/jsPeer/__test__/par.spec.ts b/packages/core/js-client/src/jsPeer/__test__/par.spec.ts index 268c37a5..c02b07c7 100644 --- a/packages/core/js-client/src/jsPeer/__test__/par.spec.ts +++ b/packages/core/js-client/src/jsPeer/__test__/par.spec.ts @@ -94,7 +94,11 @@ describe("FluencePeer flow tests", () => { }, }); - peer.internals.initiateParticle(particle, handleTimeout(reject)); + peer.internals.initiateParticle( + particle, + () => {}, + handleTimeout(reject), + ); }); expect(res).toEqual(expect.arrayContaining(["test1", "test1"])); diff --git a/packages/core/js-client/src/jsPeer/__test__/peer.spec.ts b/packages/core/js-client/src/jsPeer/__test__/peer.spec.ts index f71db1be..93e27b44 100644 --- a/packages/core/js-client/src/jsPeer/__test__/peer.spec.ts +++ b/packages/core/js-client/src/jsPeer/__test__/peer.spec.ts @@ -72,7 +72,11 @@ describe("FluencePeer usage test suite", () => { }, }); - peer.internals.initiateParticle(particle, handleTimeout(reject)); + peer.internals.initiateParticle( + particle, + () => {}, + handleTimeout(reject), + ); }); expect(res).toBe("test"); @@ -130,7 +134,11 @@ describe("FluencePeer usage test suite", () => { }, }); - peer.internals.initiateParticle(particle, handleTimeout(reject)); + peer.internals.initiateParticle( + particle, + () => {}, + handleTimeout(reject), + ); }); expect(res).toBe(null); @@ -167,7 +175,11 @@ describe("FluencePeer usage test suite", () => { }, }); - peer.internals.initiateParticle(particle, handleTimeout(reject)); + peer.internals.initiateParticle( + particle, + () => {}, + handleTimeout(reject), + ); }); await expect(promise).rejects.toMatchObject({ @@ -205,6 +217,6 @@ async function callIncorrectService(peer: FluencePeer) { }, }); - peer.internals.initiateParticle(particle, handleTimeout(reject)); + peer.internals.initiateParticle(particle, () => {}, handleTimeout(reject)); }); } diff --git a/packages/core/js-client/src/jsPeer/errors.ts b/packages/core/js-client/src/jsPeer/errors.ts new file mode 100644 index 00000000..f49d86e3 --- /dev/null +++ b/packages/core/js-client/src/jsPeer/errors.ts @@ -0,0 +1,21 @@ +/** + * Copyright 2023 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class ExpirationError extends Error {} + +export class InterpreterError extends Error {} + +export class SendError extends Error {} diff --git a/packages/core/js-client/src/jsServiceHost/interfaces.ts b/packages/core/js-client/src/jsServiceHost/interfaces.ts index be6c3818..02f08898 100644 --- a/packages/core/js-client/src/jsServiceHost/interfaces.ts +++ b/packages/core/js-client/src/jsServiceHost/interfaces.ts @@ -79,7 +79,7 @@ export enum ResultCodes { /** * Particle context. Contains additional information about particle which triggered `call` air instruction from AVM */ -export interface ParticleContext { +export type ParticleContext = { /** * The identifier of particle which triggered the call */ @@ -104,7 +104,12 @@ export interface ParticleContext { * Particle's signature */ signature: Uint8Array; -} + + /** + * Security Tetraplets received from AVM and copied here + */ + tetraplets: SecurityTetraplet[][]; +}; /** * Represents the information passed from AVM when a `call` air instruction is executed on the local peer diff --git a/packages/core/js-client/src/jsServiceHost/serviceUtils.ts b/packages/core/js-client/src/jsServiceHost/serviceUtils.ts index ac9090c3..42e9b8ef 100644 --- a/packages/core/js-client/src/jsServiceHost/serviceUtils.ts +++ b/packages/core/js-client/src/jsServiceHost/serviceUtils.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { SecurityTetraplet } from "@fluencelabs/avm"; import { JSONArray } from "@fluencelabs/interfaces"; import { FluencePeer } from "../jsPeer/FluencePeer.js"; @@ -28,10 +29,6 @@ import { ResultCodes, } from "./interfaces.js"; -export const doNothing = () => { - return undefined; -}; - export const WrapFnIntoServiceCall = ( fn: (args: JSONArray) => CallServiceResultType | undefined, ) => { @@ -51,13 +48,17 @@ export class ServiceError extends Error { } } -export const getParticleContext = (particle: IParticle): ParticleContext => { +export const getParticleContext = ( + particle: IParticle, + tetraplets: SecurityTetraplet[][], +): ParticleContext => { return { particleId: particle.id, initPeerId: particle.initPeerId, timestamp: particle.timestamp, ttl: particle.ttl, signature: particle.signature, + tetraplets, }; }; diff --git a/packages/core/js-client/src/particle/Particle.ts b/packages/core/js-client/src/particle/Particle.ts index 15a5127b..e4e26fbf 100644 --- a/packages/core/js-client/src/particle/Particle.ts +++ b/packages/core/js-client/src/particle/Particle.ts @@ -24,6 +24,8 @@ import { KeyPair } from "../keypair/index.js"; import { numberToLittleEndianBytes } from "../util/bytes.js"; import { IParticle } from "./interfaces.js"; +import { JSONValue } from "@fluencelabs/interfaces"; +import { ExpirationError } from "../jsPeer/errors.js"; const particleSchema = z.object({ id: z.string(), @@ -183,15 +185,16 @@ export type ParticleExecutionStage = export interface ParticleQueueItem { particle: IParticle; callResults: CallResultsArray; - onStageChange: (state: ParticleExecutionStage) => void; + onSuccess: (result: JSONValue) => void; + onError: (error: Error) => void; } /** * Helper function to handle particle at expired stage */ export const handleTimeout = (fn: () => void) => { - return (stage: ParticleExecutionStage) => { - if (stage.stage === "expired") { + return (error: Error) => { + if (error instanceof ExpirationError) { fn(); } }; diff --git a/packages/core/js-client/src/services/NodeUtils.ts b/packages/core/js-client/src/services/NodeUtils.ts index 4ebba7b4..1e017876 100644 --- a/packages/core/js-client/src/services/NodeUtils.ts +++ b/packages/core/js-client/src/services/NodeUtils.ts @@ -14,58 +14,45 @@ * limitations under the License. */ -import { Buffer } from "buffer"; -import * as fs from "fs"; - -import { CallParams } from "@fluencelabs/interfaces"; +import { readFile } from "fs/promises"; import { FluencePeer } from "../jsPeer/FluencePeer.js"; +import { ParticleContext } from "../jsServiceHost/interfaces.js"; import { getErrorMessage } from "../util/utils.js"; -import { NodeUtilsDef, registerNodeUtils } from "./_aqua/node-utils.js"; +import { registerNodeUtils } from "./_aqua/node-utils.js"; import { SecurityGuard } from "./securityGuard.js"; import { defaultGuard } from "./SingleModuleSrv.js"; -export class NodeUtils implements NodeUtilsDef { +export class NodeUtils { constructor(private peer: FluencePeer) { this.securityGuard_readFile = defaultGuard(this.peer); } - securityGuard_readFile: SecurityGuard<"path">; + securityGuard_readFile: SecurityGuard; - async read_file(path: string, callParams: CallParams<"path">) { + async read_file(path: string, callParams: ParticleContext) { if (!this.securityGuard_readFile(callParams)) { return { success: false, - error: "Security guard validation failed", + error: ["Security guard validation failed"], content: null, }; } try { // Strange enough, but Buffer type works here, while reading with encoding 'utf-8' doesn't - const data = await new Promise((resolve, reject) => { - fs.readFile(path, (err, data) => { - if (err != null) { - reject(err); - return; - } - - resolve(data); - }); - }); + const data = await readFile(path, "base64"); return { success: true, - // TODO: this is strange bug. - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - content: data as unknown as string, + content: [data], error: null, }; } catch (err: unknown) { return { success: false, - error: getErrorMessage(err), + error: [getErrorMessage(err)], content: null, }; } diff --git a/packages/core/js-client/src/services/Sig.ts b/packages/core/js-client/src/services/Sig.ts index 5663e572..e021e71b 100644 --- a/packages/core/js-client/src/services/Sig.ts +++ b/packages/core/js-client/src/services/Sig.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { CallParams, PeerIdB58 } from "@fluencelabs/interfaces"; +import { PeerIdB58 } from "@fluencelabs/interfaces"; +import { ParticleContext } from "../jsServiceHost/interfaces.js"; import { KeyPair } from "../keypair/index.js"; -import { SigDef } from "./_aqua/services.js"; import { allowOnlyParticleOriginatedAt, allowServiceFn, @@ -28,7 +28,7 @@ import { } from "./securityGuard.js"; export const defaultSigGuard = (peerId: PeerIdB58) => { - return and<"data">( + return and( allowOnlyParticleOriginatedAt(peerId), or( allowServiceFn("trust-graph", "get_trust_bytes"), @@ -43,23 +43,23 @@ export const defaultSigGuard = (peerId: PeerIdB58) => { type SignReturnType = | { - error: null; - signature: number[]; + error: []; + signature: [number[]]; success: true; } | { - error: string; - signature: null; + error: [string]; + signature: []; success: false; }; -export class Sig implements SigDef { +export class Sig { constructor(private keyPair: KeyPair) {} /** * Configurable security guard for sign method */ - securityGuard: SecurityGuard<"data"> = () => { + securityGuard: SecurityGuard = () => { return true; }; @@ -75,13 +75,13 @@ export class Sig implements SigDef { */ async sign( data: number[], - callParams: CallParams<"data">, + context: ParticleContext, ): Promise { - if (!this.securityGuard(callParams)) { + if (!this.securityGuard(context)) { return { success: false, - error: "Security guard validation failed", - signature: null, + error: ["Security guard validation failed"], + signature: [], }; } @@ -89,8 +89,8 @@ export class Sig implements SigDef { return { success: true, - error: null, - signature: Array.from(signedData), + error: [], + signature: [Array.from(signedData)], }; } diff --git a/packages/core/js-client/src/services/SingleModuleSrv.ts b/packages/core/js-client/src/services/SingleModuleSrv.ts index 4c29e875..e75e2cda 100644 --- a/packages/core/js-client/src/services/SingleModuleSrv.ts +++ b/packages/core/js-client/src/services/SingleModuleSrv.ts @@ -16,13 +16,12 @@ import { Buffer } from "buffer"; -import { CallParams } from "@fluencelabs/interfaces"; import { v4 as uuidv4 } from "uuid"; import { FluencePeer } from "../jsPeer/FluencePeer.js"; +import { ParticleContext } from "../jsServiceHost/interfaces.js"; import { getErrorMessage } from "../util/utils.js"; -import { SrvDef } from "./_aqua/single-module-srv.js"; import { allowOnlyParticleOriginatedAt, SecurityGuard, @@ -32,7 +31,7 @@ export const defaultGuard = (peer: FluencePeer) => { return allowOnlyParticleOriginatedAt(peer.keyPair.getPeerId()); }; -export class Srv implements SrvDef { +export class Srv { private services: Set = new Set(); constructor(private peer: FluencePeer) { @@ -40,16 +39,13 @@ export class Srv implements SrvDef { this.securityGuard_remove = defaultGuard(this.peer); } - securityGuard_create: SecurityGuard<"wasm_b64_content">; + securityGuard_create: SecurityGuard; - async create( - wasm_b64_content: string, - callParams: CallParams<"wasm_b64_content">, - ) { + async create(wasm_b64_content: string, callParams: ParticleContext) { if (!this.securityGuard_create(callParams)) { return { success: false, - error: "Security guard validation failed", + error: ["Security guard validation failed"], service_id: null, }; } @@ -66,25 +62,25 @@ export class Srv implements SrvDef { return { success: true, - service_id: newServiceId, + service_id: [newServiceId], error: null, }; } catch (err: unknown) { return { success: true, service_id: null, - error: getErrorMessage(err), + error: [getErrorMessage(err)], }; } } - securityGuard_remove: SecurityGuard<"service_id">; + securityGuard_remove: SecurityGuard; - async remove(service_id: string, callParams: CallParams<"service_id">) { + async remove(service_id: string, callParams: ParticleContext) { if (!this.securityGuard_remove(callParams)) { return { success: false, - error: "Security guard validation failed", + error: ["Security guard validation failed"], service_id: null, }; } @@ -92,7 +88,7 @@ export class Srv implements SrvDef { if (!this.services.has(service_id)) { return { success: false, - error: `Service with id ${service_id} not found`, + error: [`Service with id ${service_id} not found`], }; } diff --git a/packages/core/js-client/src/services/__test__/builtInHandler.spec.ts b/packages/core/js-client/src/services/__test__/builtInHandler.spec.ts index 3d538096..39b1ba41 100644 --- a/packages/core/js-client/src/services/__test__/builtInHandler.spec.ts +++ b/packages/core/js-client/src/services/__test__/builtInHandler.spec.ts @@ -16,11 +16,14 @@ import assert from "assert"; -import { CallParams, JSONArray } from "@fluencelabs/interfaces"; +import { JSONArray } from "@fluencelabs/interfaces"; import { toUint8Array } from "js-base64"; import { it, describe, expect, test } from "vitest"; -import { CallServiceData } from "../../jsServiceHost/interfaces.js"; +import { + CallServiceData, + ParticleContext, +} from "../../jsServiceHost/interfaces.js"; import { KeyPair } from "../../keypair/index.js"; import { builtInServices } from "../builtins.js"; import { allowServiceFn } from "../securityGuard.js"; @@ -149,6 +152,7 @@ describe("Tests for default handler", () => { timestamp: 595951200, ttl: 595961200, signature: new Uint8Array([]), + tetraplets: [], }, }; @@ -185,6 +189,7 @@ describe("Tests for default handler", () => { timestamp: 595951200, ttl: 595961200, signature: new Uint8Array([]), + tetraplets: [], }, }; @@ -243,14 +248,15 @@ const makeTestTetraplet = ( initPeerId: string, serviceId: string, fnName: string, -): CallParams<"data"> => { +): ParticleContext => { return { particleId: "", timestamp: 0, ttl: 0, initPeerId: initPeerId, - tetraplets: { - data: [ + signature: new Uint8Array([]), + tetraplets: [ + [ { peer_pk: initPeerId, function_name: fnName, @@ -258,7 +264,7 @@ const makeTestTetraplet = ( json_path: "", }, ], - }, + ], }; }; @@ -273,7 +279,7 @@ describe("Sig service tests", () => { ); expect(res.success).toBe(true); - expect(res.signature).toStrictEqual(testDataSig); + expect(res.signature).toStrictEqual([testDataSig]); }); it("sig.verify should return true for the correct signature", async () => { @@ -305,7 +311,7 @@ describe("Sig service tests", () => { expect(signature.success).toBe(true); assert(signature.success); - const res = await sig.verify(signature.signature, testData); + const res = await sig.verify(signature.signature[0], testData); expect(res).toBe(true); }); @@ -334,7 +340,7 @@ describe("Sig service tests", () => { ); expect(res.success).toBe(false); - expect(res.error).toBe("Security guard validation failed"); + expect(res.error).toStrictEqual(["Security guard validation failed"]); }); it("sig.sign with defaultSigGuard should not allow particles initiated from other peers", async () => { @@ -352,7 +358,7 @@ describe("Sig service tests", () => { ); expect(res.success).toBe(false); - expect(res.error).toBe("Security guard validation failed"); + expect(res.error).toStrictEqual(["Security guard validation failed"]); }); it("changing securityGuard should work", async () => { diff --git a/packages/core/js-client/src/services/__test__/jsonBuiltin.spec.ts b/packages/core/js-client/src/services/__test__/jsonBuiltin.spec.ts index 430ee609..061d5f2a 100644 --- a/packages/core/js-client/src/services/__test__/jsonBuiltin.spec.ts +++ b/packages/core/js-client/src/services/__test__/jsonBuiltin.spec.ts @@ -17,7 +17,6 @@ import { it, describe, expect, beforeEach, afterEach } from "vitest"; import { FluencePeer } from "../../jsPeer/FluencePeer.js"; -import { doNothing } from "../../jsServiceHost/serviceUtils.js"; import { mkTestPeer } from "../../util/testUtils.js"; let peer: FluencePeer; @@ -72,7 +71,11 @@ describe("Sig service test suite", () => { }); const p = await peer.internals.createNewParticle(script); - peer.internals.initiateParticle(p, doNothing); + peer.internals.initiateParticle( + p, + () => {}, + () => {}, + ); const [ nestedFirst, diff --git a/packages/core/js-client/src/services/__test__/sigService.spec.ts b/packages/core/js-client/src/services/__test__/sigService.spec.ts index c46ffffa..cbb8c7b1 100644 --- a/packages/core/js-client/src/services/__test__/sigService.spec.ts +++ b/packages/core/js-client/src/services/__test__/sigService.spec.ts @@ -17,10 +17,10 @@ import * as path from "path"; import * as url from "url"; -import { ServiceDef, ServiceImpl } from "@fluencelabs/interfaces"; import { it, describe, expect, beforeAll } from "vitest"; import { registerService } from "../../compilerSupport/registerService.js"; +import { ServiceImpl } from "../../compilerSupport/types.js"; import { KeyPair } from "../../keypair/index.js"; import { compileAqua, CompiledFnCall, withPeer } from "../../util/testUtils.js"; import { allowServiceFn } from "../securityGuard.js"; @@ -29,8 +29,6 @@ import { Sig } from "../Sig.js"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); let aqua: Record; -let sigDef: ServiceDef; -let dataProviderDef: ServiceDef; describe("Sig service test suite", () => { beforeAll(async () => { @@ -39,14 +37,12 @@ describe("Sig service test suite", () => { "../../../aqua_test/sigService.aqua", ); - const { services, functions } = await compileAqua(pathToAquaFiles); + const { functions } = await compileAqua(pathToAquaFiles); aqua = functions; - sigDef = services["Sig"]; - dataProviderDef = services["DataProvider"]; }); - it("Use custom sig service, success path", async () => { + it.only("Use custom sig service, success path", async () => { await withPeer(async (peer) => { const customKeyPair = await KeyPair.randomEd25519(); const customSig = new Sig(customKeyPair); @@ -54,7 +50,6 @@ describe("Sig service test suite", () => { registerService({ peer, - def: sigDef, serviceId: "CustomSig", // TODO: fix this after changing registerService signature // eslint-disable-next-line @typescript-eslint/consistent-type-assertions @@ -63,7 +58,6 @@ describe("Sig service test suite", () => { registerService({ peer, - def: dataProviderDef, serviceId: "data", service: { provide_data: () => { @@ -81,7 +75,7 @@ describe("Sig service test suite", () => { const isSigCorrect = await customSig.verify( // TODO: Use compiled ts wrappers // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - (result as { signature: number[] }).signature, + (result as { signature: [number[]] }).signature[0], data, ); @@ -97,7 +91,6 @@ describe("Sig service test suite", () => { registerService({ peer, - def: sigDef, serviceId: "CustomSig", // TODO: fix this after changing registerService signature // eslint-disable-next-line @typescript-eslint/consistent-type-assertions @@ -106,7 +99,6 @@ describe("Sig service test suite", () => { registerService({ peer, - def: dataProviderDef, serviceId: "data", service: { provide_data: () => { @@ -122,7 +114,7 @@ describe("Sig service test suite", () => { }); }); - it("Default sig service should be resolvable by peer id", async () => { + it.only("Default sig service should be resolvable by peer id", async () => { await withPeer(async (peer) => { const sig = peer.internals.getServices().sig; @@ -130,7 +122,6 @@ describe("Sig service test suite", () => { registerService({ peer: peer, - def: dataProviderDef, serviceId: "data", service: { provide_data: () => { @@ -146,6 +137,11 @@ describe("Sig service test suite", () => { }); expect(callAsSigRes).toHaveProperty("success", false); + + expect(callAsPeerIdRes).toHaveProperty("error", [ + "Security guard validation failed", + ]); + expect(callAsPeerIdRes).toHaveProperty("success", false); sig.securityGuard = () => { @@ -167,7 +163,8 @@ describe("Sig service test suite", () => { const isValid = await sig.verify( // TODO: Use compiled ts wrappers // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - (callAsSigResAfterGuardChange as { signature: number[] }).signature, + (callAsSigResAfterGuardChange as { signature: [number[]] }) + .signature[0], data, ); diff --git a/packages/core/js-client/src/services/_aqua/node-utils.ts b/packages/core/js-client/src/services/_aqua/node-utils.ts index c9debf71..e5b11d47 100644 --- a/packages/core/js-client/src/services/_aqua/node-utils.ts +++ b/packages/core/js-client/src/services/_aqua/node-utils.ts @@ -14,30 +14,11 @@ * limitations under the License. */ -/** - * This compiled aqua file was modified to make it work in monorepo - */ -import { CallParams, ServiceImpl } from "@fluencelabs/interfaces"; - import { registerService } from "../../compilerSupport/registerService.js"; +import { ServiceImpl } from "../../compilerSupport/types.js"; import { FluencePeer } from "../../jsPeer/FluencePeer.js"; import { NodeUtils } from "../NodeUtils.js"; -// Services - -export interface NodeUtilsDef { - read_file: ( - path: string, - callParams: CallParams<"path">, - ) => - | { content: string | null; error: string | null; success: boolean } - | Promise<{ - content: string | null; - error: string | null; - success: boolean; - }>; -} - export function registerNodeUtils( peer: FluencePeer, serviceId: string, @@ -49,55 +30,6 @@ export function registerNodeUtils( // eslint-disable-next-line @typescript-eslint/consistent-type-assertions service: service as unknown as ServiceImpl, serviceId, - def: { - defaultServiceId: "node_utils", - functions: { - tag: "labeledProduct", - fields: { - read_file: { - tag: "arrow", - domain: { - tag: "labeledProduct", - fields: { - path: { - tag: "scalar", - name: "string", - }, - }, - }, - codomain: { - tag: "unlabeledProduct", - items: [ - { - tag: "struct", - name: "ReadFileResult", - fields: { - content: { - tag: "option", - type: { - tag: "scalar", - name: "string", - }, - }, - error: { - tag: "option", - type: { - tag: "scalar", - name: "string", - }, - }, - success: { - tag: "scalar", - name: "bool", - }, - }, - }, - ], - }, - }, - }, - }, - }, }); } diff --git a/packages/core/js-client/src/services/_aqua/services.ts b/packages/core/js-client/src/services/_aqua/services.ts index 896ad0a8..561fb78d 100644 --- a/packages/core/js-client/src/services/_aqua/services.ts +++ b/packages/core/js-client/src/services/_aqua/services.ts @@ -14,33 +14,30 @@ * limitations under the License. */ -/** - * This compiled aqua file was modified to make it work in monorepo - */ -import { CallParams, ServiceImpl } from "@fluencelabs/interfaces"; - import { registerService } from "../../compilerSupport/registerService.js"; +import { ServiceImpl } from "../../compilerSupport/types.js"; import { FluencePeer } from "../../jsPeer/FluencePeer.js"; +import { ParticleContext } from "../../jsServiceHost/interfaces.js"; import { Sig } from "../Sig.js"; // Services export interface SigDef { - get_peer_id: (callParams: CallParams) => string | Promise; + get_peer_id: (callParams: ParticleContext) => string | Promise; sign: ( data: number[], - callParams: CallParams<"data">, + callParams: ParticleContext, ) => - | { error: string | null; signature: number[] | null; success: boolean } + | { error: [string?]; signature: [number[]?]; success: boolean } | Promise<{ - error: string | null; - signature: number[] | null; + error: [string?]; + signature: [number[]?]; success: boolean; }>; verify: ( signature: number[], data: number[], - callParams: CallParams<"signature" | "data">, + callParams: ParticleContext, ) => boolean | Promise; } @@ -55,107 +52,6 @@ export function registerSig( // eslint-disable-next-line @typescript-eslint/consistent-type-assertions service: service as unknown as ServiceImpl, serviceId, - def: { - defaultServiceId: "sig", - functions: { - tag: "labeledProduct", - fields: { - get_peer_id: { - tag: "arrow", - domain: { - tag: "nil", - }, - codomain: { - tag: "unlabeledProduct", - items: [ - { - tag: "scalar", - name: "string", - }, - ], - }, - }, - sign: { - tag: "arrow", - domain: { - tag: "labeledProduct", - fields: { - data: { - tag: "array", - type: { - tag: "scalar", - name: "u8", - }, - }, - }, - }, - codomain: { - tag: "unlabeledProduct", - items: [ - { - tag: "struct", - name: "SignResult", - fields: { - error: { - tag: "option", - type: { - tag: "scalar", - name: "string", - }, - }, - signature: { - tag: "option", - type: { - tag: "array", - type: { - tag: "scalar", - name: "u8", - }, - }, - }, - success: { - tag: "scalar", - name: "bool", - }, - }, - }, - ], - }, - }, - verify: { - tag: "arrow", - domain: { - tag: "labeledProduct", - fields: { - signature: { - tag: "array", - type: { - tag: "scalar", - name: "u8", - }, - }, - data: { - tag: "array", - type: { - tag: "scalar", - name: "u8", - }, - }, - }, - }, - codomain: { - tag: "unlabeledProduct", - items: [ - { - tag: "scalar", - name: "bool", - }, - ], - }, - }, - }, - }, - }, }); } diff --git a/packages/core/js-client/src/services/_aqua/single-module-srv.ts b/packages/core/js-client/src/services/_aqua/single-module-srv.ts index b82665ba..1dc90dc6 100644 --- a/packages/core/js-client/src/services/_aqua/single-module-srv.ts +++ b/packages/core/js-client/src/services/_aqua/single-module-srv.ts @@ -14,37 +14,11 @@ * limitations under the License. */ -/** - * This compiled aqua file was modified to make it work in monorepo - */ -import { CallParams, ServiceImpl } from "@fluencelabs/interfaces"; - import { registerService } from "../../compilerSupport/registerService.js"; +import { ServiceImpl } from "../../compilerSupport/types.js"; import { FluencePeer } from "../../jsPeer/FluencePeer.js"; import { Srv } from "../SingleModuleSrv.js"; -// Services - -export interface SrvDef { - create: ( - wasm_b64_content: string, - callParams: CallParams<"wasm_b64_content">, - ) => - | { error: string | null; service_id: string | null; success: boolean } - | Promise<{ - error: string | null; - service_id: string | null; - success: boolean; - }>; - list: (callParams: CallParams) => string[] | Promise; - remove: ( - service_id: string, - callParams: CallParams<"service_id">, - ) => - | { error: string | null; success: boolean } - | Promise<{ error: string | null; success: boolean }>; -} - export function registerSrv( peer: FluencePeer, serviceId: string, @@ -56,107 +30,6 @@ export function registerSrv( // TODO: fix this after changing registerService signature // eslint-disable-next-line @typescript-eslint/consistent-type-assertions service: service as unknown as ServiceImpl, - def: { - defaultServiceId: "single_module_srv", - functions: { - tag: "labeledProduct", - fields: { - create: { - tag: "arrow", - domain: { - tag: "labeledProduct", - fields: { - wasm_b64_content: { - tag: "scalar", - name: "string", - }, - }, - }, - codomain: { - tag: "unlabeledProduct", - items: [ - { - tag: "struct", - name: "ServiceCreationResult", - fields: { - error: { - tag: "option", - type: { - tag: "scalar", - name: "string", - }, - }, - service_id: { - tag: "option", - type: { - tag: "scalar", - name: "string", - }, - }, - success: { - tag: "scalar", - name: "bool", - }, - }, - }, - ], - }, - }, - list: { - tag: "arrow", - domain: { - tag: "nil", - }, - codomain: { - tag: "unlabeledProduct", - items: [ - { - tag: "array", - type: { - tag: "scalar", - name: "string", - }, - }, - ], - }, - }, - remove: { - tag: "arrow", - domain: { - tag: "labeledProduct", - fields: { - service_id: { - tag: "scalar", - name: "string", - }, - }, - }, - codomain: { - tag: "unlabeledProduct", - items: [ - { - tag: "struct", - name: "RemoveResult", - fields: { - error: { - tag: "option", - type: { - tag: "scalar", - name: "string", - }, - }, - success: { - tag: "scalar", - name: "bool", - }, - }, - }, - ], - }, - }, - }, - }, - }, }); } diff --git a/packages/core/js-client/src/services/securityGuard.ts b/packages/core/js-client/src/services/securityGuard.ts index 02462cde..1417e52b 100644 --- a/packages/core/js-client/src/services/securityGuard.ts +++ b/packages/core/js-client/src/services/securityGuard.ts @@ -15,25 +15,23 @@ */ import { SecurityTetraplet } from "@fluencelabs/avm"; -import { CallParams, PeerIdB58 } from "@fluencelabs/interfaces"; +import { PeerIdB58 } from "@fluencelabs/interfaces"; -type ArgName = string | null; +import { ParticleContext } from "../jsServiceHost/interfaces.js"; /** * A predicate of call params for sig service's sign method which determines whether signing operation is allowed or not */ -export type SecurityGuard = ( - params: CallParams, -) => boolean; +export type SecurityGuard = (params: ParticleContext) => boolean; /** * Only allow calls when tetraplet for 'data' argument satisfies the predicate */ -export const allowTetraplet = ( +export const allowTetraplet = ( pred: (tetraplet: SecurityTetraplet) => boolean, -): SecurityGuard => { +): SecurityGuard => { return (params) => { - const t = params.tetraplets["data"][0]; + const t = params.tetraplets[0][0]; return pred(t); }; }; @@ -41,10 +39,10 @@ export const allowTetraplet = ( /** * Only allow data which comes from the specified serviceId and fnName */ -export const allowServiceFn = ( +export const allowServiceFn = ( serviceId: string, fnName: string, -): SecurityGuard => { +): SecurityGuard => { return allowTetraplet((t) => { return t.service_id === serviceId && t.function_name === fnName; }); @@ -53,9 +51,7 @@ export const allowServiceFn = ( /** * Only allow data originated from the specified json_path */ -export const allowExactJsonPath = ( - jsonPath: string, -): SecurityGuard => { +export const allowExactJsonPath = (jsonPath: string): SecurityGuard => { return allowTetraplet((t) => { return t.json_path === jsonPath; }); @@ -64,9 +60,9 @@ export const allowExactJsonPath = ( /** * Only allow signing when particle is initiated at the specified peer */ -export const allowOnlyParticleOriginatedAt = ( +export const allowOnlyParticleOriginatedAt = ( peerId: PeerIdB58, -): SecurityGuard => { +): SecurityGuard => { return (params) => { return params.initPeerId === peerId; }; @@ -76,9 +72,7 @@ export const allowOnlyParticleOriginatedAt = ( * Only allow signing when all of the predicates are satisfied. * Useful for predicates reuse */ -export const and = ( - ...predicates: SecurityGuard[] -): SecurityGuard => { +export const and = (...predicates: SecurityGuard[]): SecurityGuard => { return (params) => { return predicates.every((x) => { return x(params); @@ -90,9 +84,7 @@ export const and = ( * Only allow signing when any of the predicates are satisfied. * Useful for predicates reuse */ -export const or = ( - ...predicates: SecurityGuard[] -): SecurityGuard => { +export const or = (...predicates: SecurityGuard[]): SecurityGuard => { return (params) => { return predicates.some((x) => { return x(params); diff --git a/packages/core/js-client/src/util/testUtils.ts b/packages/core/js-client/src/util/testUtils.ts index 80564f5b..ec75d0dc 100644 --- a/packages/core/js-client/src/util/testUtils.ts +++ b/packages/core/js-client/src/util/testUtils.ts @@ -20,7 +20,7 @@ import { Path, Aqua } from "@fluencelabs/aqua-api/aqua-api.js"; import { FunctionCallDef, JSONArray, - PassedArgs, + JSONValue, ServiceDef, } from "@fluencelabs/interfaces"; import { Subject, Subscribable } from "rxjs"; @@ -30,7 +30,10 @@ import { ClientConfig, RelayOptions } from "../clientPeer/types.js"; import { callAquaFunction } from "../compilerSupport/callFunction.js"; import { IConnection } from "../connection/interfaces.js"; import { DEFAULT_CONFIG, FluencePeer } from "../jsPeer/FluencePeer.js"; -import { CallServiceResultType } from "../jsServiceHost/interfaces.js"; +import { + CallServiceResultType, + ParticleContext, +} from "../jsServiceHost/interfaces.js"; import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js"; import { WrapFnIntoServiceCall } from "../jsServiceHost/serviceUtils.js"; import { KeyPair } from "../keypair/index.js"; @@ -73,6 +76,18 @@ interface FunctionInfo { funcDef: FunctionCallDef; } +/** + * Type for callback passed as aqua function argument + */ +export type ArgCallbackFunction = ( + ...args: [...JSONValue[], ParticleContext] +) => JSONValue | Promise; + +/** + * Arguments passed to Aqua function + */ +export type PassedArgs = { [key: string]: JSONValue | ArgCallbackFunction }; + export const compileAqua = async (aquaFile: string): Promise => { await fs.access(aquaFile); @@ -88,11 +103,12 @@ export const compileAqua = async (aquaFile: string): Promise => { ); } + console.log(compilationResult); + const functions = Object.entries(compilationResult.functions) .map(([name, fnInfo]: [string, FunctionInfo]) => { const callFn = (peer: FluencePeer, args: PassedArgs) => { return callAquaFunction({ - def: fnInfo.funcDef, script: fnInfo.script, config: {}, peer: peer,