Implement handlers for built-in calls from avm (#52)

This commit is contained in:
Pavel 2021-06-03 20:11:09 +03:00 committed by GitHub
parent c01c6478b7
commit 53385c556a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 289 additions and 63 deletions

127
package-lock.json generated
View File

@ -842,6 +842,15 @@
"@types/node": "*"
}
},
"@types/bs58": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz",
"integrity": "sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA==",
"dev": true,
"requires": {
"base-x": "^3.0.6"
}
},
"@types/graceful-fs": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
@ -3500,16 +3509,105 @@
}
},
"jest-each": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz",
"integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==",
"version": "27.0.2",
"resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.0.2.tgz",
"integrity": "sha512-OLMBZBZ6JkoXgUenDtseFRWA43wVl2BwmZYIWQws7eS7pqsIvePqj/jJmEnfq91ALk3LNphgwNK/PRFBYi7ITQ==",
"dev": true,
"requires": {
"@jest/types": "^26.6.2",
"@jest/types": "^27.0.2",
"chalk": "^4.0.0",
"jest-get-type": "^26.3.0",
"jest-util": "^26.6.2",
"pretty-format": "^26.6.2"
"jest-get-type": "^27.0.1",
"jest-util": "^27.0.2",
"pretty-format": "^27.0.2"
},
"dependencies": {
"@jest/types": {
"version": "27.0.2",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.2.tgz",
"integrity": "sha512-XpjCtJ/99HB4PmyJ2vgmN7vT+JLP7RW1FBT9RgnMFS4Dt7cvIyBee8O3/j98aUZ34ZpenPZFqmaaObWSeL65dg==",
"dev": true,
"requires": {
"@types/istanbul-lib-coverage": "^2.0.0",
"@types/istanbul-reports": "^3.0.0",
"@types/node": "*",
"@types/yargs": "^16.0.0",
"chalk": "^4.0.0"
}
},
"@types/yargs": {
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz",
"integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
}
},
"ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true
},
"ci-info": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz",
"integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==",
"dev": true
},
"graceful-fs": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
"integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
"dev": true
},
"is-ci": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz",
"integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==",
"dev": true,
"requires": {
"ci-info": "^3.1.1"
}
},
"jest-get-type": {
"version": "27.0.1",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz",
"integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==",
"dev": true
},
"jest-util": {
"version": "27.0.2",
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.0.2.tgz",
"integrity": "sha512-1d9uH3a00OFGGWSibpNYr+jojZ6AckOMCXV2Z4K3YXDnzpkAaXQyIpY14FOJPiUmil7CD+A6Qs+lnnh6ctRbIA==",
"dev": true,
"requires": {
"@jest/types": "^27.0.2",
"@types/node": "*",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.4",
"is-ci": "^3.0.0",
"picomatch": "^2.2.3"
}
},
"picomatch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
"dev": true
},
"pretty-format": {
"version": "27.0.2",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.2.tgz",
"integrity": "sha512-mXKbbBPnYTG7Yra9qFBtqj+IXcsvxsvOBco3QHxtxTl+hHKq6QdzMZ+q0CtL4ORHZgwGImRr2XZUX2EWzORxig==",
"dev": true,
"requires": {
"@jest/types": "^27.0.2",
"ansi-regex": "^5.0.0",
"ansi-styles": "^5.0.0",
"react-is": "^17.0.1"
}
}
}
},
"jest-environment-jsdom": {
@ -3601,6 +3699,21 @@
"jest-util": "^26.6.2",
"pretty-format": "^26.6.2",
"throat": "^5.0.0"
},
"dependencies": {
"jest-each": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz",
"integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==",
"dev": true,
"requires": {
"@jest/types": "^26.6.2",
"chalk": "^4.0.0",
"jest-get-type": "^26.3.0",
"jest-util": "^26.6.2",
"pretty-format": "^26.6.2"
}
}
}
},
"jest-leak-detector": {

View File

@ -37,8 +37,10 @@
"uuid": "8.3.0"
},
"devDependencies": {
"@types/bs58": "^4.0.1",
"@types/jest": "^26.0.22",
"jest": "^26.6.3",
"jest-each": "^27.0.2",
"ts-jest": "^26.5.4",
"typescript": "^3.9.5"
}

View File

@ -133,14 +133,14 @@ export const checkConnection = async (client: FluenceClient, ttl?: number): Prom
.withVariables({
msg,
})
.buildAsFetch<[[string]]>(callbackService, callbackFn);
.buildAsFetch<[string]>(callbackService, callbackFn);
await client.initiateFlow(request);
try {
const [[result]] = await promise;
const [result] = await promise;
if (result != msg) {
log.warn("unexpected behavior. 'identity' must return arguments the passed arguments.");
log.warn("unexpected behavior. 'identity' must return the passed arguments.");
}
return true;
} catch (e) {

View File

@ -26,11 +26,11 @@ describe('Typescript usage suite', () => {
(call %init_peer_id% ("callback" "callback") [result])
)`,
)
.buildAsFetch<[[string]]>('callback', 'callback');
.buildAsFetch<[string]>('callback', 'callback');
await client.initiateFlow(request);
// assert
const [[result]] = await promise;
const [result] = await promise;
expect(result).toBe('hello world!');
});

View File

@ -0,0 +1,60 @@
import each from 'jest-each';
import { CallServiceData } from '../../internal/CallServiceHandler';
import makeDefaultClientHandler from '../../internal/defaultClientHandler';
describe('Tests for default handler', () => {
// prettier-ignore
each`
fnName | args | retCode | result
${'identity'} | ${[]} | ${0} | ${{}}
${'identity'} | ${[1]} | ${0} | ${1}
${'identity'} | ${[1, 2]} | ${1} | ${'identity accepts up to 1 arguments, received 2 arguments'}
${'noop'} | ${[1, 2]} | ${0} | ${{}}
${'array'} | ${[1, 2, 3]} | ${0} | ${[1, 2, 3]}
${'concat'} | ${[[1, 2], [3, 4], [5, 6]]} | ${0} | ${[1, 2, 3, 4, 5, 6]}
${'concat'} | ${[[1, 2]]} | ${0} | ${[1, 2]}
${'concat'} | ${[]} | ${0} | ${[]}
${'concat'} | ${[1, [1, 2], 1]} | ${1} | ${"All arguments of 'concat' must be arrays: arguments 0, 2 are not"}
${'string_to_b58'} | ${["test"]} | ${0} | ${"3yZe7d"}
${'string_to_b58'} | ${["test", 1]} | ${1} | ${"string_to_b58 accepts only one string argument"}
${'string_from_b58'} | ${["3yZe7d"]} | ${0} | ${"test"}
${'string_from_b58'} | ${["3yZe7d", 1]} | ${1} | ${"string_from_b58 accepts only one string argument"}
${'bytes_to_b58'} | ${[[116, 101, 115, 116]]} | ${0} | ${"3yZe7d"}
${'bytes_to_b58'} | ${[[116, 101, 115, 116], 1]} | ${1} | ${"bytes_to_b58 accepts only single argument: array of numbers"}
${'bytes_from_b58'} | ${["3yZe7d"]} | ${0} | ${[116, 101, 115, 116]}
${'bytes_from_b58'} | ${["3yZe7d", 1]} | ${1} | ${"bytes_from_b58 accepts only one string argument"}
`.test(
//
'$fnName with $args expected retcode: $retCode and result: $result',
({ fnName, args, retCode, result }) => {
// arrange
const req: CallServiceData = {
serviceId: 'Op',
fnName: fnName,
args: args,
tetraplets: [],
particleContext: {
particleId: 'some',
},
};
// act
const res = makeDefaultClientHandler().execute(req);
// assert
expect(res).toMatchObject({
retCode: retCode,
result: result,
});
const handler = makeDefaultClientHandler();
},
);
});

View File

@ -21,7 +21,7 @@ interface ParticleContext {
/**
* Represents the information passed from AVM when a `call` air instruction is executed on the local peer
*/
interface CallServiceData {
export interface CallServiceData {
/**
* Service ID as specified in `call` air instruction
*/
@ -58,7 +58,7 @@ export type CallServiceResultType = object | boolean | number | string;
/**
* Represents the result of the `call` air instruction to be returned into AVM
*/
interface CallServiceResult {
export interface CallServiceResult {
/**
* Return code to be returned to AVM
*/

View File

@ -21,18 +21,12 @@ import { FluenceConnection, FluenceConnectionOptions } from './FluenceConnection
import { PeerIdB58 } from './commonTypes';
import { FluenceClient } from '../FluenceClient';
import { RequestFlow } from './RequestFlow';
import { CallServiceHandler, errorHandler, fnHandler } from './CallServiceHandler';
import { CallServiceHandler } from './CallServiceHandler';
import { loadRelayFn, loadVariablesService } from './RequestFlowBuilder';
import { logParticle, Particle } from './particle';
import log from 'loglevel';
import { AirInterpreter, CallServiceResult, ParticleHandler, SecurityTetraplet } from '@fluencelabs/avm';
const makeDefaultClientHandler = (): CallServiceHandler => {
const res = new CallServiceHandler();
res.use(errorHandler);
res.use(fnHandler('op', 'identity', (args, _) => args));
return res;
};
import { AirInterpreter, ParticleHandler, SecurityTetraplet, CallServiceResult } from '@fluencelabs/avm';
import makeDefaultClientHandler from './defaultClientHandler';
export class ClientImpl implements FluenceClient {
readonly selfPeerIdFull: PeerId;

View File

@ -0,0 +1,97 @@
import { encode, decode } from 'bs58';
import {
CallServiceData,
CallServiceHandler,
CallServiceResult,
CallServiceResultType,
errorHandler,
Middleware,
} from './CallServiceHandler';
const makeDefaultClientHandler = (): CallServiceHandler => {
const success = (resp: CallServiceResult, result: CallServiceResultType) => {
resp.retCode = 0;
resp.result = result;
};
const error = (resp: CallServiceResult, errorMsg: string) => {
resp.retCode = 1;
resp.result = errorMsg;
};
const mw: Middleware = (req: CallServiceData, resp: CallServiceResult, next: Function) => {
if (req.serviceId === 'Op') {
switch (req.fnName) {
case 'noop':
success(resp, {});
return;
case 'array':
success(resp, req.args);
return;
case 'identity':
if (req.args.length > 1) {
error(resp, `identity accepts up to 1 arguments, received ${req.args.length} arguments`);
} else {
success(resp, req.args.length === 0 ? {} : req.args[0]);
}
return;
case 'concat':
const incorrectArgIndices = req.args //
.map((x, i) => [Array.isArray(x), i])
.filter(([isArray, _]) => !isArray)
.map(([_, index]) => index);
if (incorrectArgIndices.length > 0) {
const str = incorrectArgIndices.join(', ');
error(resp, `All arguments of 'concat' must be arrays: arguments ${str} are not`);
} else {
success(resp, [].concat.apply([], req.args));
}
return;
case 'string_to_b58':
if (req.args.length !== 1) {
error(resp, 'string_to_b58 accepts only one string argument');
} else {
success(resp, encode(new TextEncoder().encode(req.args[0])));
}
return;
case 'string_from_b58':
if (req.args.length !== 1) {
error(resp, 'string_from_b58 accepts only one string argument');
} else {
success(resp, new TextDecoder().decode(decode(req.args[0])));
}
return;
case 'bytes_to_b58':
if (req.args.length !== 1 || !Array.isArray(req.args[0])) {
error(resp, 'bytes_to_b58 accepts only single argument: array of numbers');
} else {
const argumentArray = req.args[0] as number[];
success(resp, encode(new Uint8Array(argumentArray)));
}
return;
case 'bytes_from_b58':
if (req.args.length !== 1) {
error(resp, 'bytes_from_b58 accepts only one string argument');
} else {
success(resp, Array.from(decode(req.args[0])));
}
return;
}
}
next();
};
const res = new CallServiceHandler();
res.use(errorHandler);
res.use(mw);
return res;
};
export default makeDefaultClientHandler;

View File

@ -3,7 +3,6 @@
"typeRoots": [
"./node_modules/@types",
"./node_modules/libp2p-ts/types",
"./types"
],
"outDir": "./dist/",
"baseUrl": ".",

View File

@ -1,19 +0,0 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
declare module 'ipfs-only-hash' {
export function of(data: Buffer): Promise<string>
}

View File

@ -1,20 +0,0 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
declare module 'it-length-prefixed' {
export function decode(): any
export function encode(): any
}