2021-10-20 22:20:43 +03:00
|
|
|
/*
|
|
|
|
* Copyright 2021 Fluence Labs Limited
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import {
|
|
|
|
AirInterpreter,
|
|
|
|
CallRequestsArray,
|
|
|
|
CallResultsArray,
|
|
|
|
InterpreterResult,
|
|
|
|
LogLevel,
|
|
|
|
CallServiceResult as AvmCallServiceResult,
|
|
|
|
} from '@fluencelabs/avm';
|
2021-09-08 12:42:30 +03:00
|
|
|
import { Multiaddr } from 'multiaddr';
|
2021-10-20 22:20:43 +03:00
|
|
|
import { CallServiceData, CallServiceResult, GenericCallServiceHandler, ResultCodes } from './commonTypes';
|
|
|
|
import { CallServiceHandler as LegacyCallServiceHandler } from './compilerSupport/LegacyCallServiceHandler';
|
2021-09-08 12:42:30 +03:00
|
|
|
import { PeerIdB58 } from './commonTypes';
|
2021-10-20 22:20:43 +03:00
|
|
|
import { FluenceConnection } from './FluenceConnection';
|
2021-11-09 14:37:44 +03:00
|
|
|
import { Particle, ParticleExecutionStage, ParticleQueueItem } from './Particle';
|
2021-09-08 12:42:30 +03:00
|
|
|
import { KeyPair } from './KeyPair';
|
2021-10-20 22:20:43 +03:00
|
|
|
import { createInterpreter, dataToString } from './utils';
|
|
|
|
import { filter, pipe, Subject, tap } from 'rxjs';
|
|
|
|
import { RequestFlow } from './compilerSupport/v1';
|
|
|
|
import log from 'loglevel';
|
|
|
|
import { defaultServices } from './defaultServices';
|
2021-11-09 14:37:44 +03:00
|
|
|
import { instanceOf } from 'ts-pattern';
|
2021-09-08 12:42:30 +03:00
|
|
|
|
|
|
|
/**
|
2021-10-20 22:20:43 +03:00
|
|
|
* Node of the Fluence network specified as a pair of node's multiaddr and it's peer id
|
2021-09-08 12:42:30 +03:00
|
|
|
*/
|
|
|
|
type Node = {
|
|
|
|
peerId: PeerIdB58;
|
|
|
|
multiaddr: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enum representing the log level used in Aqua VM.
|
|
|
|
* Possible values: 'info', 'trace', 'debug', 'info', 'warn', 'error', 'off';
|
|
|
|
*/
|
|
|
|
export type AvmLoglevel = LogLevel;
|
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
const DEFAULT_TTL = 7000;
|
|
|
|
|
2021-09-08 12:42:30 +03:00
|
|
|
/**
|
|
|
|
* Configuration used when initiating Fluence Peer
|
|
|
|
*/
|
|
|
|
export interface PeerConfig {
|
|
|
|
/**
|
|
|
|
* Node in Fluence network to connect to.
|
|
|
|
* Can be in the form of:
|
|
|
|
* - string: multiaddr in string format
|
|
|
|
* - Multiaddr: multiaddr object, @see https://github.com/multiformats/js-multiaddr
|
|
|
|
* - Node: node structure, @see Node
|
|
|
|
* If not specified the will work locally and would not be able to send or receive particles.
|
|
|
|
*/
|
|
|
|
connectTo?: string | Multiaddr | Node;
|
|
|
|
|
2021-09-10 19:21:45 +03:00
|
|
|
/**
|
|
|
|
* Specify log level for Aqua VM running on the peer
|
|
|
|
*/
|
2021-09-08 12:42:30 +03:00
|
|
|
avmLogLevel?: AvmLoglevel;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Specify the KeyPair to be used to identify the Fluence Peer.
|
|
|
|
* Will be generated randomly if not specified
|
|
|
|
*/
|
|
|
|
KeyPair?: KeyPair;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When the peer established the connection to the network it sends a ping-like message to check if it works correctly.
|
|
|
|
* The options allows to specify the timeout for that message in milliseconds.
|
|
|
|
* If not specified the default timeout will be used
|
|
|
|
*/
|
|
|
|
checkConnectionTimeoutMs?: number;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When the peer established the connection to the network it sends a ping-like message to check if it works correctly.
|
|
|
|
* If set to true, the ping-like message will be skipped
|
|
|
|
* Default: false
|
|
|
|
*/
|
|
|
|
skipCheckConnection?: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The dialing timeout in milliseconds
|
|
|
|
*/
|
|
|
|
dialTimeoutMs?: number;
|
2021-10-21 17:56:21 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the default TTL for all particles originating from the peer with no TTL specified.
|
|
|
|
* If the originating particle's TTL is defined then that value will be used
|
|
|
|
* If the option is not set default TTL will be 7000
|
|
|
|
*/
|
|
|
|
defaultTtlMs?: number;
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Information about Fluence Peer connection
|
|
|
|
*/
|
2021-09-10 19:21:45 +03:00
|
|
|
export interface PeerStatus {
|
|
|
|
/**
|
|
|
|
* Is the peer connected to network or not
|
|
|
|
*/
|
|
|
|
isInitialized: Boolean;
|
|
|
|
|
2021-09-08 12:42:30 +03:00
|
|
|
/**
|
|
|
|
* Is the peer connected to network or not
|
|
|
|
*/
|
|
|
|
isConnected: Boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The Peer's identification in the Fluence network
|
|
|
|
*/
|
2021-09-10 19:21:45 +03:00
|
|
|
peerId: PeerIdB58 | null;
|
2021-09-08 12:42:30 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The relays's peer id to which the peer is connected to
|
|
|
|
*/
|
2021-09-10 19:21:45 +03:00
|
|
|
relayPeerId: PeerIdB58 | null;
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This class implements the Fluence protocol for javascript-based environments.
|
|
|
|
* It provides all the necessary features to communicate with Fluence network
|
|
|
|
*/
|
|
|
|
export class FluencePeer {
|
|
|
|
/**
|
2021-09-10 19:21:45 +03:00
|
|
|
* Creates a new Fluence Peer instance.
|
2021-09-08 12:42:30 +03:00
|
|
|
*/
|
|
|
|
constructor() {}
|
|
|
|
|
|
|
|
/**
|
2021-09-10 19:21:45 +03:00
|
|
|
* Checks whether the object is instance of FluencePeer class
|
|
|
|
* @param obj - object to check if it is FluencePeer
|
|
|
|
* @returns true if the object is FluencePeer false otherwise
|
|
|
|
*/
|
|
|
|
static isInstance(obj: FluencePeer): boolean {
|
|
|
|
if (obj && obj._isFluenceAwesome) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the peer's status
|
2021-09-08 12:42:30 +03:00
|
|
|
*/
|
2021-09-10 19:21:45 +03:00
|
|
|
getStatus(): PeerStatus {
|
|
|
|
const hasKeyPair = this._keyPair !== undefined;
|
2021-09-08 12:42:30 +03:00
|
|
|
return {
|
2021-09-10 19:21:45 +03:00
|
|
|
isInitialized: hasKeyPair,
|
2021-10-20 22:20:43 +03:00
|
|
|
isConnected: this._connection !== undefined,
|
|
|
|
peerId: this._keyPair?.Libp2pPeerId?.toB58String() || null,
|
2021-09-10 19:21:45 +03:00
|
|
|
relayPeerId: this._relayPeerId || null,
|
2021-09-08 12:42:30 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes the peer: starts the Aqua VM, initializes the default call service handlers
|
|
|
|
* and (optionally) connect to the Fluence network
|
|
|
|
* @param config - object specifying peer configuration
|
|
|
|
*/
|
2021-09-10 19:21:45 +03:00
|
|
|
async start(config?: PeerConfig): Promise<void> {
|
2021-09-08 12:42:30 +03:00
|
|
|
if (config?.KeyPair) {
|
|
|
|
this._keyPair = config!.KeyPair;
|
|
|
|
} else {
|
|
|
|
this._keyPair = await KeyPair.randomEd25519();
|
|
|
|
}
|
|
|
|
|
2021-11-04 15:15:30 +03:00
|
|
|
this._defaultTTL =
|
|
|
|
config?.defaultTtlMs !== undefined // don't miss value 0 (zero)
|
|
|
|
? config?.defaultTtlMs
|
|
|
|
: DEFAULT_TTL;
|
2021-10-21 17:56:21 +03:00
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
this._interpreter = await createInterpreter(config?.avmLogLevel || 'off');
|
2021-09-08 12:42:30 +03:00
|
|
|
|
|
|
|
if (config?.connectTo) {
|
2021-10-20 22:20:43 +03:00
|
|
|
let connectToMultiAddr: Multiaddr;
|
2021-09-08 12:42:30 +03:00
|
|
|
let fromNode = (config.connectTo as any).multiaddr;
|
|
|
|
if (fromNode) {
|
2021-10-20 22:20:43 +03:00
|
|
|
connectToMultiAddr = new Multiaddr(fromNode);
|
2021-09-08 12:42:30 +03:00
|
|
|
} else {
|
2021-10-20 22:20:43 +03:00
|
|
|
connectToMultiAddr = new Multiaddr(config.connectTo as string);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._relayPeerId = connectToMultiAddr.getPeerId();
|
|
|
|
|
|
|
|
if (this._connection) {
|
|
|
|
await this._connection.disconnect();
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
this._connection = await FluenceConnection.createConnection({
|
|
|
|
peerId: this._keyPair.Libp2pPeerId,
|
|
|
|
relayAddress: connectToMultiAddr,
|
|
|
|
dialTimeoutMs: config.dialTimeoutMs,
|
2021-11-09 14:37:44 +03:00
|
|
|
onIncomingParticle: (p) => this._incomingParticles.next({ particle: p, onStageChange: () => {} }),
|
2021-10-20 22:20:43 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
await this._connect();
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
2021-10-20 22:20:43 +03:00
|
|
|
|
|
|
|
this._legacyCallServiceHandler = new LegacyCallServiceHandler();
|
|
|
|
registerDefaultServices(this);
|
|
|
|
|
|
|
|
this._startParticleProcessing();
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-10-20 22:20:43 +03:00
|
|
|
* Un-initializes the peer: stops all the underlying workflows, stops the Aqua VM
|
2021-09-08 12:42:30 +03:00
|
|
|
* and disconnects from the Fluence network
|
|
|
|
*/
|
2021-09-10 19:21:45 +03:00
|
|
|
async stop() {
|
2021-10-20 22:20:43 +03:00
|
|
|
this._stopParticleProcessing();
|
2021-09-08 12:42:30 +03:00
|
|
|
await this._disconnect();
|
2021-10-20 22:20:43 +03:00
|
|
|
this._relayPeerId = null;
|
|
|
|
this._legacyCallServiceHandler = null;
|
|
|
|
|
|
|
|
this._particleSpecificHandlers.clear();
|
|
|
|
this._commonHandlers.clear();
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// internal api
|
|
|
|
|
|
|
|
/**
|
2021-09-27 22:11:35 +03:00
|
|
|
* Is not intended to be used manually. Subject to change
|
2021-09-08 12:42:30 +03:00
|
|
|
*/
|
|
|
|
get internals() {
|
|
|
|
return {
|
2021-10-20 22:20:43 +03:00
|
|
|
/**
|
|
|
|
* Initiates a new particle execution starting from local peer
|
|
|
|
* @param particle - particle to start execution of
|
|
|
|
*/
|
2021-11-09 14:37:44 +03:00
|
|
|
initiateParticle: (particle: Particle, onStageChange: (stage: ParticleExecutionStage) => void): void => {
|
|
|
|
if (!this.getStatus().isInitialized) {
|
|
|
|
throw 'Cannot initiate new particle: peer is not initialized';
|
|
|
|
}
|
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
if (particle.initPeerId === undefined) {
|
|
|
|
particle.initPeerId = this.getStatus().peerId;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (particle.ttl === undefined) {
|
2021-10-21 17:56:21 +03:00
|
|
|
particle.ttl = this._defaultTTL;
|
2021-10-20 22:20:43 +03:00
|
|
|
}
|
|
|
|
|
2021-11-09 14:37:44 +03:00
|
|
|
this._incomingParticles.next({
|
|
|
|
particle: particle,
|
|
|
|
onStageChange: onStageChange,
|
|
|
|
});
|
2021-10-20 22:20:43 +03:00
|
|
|
},
|
2021-11-09 14:37:44 +03:00
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
/**
|
|
|
|
* Register Call Service handler functions
|
|
|
|
*/
|
|
|
|
regHandler: {
|
|
|
|
/**
|
|
|
|
* Register handler for all particles
|
|
|
|
*/
|
|
|
|
common: (
|
|
|
|
// force new line
|
|
|
|
serviceId: string,
|
|
|
|
fnName: string,
|
|
|
|
handler: GenericCallServiceHandler,
|
|
|
|
) => {
|
|
|
|
this._commonHandlers.set(serviceFnKey(serviceId, fnName), handler);
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Register handler which will be called only for particle with the specific id
|
|
|
|
*/
|
|
|
|
forParticle: (
|
|
|
|
particleId: string,
|
|
|
|
serviceId: string,
|
|
|
|
fnName: string,
|
|
|
|
handler: GenericCallServiceHandler,
|
|
|
|
) => {
|
|
|
|
let psh = this._particleSpecificHandlers.get(particleId);
|
|
|
|
if (psh === undefined) {
|
|
|
|
psh = new Map<string, GenericCallServiceHandler>();
|
|
|
|
this._particleSpecificHandlers.set(particleId, psh);
|
|
|
|
}
|
|
|
|
|
|
|
|
psh.set(serviceFnKey(serviceId, fnName), handler);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @deprecated
|
|
|
|
*/
|
|
|
|
initiateFlow: (request: RequestFlow): void => {
|
|
|
|
const particle = request.particle;
|
|
|
|
|
|
|
|
this._legacyParticleSpecificHandlers.set(particle.id, {
|
|
|
|
handler: request.handler,
|
|
|
|
error: request.error,
|
|
|
|
timeout: request.timeout,
|
|
|
|
});
|
|
|
|
|
2021-11-09 14:37:44 +03:00
|
|
|
this.internals.initiateParticle(particle, (stage) => {
|
|
|
|
if (stage.stage === 'interpreterError') {
|
|
|
|
request?.error(stage.errorMessage);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stage.stage === 'expired') {
|
|
|
|
request?.timeout();
|
|
|
|
}
|
|
|
|
});
|
2021-10-20 22:20:43 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @deprecated
|
|
|
|
*/
|
|
|
|
callServiceHandler: this._legacyCallServiceHandler,
|
2021-09-08 12:42:30 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// private
|
|
|
|
|
2021-09-10 19:21:45 +03:00
|
|
|
/**
|
|
|
|
* Used in `isInstance` to check if an object is of type FluencePeer. That's a hack to work around corner cases in JS type system
|
|
|
|
*/
|
|
|
|
private _isFluenceAwesome = true;
|
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
// TODO:: make public when full connection\disconnection cycle is implemented properly
|
|
|
|
private async _connect(): Promise<void> {
|
|
|
|
return this._connection?.connect();
|
|
|
|
}
|
2021-09-08 12:42:30 +03:00
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
// TODO:: make public when full connection\disconnection cycle is implemented properly
|
|
|
|
private async _disconnect(): Promise<void> {
|
|
|
|
if (this._connection) {
|
|
|
|
return this._connection.disconnect();
|
|
|
|
}
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
// Queues for incoming and outgoing particles
|
2021-09-08 12:42:30 +03:00
|
|
|
|
2021-11-09 14:37:44 +03:00
|
|
|
private _incomingParticles = new Subject<ParticleQueueItem>();
|
|
|
|
private _outgoingParticles = new Subject<ParticleQueueItem>();
|
2021-10-20 22:20:43 +03:00
|
|
|
|
|
|
|
// Call service handler
|
|
|
|
|
|
|
|
private _particleSpecificHandlers = new Map<string, Map<string, GenericCallServiceHandler>>();
|
|
|
|
private _commonHandlers = new Map<string, GenericCallServiceHandler>();
|
2021-09-08 12:42:30 +03:00
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
// Internal peer state
|
|
|
|
|
2021-10-21 17:56:21 +03:00
|
|
|
private _defaultTTL: number;
|
2021-10-20 22:20:43 +03:00
|
|
|
private _relayPeerId: PeerIdB58 | null = null;
|
|
|
|
private _keyPair: KeyPair;
|
2021-09-08 12:42:30 +03:00
|
|
|
private _connection: FluenceConnection;
|
|
|
|
private _interpreter: AirInterpreter;
|
2021-10-20 22:20:43 +03:00
|
|
|
private _timeouts: Array<NodeJS.Timeout> = [];
|
2021-11-09 14:37:44 +03:00
|
|
|
private _particleQueues = new Map<string, Subject<ParticleQueueItem>>();
|
2021-10-20 22:20:43 +03:00
|
|
|
|
|
|
|
private _startParticleProcessing() {
|
|
|
|
this._incomingParticles
|
|
|
|
.pipe(
|
2021-11-09 14:37:44 +03:00
|
|
|
tap((x) => {
|
|
|
|
x.particle.logTo('debug', 'particle received:');
|
|
|
|
}),
|
2021-10-21 17:56:21 +03:00
|
|
|
filterExpiredParticles(this._expireParticle.bind(this)),
|
2021-10-20 22:20:43 +03:00
|
|
|
)
|
2021-11-09 14:37:44 +03:00
|
|
|
.subscribe((item) => {
|
|
|
|
const p = item.particle;
|
2021-10-21 17:56:21 +03:00
|
|
|
let particlesQueue = this._particleQueues.get(p.id);
|
2021-10-20 22:20:43 +03:00
|
|
|
|
|
|
|
if (!particlesQueue) {
|
|
|
|
particlesQueue = this._createParticlesProcessingQueue();
|
2021-10-21 17:56:21 +03:00
|
|
|
this._particleQueues.set(p.id, particlesQueue);
|
2021-10-20 22:20:43 +03:00
|
|
|
|
|
|
|
const timeout = setTimeout(() => {
|
2021-11-09 14:37:44 +03:00
|
|
|
this._expireParticle(item);
|
2021-10-20 22:20:43 +03:00
|
|
|
}, p.actualTtl());
|
|
|
|
|
|
|
|
this._timeouts.push(timeout);
|
|
|
|
}
|
|
|
|
|
2021-11-09 14:37:44 +03:00
|
|
|
particlesQueue.next(item);
|
2021-10-20 22:20:43 +03:00
|
|
|
});
|
2021-09-08 12:42:30 +03:00
|
|
|
|
2021-11-09 14:37:44 +03:00
|
|
|
this._outgoingParticles.subscribe(async (item) => {
|
|
|
|
await this._connection.sendParticle(item.particle);
|
|
|
|
item.onStageChange({ stage: 'sent' });
|
2021-10-20 22:20:43 +03:00
|
|
|
});
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
|
|
|
|
2021-11-09 14:37:44 +03:00
|
|
|
private _expireParticle(item: ParticleQueueItem) {
|
|
|
|
const particleId = item.particle.id;
|
|
|
|
log.debug(
|
|
|
|
`particle ${particleId} has expired after ${item.particle.ttl}. Deleting particle-related queues and handlers`,
|
|
|
|
);
|
2021-10-21 17:56:21 +03:00
|
|
|
|
|
|
|
this._particleQueues.delete(particleId);
|
|
|
|
this._particleSpecificHandlers.delete(particleId);
|
2021-11-09 14:37:44 +03:00
|
|
|
|
|
|
|
item.onStageChange({ stage: 'expired' });
|
2021-10-21 17:56:21 +03:00
|
|
|
}
|
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
private _createParticlesProcessingQueue() {
|
2021-11-09 14:37:44 +03:00
|
|
|
let particlesQueue = new Subject<ParticleQueueItem>();
|
2021-10-20 22:20:43 +03:00
|
|
|
let prevData: Uint8Array = Buffer.from([]);
|
|
|
|
|
|
|
|
particlesQueue
|
|
|
|
.pipe(
|
|
|
|
// force new line
|
2021-10-21 17:56:21 +03:00
|
|
|
filterExpiredParticles(this._expireParticle.bind(this)),
|
2021-10-20 22:20:43 +03:00
|
|
|
)
|
2021-11-09 14:37:44 +03:00
|
|
|
.subscribe((item) => {
|
|
|
|
const particle = item.particle;
|
|
|
|
const result = runInterpreter(this.getStatus().peerId, this._interpreter, particle, prevData);
|
|
|
|
|
|
|
|
// Do not continue if there was an error in particle interpretation
|
|
|
|
if (isInterpretationSuccessful(result)) {
|
|
|
|
item.onStageChange({ stage: 'interpreterError', errorMessage: result.errorMessage });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
item.onStageChange({ stage: 'interpreted' });
|
|
|
|
}, 0);
|
2021-10-20 22:20:43 +03:00
|
|
|
|
2021-11-09 14:37:44 +03:00
|
|
|
const newData = Buffer.from(result.data);
|
|
|
|
prevData = newData;
|
2021-10-20 22:20:43 +03:00
|
|
|
|
|
|
|
// send particle further if requested
|
|
|
|
if (result.nextPeerPks.length > 0) {
|
2021-11-09 14:37:44 +03:00
|
|
|
const newParticle = particle.clone();
|
|
|
|
newParticle.data = newData;
|
|
|
|
this._outgoingParticles.next({ ...item, particle: newParticle });
|
2021-10-20 22:20:43 +03:00
|
|
|
}
|
2021-09-08 12:42:30 +03:00
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
// execute call requests if needed
|
|
|
|
// and put particle with the results back to queue
|
|
|
|
if (result.callRequests.length > 0) {
|
2021-11-09 14:37:44 +03:00
|
|
|
this._execCallRequests(particle, result.callRequests).then((callResults) => {
|
|
|
|
const newParticle = particle.clone();
|
2021-10-20 22:20:43 +03:00
|
|
|
newParticle.callResults = callResults;
|
|
|
|
newParticle.data = Buffer.from([]);
|
|
|
|
|
2021-11-09 14:37:44 +03:00
|
|
|
particlesQueue.next({ ...item, particle: newParticle });
|
2021-10-20 22:20:43 +03:00
|
|
|
});
|
2021-11-09 14:37:44 +03:00
|
|
|
} else {
|
|
|
|
item.onStageChange({ stage: 'localWorkDone' });
|
2021-10-20 22:20:43 +03:00
|
|
|
}
|
|
|
|
});
|
2021-09-08 12:42:30 +03:00
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
return particlesQueue;
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
private async _execCallRequests(p: Particle, callRequests: CallRequestsArray): Promise<CallResultsArray> {
|
|
|
|
// execute all requests asynchronously
|
|
|
|
const promises = callRequests.map(([key, callRequest]) => {
|
|
|
|
const req = {
|
|
|
|
fnName: callRequest.functionName,
|
|
|
|
args: callRequest.arguments,
|
|
|
|
serviceId: callRequest.serviceId,
|
|
|
|
tetraplets: callRequest.tetraplets,
|
|
|
|
particleContext: p.getParticleContext(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// execute single requests and catch possible errors
|
|
|
|
const promise = this._execSingleCallRequest(req)
|
|
|
|
.catch(
|
|
|
|
(err): CallServiceResult => ({
|
|
|
|
retCode: ResultCodes.exceptionInHandler,
|
|
|
|
result: `Handler failed. fnName="${req.fnName}" serviceId="${
|
|
|
|
req.serviceId
|
|
|
|
}" error: ${err.toString()}`,
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
.then(
|
|
|
|
(res): AvmCallServiceResult => ({
|
|
|
|
result: JSON.stringify(res.result),
|
|
|
|
retCode: res.retCode,
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
.then((res): [key: number, res: AvmCallServiceResult] => [key, res]);
|
|
|
|
|
|
|
|
return promise;
|
2021-09-08 12:42:30 +03:00
|
|
|
});
|
2021-10-20 22:20:43 +03:00
|
|
|
// don't block
|
|
|
|
const res = await Promise.all(promises);
|
|
|
|
log.debug(`Executed call service for particle id=${p.id}, Call service results: `, res);
|
|
|
|
return res;
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
private async _execSingleCallRequest(req: CallServiceData): Promise<CallServiceResult> {
|
|
|
|
const particleId = req.particleContext.particleId;
|
2021-09-08 12:42:30 +03:00
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
// trying particle-specific handler
|
|
|
|
const lh = this._legacyParticleSpecificHandlers.get(particleId);
|
|
|
|
let res: CallServiceResult = {
|
|
|
|
result: undefined,
|
|
|
|
retCode: undefined,
|
|
|
|
};
|
|
|
|
if (lh !== undefined) {
|
|
|
|
res = lh.handler.execute(req);
|
|
|
|
}
|
2021-09-08 12:42:30 +03:00
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
// if it didn't return any result trying to run the common handler
|
|
|
|
if (res?.result === undefined) {
|
|
|
|
res = this._legacyCallServiceHandler.execute(req);
|
|
|
|
}
|
2021-09-08 12:42:30 +03:00
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
// No result from legacy handler.
|
|
|
|
// Trying to execute async handler
|
|
|
|
if (res.retCode === undefined) {
|
|
|
|
const key = serviceFnKey(req.serviceId, req.fnName);
|
|
|
|
const psh = this._particleSpecificHandlers.get(particleId);
|
|
|
|
let handler: GenericCallServiceHandler;
|
|
|
|
|
|
|
|
// we should prioritize handler for this particle if there is one
|
|
|
|
// if particle-specific handlers exist for this particle try getting handler there
|
|
|
|
if (psh !== undefined) {
|
|
|
|
handler = psh.get(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
// then try to find a common handler for all particles with this service-fn key
|
|
|
|
// if there is no particle-specific handler, get one from common map
|
|
|
|
if (handler === undefined) {
|
|
|
|
handler = this._commonHandlers.get(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we found a handler, execute it
|
|
|
|
// otherwise return useful error message to AVM
|
|
|
|
res = handler
|
|
|
|
? await handler(req)
|
|
|
|
: {
|
|
|
|
retCode: ResultCodes.unknownError,
|
|
|
|
result: `No handler has been registered for serviceId='${req.serviceId}' fnName='${req.fnName}' args='${req.args}'`,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (res.result === undefined) {
|
|
|
|
res.result = null;
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
return res;
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
private _stopParticleProcessing() {
|
|
|
|
// do not hang if the peer has been stopped while some of the timeouts are still being executed
|
|
|
|
for (let item of this._timeouts) {
|
|
|
|
clearTimeout(item);
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
2021-10-21 17:56:21 +03:00
|
|
|
this._particleQueues.clear();
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
/**
|
|
|
|
* @deprecated
|
|
|
|
*/
|
|
|
|
private _legacyParticleSpecificHandlers = new Map<
|
|
|
|
string,
|
|
|
|
{
|
|
|
|
handler: LegacyCallServiceHandler;
|
|
|
|
timeout?: () => void;
|
|
|
|
error?: (reason?: any) => void;
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
2021-10-20 22:20:43 +03:00
|
|
|
>();
|
2021-09-08 12:42:30 +03:00
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
/**
|
|
|
|
* @deprecated
|
|
|
|
*/
|
|
|
|
private _legacyCallServiceHandler: LegacyCallServiceHandler;
|
|
|
|
}
|
2021-09-08 12:42:30 +03:00
|
|
|
|
2021-11-09 14:37:44 +03:00
|
|
|
function isInterpretationSuccessful(result: InterpreterResult) {
|
|
|
|
return result.retCode !== 0 || result?.errorMessage?.length > 0;
|
|
|
|
}
|
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
function serviceFnKey(serviceId: string, fnName: string) {
|
|
|
|
return `${serviceId}/${fnName}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
function registerDefaultServices(peer: FluencePeer) {
|
|
|
|
for (let serviceId in defaultServices) {
|
|
|
|
for (let fnName in defaultServices[serviceId]) {
|
|
|
|
const h = defaultServices[serviceId][fnName];
|
|
|
|
peer.internals.regHandler.common(serviceId, fnName, h);
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
2021-10-20 22:20:43 +03:00
|
|
|
}
|
|
|
|
}
|
2021-09-08 12:42:30 +03:00
|
|
|
|
2021-10-20 22:20:43 +03:00
|
|
|
function runInterpreter(
|
|
|
|
currentPeerId: PeerIdB58,
|
|
|
|
interpreter: AirInterpreter,
|
|
|
|
particle: Particle,
|
|
|
|
prevData: Uint8Array,
|
|
|
|
): InterpreterResult {
|
|
|
|
particle.logTo('debug', 'Sending particle to interpreter');
|
|
|
|
log.debug('prevData: ', dataToString(prevData));
|
|
|
|
log.debug('data: ', dataToString(particle.data));
|
|
|
|
const interpreterResult = interpreter.invoke(
|
|
|
|
particle.script,
|
|
|
|
prevData,
|
|
|
|
particle.data,
|
|
|
|
{
|
|
|
|
initPeerId: particle.initPeerId,
|
|
|
|
currentPeerId: currentPeerId,
|
|
|
|
},
|
|
|
|
particle.callResults,
|
|
|
|
);
|
|
|
|
|
|
|
|
const toLog: any = { ...interpreterResult };
|
|
|
|
toLog.data = dataToString(toLog.data);
|
2021-11-09 14:37:44 +03:00
|
|
|
|
|
|
|
if (isInterpretationSuccessful(interpreterResult)) {
|
|
|
|
log.debug('Interpreter result: ', toLog);
|
|
|
|
} else {
|
|
|
|
log.error('Interpreter failed: ', toLog);
|
|
|
|
}
|
2021-10-20 22:20:43 +03:00
|
|
|
return interpreterResult;
|
|
|
|
}
|
2021-09-08 12:42:30 +03:00
|
|
|
|
2021-11-09 14:37:44 +03:00
|
|
|
function filterExpiredParticles(onParticleExpiration: (item: ParticleQueueItem) => void) {
|
2021-10-20 22:20:43 +03:00
|
|
|
return pipe(
|
2021-11-09 14:37:44 +03:00
|
|
|
tap((item: ParticleQueueItem) => {
|
|
|
|
if (item.particle.hasExpired()) {
|
|
|
|
onParticleExpiration(item);
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|
2021-10-20 22:20:43 +03:00
|
|
|
}),
|
2021-11-09 14:37:44 +03:00
|
|
|
filter((x: ParticleQueueItem) => !x.particle.hasExpired()),
|
2021-10-20 22:20:43 +03:00
|
|
|
);
|
2021-09-08 12:42:30 +03:00
|
|
|
}
|