Remove schema from js-client

This commit is contained in:
Akim Mamedov 2023-11-13 17:45:24 +07:00
parent 2f316cc8fb
commit 426f154377
26 changed files with 348 additions and 685 deletions

View File

@ -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<ArgName extends string | null> = {
/**
* The identifier of particle which triggered the call
*/
particleId: string;
/**
* The peer id which created the particle
*/
initPeerId: PeerIdB58;
/**
* Particle's timestamp when it was created
*/
timestamp: number;
/**
* Time to live in milliseconds. The time after the particle should be expired
*/
ttl: number;
/**
* Particle's signature
*/
signature?: string;
/**
* Security tetraplets
*/
tetraplets: ArgName extends string
? Record<ArgName, InterfaceToType<SecurityTetraplet>[]>
: Record<string, never>;
};
export type ServiceImpl = Record<
string,
(
...args: [...JSONArray, CallParams<string>]
) => MaybePromise<JSONValue | undefined>
>;
export type JSONValue =
| string
| number

View File

@ -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;

View File

@ -110,6 +110,7 @@ export const checkConnection = async (
peer.internals.initiateParticle(
particle,
() => {},
handleTimeout(() => {
reject("particle timed out");
}),

View File

@ -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),
);
peer.internals.initiateParticle(particle, (stage) => {
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')
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})`,
);
}
// TODO: make test
// if (
// isReturnTypeVoid(def) &&
// (stage.stage === "sent" || stage.stage === "localWorkDone")
// ) {
// resolve(undefined);
// }
// },
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);
});
};

View File

@ -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<string> => {
let prototype: Record<string, unknown> = service;
const serviceMethods = new Set<string>();
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<string, unknown>;
} 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,
);

View File

@ -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<string>] = [
...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<string> => {
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<string, SecurityTetraplet[]> = {};
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,

View File

@ -22,7 +22,5 @@ export type MaybePromise<T> = T | Promise<T>;
export type ServiceImpl = Record<
string,
(
...args: [...JSONArray, ParticleContext]
) => MaybePromise<JSONValue | undefined>
(...args: [...JSONArray, ParticleContext]) => MaybePromise<JSONValue>
>;

View File

@ -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<void> = 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) {

View File

@ -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");

View File

@ -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"]));

View File

@ -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));
});
}

View File

@ -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 {}

View File

@ -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

View File

@ -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,
};
};

View File

@ -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();
}
};

View File

@ -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<Buffer>((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,
};
}

View File

@ -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<SignReturnType> {
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)],
};
}

View File

@ -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<string> = 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`],
};
}

View File

@ -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 () => {

View File

@ -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,

View File

@ -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<string, CompiledFnCall>;
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,
);

View File

@ -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",
},
},
},
],
},
},
},
},
},
});
}

View File

@ -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<null>) => string | Promise<string>;
get_peer_id: (callParams: ParticleContext) => string | Promise<string>;
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<boolean>;
}
@ -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",
},
],
},
},
},
},
},
});
}

View File

@ -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<null>) => string[] | Promise<string[]>;
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",
},
},
},
],
},
},
},
},
},
});
}

View File

@ -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<T extends ArgName> = (
params: CallParams<T>,
) => boolean;
export type SecurityGuard = (params: ParticleContext) => boolean;
/**
* Only allow calls when tetraplet for 'data' argument satisfies the predicate
*/
export const allowTetraplet = <T extends ArgName>(
export const allowTetraplet = (
pred: (tetraplet: SecurityTetraplet) => boolean,
): SecurityGuard<T> => {
): 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 = <T extends ArgName>(
/**
* Only allow data which comes from the specified serviceId and fnName
*/
export const allowServiceFn = <T extends ArgName>(
export const allowServiceFn = (
serviceId: string,
fnName: string,
): SecurityGuard<T> => {
): SecurityGuard => {
return allowTetraplet((t) => {
return t.service_id === serviceId && t.function_name === fnName;
});
@ -53,9 +51,7 @@ export const allowServiceFn = <T extends ArgName>(
/**
* Only allow data originated from the specified json_path
*/
export const allowExactJsonPath = <T extends ArgName>(
jsonPath: string,
): SecurityGuard<T> => {
export const allowExactJsonPath = (jsonPath: string): SecurityGuard => {
return allowTetraplet((t) => {
return t.json_path === jsonPath;
});
@ -64,9 +60,9 @@ export const allowExactJsonPath = <T extends ArgName>(
/**
* Only allow signing when particle is initiated at the specified peer
*/
export const allowOnlyParticleOriginatedAt = <T extends ArgName>(
export const allowOnlyParticleOriginatedAt = (
peerId: PeerIdB58,
): SecurityGuard<T> => {
): SecurityGuard => {
return (params) => {
return params.initPeerId === peerId;
};
@ -76,9 +72,7 @@ export const allowOnlyParticleOriginatedAt = <T extends ArgName>(
* Only allow signing when all of the predicates are satisfied.
* Useful for predicates reuse
*/
export const and = <T extends ArgName>(
...predicates: SecurityGuard<T>[]
): SecurityGuard<T> => {
export const and = (...predicates: SecurityGuard[]): SecurityGuard => {
return (params) => {
return predicates.every((x) => {
return x(params);
@ -90,9 +84,7 @@ export const and = <T extends ArgName>(
* Only allow signing when any of the predicates are satisfied.
* Useful for predicates reuse
*/
export const or = <T extends ArgName>(
...predicates: SecurityGuard<T>[]
): SecurityGuard<T> => {
export const or = (...predicates: SecurityGuard[]): SecurityGuard => {
return (params) => {
return predicates.some((x) => {
return x(params);

View File

@ -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<JSONValue>;
/**
* Arguments passed to Aqua function
*/
export type PassedArgs = { [key: string]: JSONValue | ArgCallbackFunction };
export const compileAqua = async (aquaFile: string): Promise<CompiledFile> => {
await fs.access(aquaFile);
@ -88,11 +103,12 @@ export const compileAqua = async (aquaFile: string): Promise<CompiledFile> => {
);
}
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,