diff --git a/src/__test__/integration/builtins.spec.ts b/src/__test__/integration/builtins.spec.ts index ef0ff75a..016dda4e 100644 --- a/src/__test__/integration/builtins.spec.ts +++ b/src/__test__/integration/builtins.spec.ts @@ -10,7 +10,6 @@ import { } from '../../internal/builtins'; import { ModuleConfig } from '../../internal/moduleConfig'; import { checkConnection } from '../../api'; -import log from 'loglevel'; import { generatePeerId } from '../..'; import { FluenceClientImpl } from '../../internal/FluenceClientImpl'; import { createConnectedClient, nodes } from '../connection'; diff --git a/src/__test__/integration/client.spec.ts b/src/__test__/integration/client.spec.ts index 0c021d5a..a87ad633 100644 --- a/src/__test__/integration/client.spec.ts +++ b/src/__test__/integration/client.spec.ts @@ -2,9 +2,9 @@ import { encode } from 'bs58'; import { generatePeerId, peerIdToSeed, seedToPeerId } from '../../internal/peerIdUtils'; import { FluenceClientImpl } from '../../internal/FluenceClientImpl'; import log from 'loglevel'; -import { createClient } from '../../api'; +import { createClient, subscribeForErrors } from '../../api'; import Multiaddr from 'multiaddr'; -import { createConnectedClient, nodes } from '../connection'; +import { createConnectedClient, createLocalClient, nodes } from '../connection'; describe('Typescript usage suite', () => { it('should create private key from seed and back', async function () { @@ -219,4 +219,46 @@ describe('Typescript usage suite', () => { let res = await resMakingPromise; expect(res).toEqual(['some a', 'some b', 'some c', 'some d']); }); + + it('xor handling should work with connected client', async function () { + // arrange + const client = await createConnectedClient(nodes[0].multiaddr); + log.setLevel('info'); + + // act + let script = ` + (seq + (call relay ("op" "identity") []) + (call relay ("incorrect" "service") ["incorrect_arg"]) + ) + `; + const data = new Map(); + data.set('relay', client.relayPeerId); + + const promise = subscribeForErrors(client, 7000); + await client.sendScript(script, data); + + // assert + await expect(promise).rejects.toMatchObject({ + error: expect.stringContaining("Service with id 'incorrect' not found"), + instruction: expect.stringContaining('incorrect'), + }); + }); + + it('xor handling should work with local client', async function () { + // arrange + const client = await createLocalClient(); + + // act + let script = `(call %init_peer_id% ("incorrect" "service") ["incorrect_arg"])`; + + const promise = subscribeForErrors(client, 7000); + await client.sendScript(script); + + // assert + await expect(promise).rejects.toMatchObject({ + error: expect.stringContaining('There is no service: incorrect'), + instruction: expect.stringContaining('incorrect'), + }); + }); }); diff --git a/src/__test__/unit/air.spec.ts b/src/__test__/unit/air.spec.ts index 04209a83..92a5f4f9 100644 --- a/src/__test__/unit/air.spec.ts +++ b/src/__test__/unit/air.spec.ts @@ -45,27 +45,39 @@ describe('== AIR suite', () => { }); it('call broken script', async function () { + // arrange const client = await createLocalClient(); + const script = `(incorrect)`; - const script = `(htyth)`; + // act + const promise = client.sendScript(script); - await expect(client.sendScript(script)).rejects.toContain("aqua script can't be parsed"); + // assert + await expect(promise).rejects.toContain("aqua script can't be parsed"); }); it('call script without ttl', async function () { + // arrange const client = await createLocalClient(); - const script = `(call %init_peer_id% ("" "") [""])`; - await expect(client.sendScript(script, undefined, 1)).rejects.toContain('Particle expired'); + // act + const promise = client.sendScript(script, undefined, 1); + + // assert + await expect(promise).rejects.toContain('Particle expired'); }); it.skip('call broken script by fetch', async function () { + // arrange const client = await createLocalClient(); + const script = `(incorrect)`; - const script = `(htyth)`; + // act + const promise = client.fetch(script, ['result']); - await expect(client.fetch(script, ['result'])).rejects.toContain("aqua script can't be parsed"); + // assert + await expect(promise).rejects.toContain("aqua script can't be parsed"); }); it('check particle arguments', async function () { diff --git a/src/api.ts b/src/api.ts index 53137dab..dab43749 100644 --- a/src/api.ts +++ b/src/api.ts @@ -190,3 +190,25 @@ export const checkConnection = async (client: FluenceClient): Promise = return false; } }; + +export const subscribeForErrors = (client: FluenceClient, ttl: number): Promise => { + return new Promise((resolve, reject) => { + registerServiceFunction(client, '__magic', 'handle_xor', (args, _) => { + setTimeout(() => { + try { + reject(JSON.parse(args[0])); + } catch { + reject(args); + } + }, 0); + + unregisterServiceFunction(client, '__magic', 'handle_xor'); + return {}; + }); + + setTimeout(() => { + unregisterServiceFunction(client, '__magic', 'handle_xor'); + resolve(); + }, ttl); + }); +}; diff --git a/src/internal/FluenceClientBase.ts b/src/internal/FluenceClientBase.ts index 0602ebdb..07315d61 100644 --- a/src/internal/FluenceClientBase.ts +++ b/src/internal/FluenceClientBase.ts @@ -26,7 +26,7 @@ import { PeerIdB58 } from './commonTypes'; export abstract class FluenceClientBase { readonly selfPeerIdFull: PeerId; - get relayPeerId(): PeerIdB58 { + get relayPeerId(): PeerIdB58 | undefined { return this.connection?.nodePeerId.toB58String(); } @@ -88,7 +88,7 @@ export abstract class FluenceClientBase { } async sendScript(script: string, data?: Map, ttl?: number): Promise { - const particle = await build(this.selfPeerIdFull, script, data, ttl); + const particle = await build(this.selfPeerIdFull, this.relayPeerId, script, data, ttl); await this.processor.executeLocalParticle(particle); return particle.id; } diff --git a/src/internal/FluenceClientImpl.ts b/src/internal/FluenceClientImpl.ts index cbcd5d7f..e2c6dfe2 100644 --- a/src/internal/FluenceClientImpl.ts +++ b/src/internal/FluenceClientImpl.ts @@ -71,7 +71,7 @@ export class FluenceClientImpl extends FluenceClientBase implements FluenceClien data = this.addRelayToArgs(data); const callBackId = genUUID(); script = wrapFetchCall(script, callBackId, resultArgNames); - const particle = await build(this.selfPeerIdFull, script, data, ttl, callBackId); + const particle = await build(this.selfPeerIdFull, this.relayPeerId, script, data, ttl, callBackId); const prFetch = new Promise(async (resolve, reject) => { this.fetchParticles.set(callBackId, { resolve, reject }); diff --git a/src/internal/ParticleProcessor.ts b/src/internal/ParticleProcessor.ts index c5b19630..abcbe0c8 100644 --- a/src/internal/ParticleProcessor.ts +++ b/src/internal/ParticleProcessor.ts @@ -77,23 +77,17 @@ export class ParticleProcessor { // TODO: destroy interpreter } - async executeLocalParticle(particle: ParticleDto) { + async executeLocalParticle(particle: ParticleDto): Promise { this.strategy?.onLocalParticleRecieved(particle); return new Promise((resolve, reject) => { - const resolveCallback = function () { - resolve(); - }; - const rejectCallback = function (err: any) { - reject(err); - }; // we check by callbacks that the script passed through the interpreter without errors - this.handleParticle(particle, resolveCallback, rejectCallback); + this.handleParticle(particle, resolve, reject); }); } - async executeExternalParticle(particle: ParticleDto) { + async executeExternalParticle(particle: ParticleDto): Promise { this.strategy?.onExternalParticleRecieved(particle); - await this.handleExternalParticle(particle); + return await this.handleExternalParticle(particle); } /* @@ -231,7 +225,7 @@ export class ParticleProcessor { if (nextParticle) { // update current particle this.setCurrentParticleId(nextParticle.id); - await this.handleParticle(nextParticle); + return await this.handleParticle(nextParticle); } else { // wait for a new call (do nothing) if there is no new particle in a queue this.setCurrentParticleId(undefined); @@ -249,7 +243,7 @@ export class ParticleProcessor { if (error !== undefined) { log.error('error in external particle: ', error); } else { - await this.handleParticle(particle); + return await this.handleParticle(particle); } } diff --git a/src/internal/particle.ts b/src/internal/particle.ts index 44326af9..c622e3ae 100644 --- a/src/internal/particle.ts +++ b/src/internal/particle.ts @@ -19,6 +19,7 @@ import { fromByteArray, toByteArray } from 'base64-js'; import PeerId from 'peer-id'; import { encode } from 'bs58'; import { injectDataIntoParticle } from './ParticleProcessor'; +import { PeerIdB58 } from './commonTypes'; const DEFAULT_TTL = 7000; @@ -91,8 +92,28 @@ function wrapWithVariableInjectionScript(script: string, fields: string[]): stri return script; } +function wrapWithXor(script: string): string { + return ` + (xor + ${script} + (seq + (call __magic_relay ("op" "identity") []) + (call %init_peer_id% ("__magic" "handle_xor") [%last_error%]) + ) + )`; +} + +function wrapWithXorLocal(script: string): string { + return ` + (xor + ${script} + (call %init_peer_id% ("__magic" "handle_xor") [%last_error%]) + )`; +} + export async function build( peerId: PeerId, + relay: PeerIdB58 | undefined, script: string, data?: Map, ttl?: number, @@ -109,8 +130,16 @@ export async function build( ttl = DEFAULT_TTL; } + if (relay) { + data.set('__magic_relay', relay); + } injectDataIntoParticle(id, data, ttl); script = wrapWithVariableInjectionScript(script, Array.from(data.keys())); + if (relay) { + script = wrapWithXor(script); + } else { + script = wrapWithXorLocal(script); + } let particle: ParticleDto = { id: id,