feat(avm-client): implement pluggable formats in JS (#776)

For parsing and producing call requests and call results in
AquaVM-compatible way with JSON and MessagePack.

Multicodec representation is also supported, both JSON and MessagePack
can be used as input.
---------

Co-authored-by: Akim <59872966+akim-bow@users.noreply.github.com>
This commit is contained in:
Ivan Boldyrev 2023-12-25 18:54:26 +04:00 committed by GitHub
parent 674108506b
commit eaa40778c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 262 additions and 14 deletions

View File

@ -8,10 +8,77 @@
"name": "@fluencelabs/avm",
"version": "0.55.0",
"license": "Apache 2.0",
"dependencies": {
"msgpack-lite": "^0.1.26",
"multicodec": "^3.2.1"
},
"devDependencies": {
"typescript": "5.2.2"
}
},
"node_modules/event-lite": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/event-lite/-/event-lite-0.1.3.tgz",
"integrity": "sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw=="
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/int64-buffer": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-0.1.10.tgz",
"integrity": "sha512-v7cSY1J8ydZ0GyjUHqF+1bshJ6cnEVLo9EnjB8p+4HDRPZc9N5jjmvUV7NvEsqQOKyH0pmIBFWXVQbiS0+OBbA=="
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
},
"node_modules/msgpack-lite": {
"version": "0.1.26",
"resolved": "https://registry.npmjs.org/msgpack-lite/-/msgpack-lite-0.1.26.tgz",
"integrity": "sha512-SZ2IxeqZ1oRFGo0xFGbvBJWMp3yLIY9rlIJyxy8CGrwZn1f0ZK4r6jV/AM1r0FZMDUkWkglOk/eeKIL9g77Nxw==",
"dependencies": {
"event-lite": "^0.1.1",
"ieee754": "^1.1.8",
"int64-buffer": "^0.1.9",
"isarray": "^1.0.0"
},
"bin": {
"msgpack": "bin/msgpack"
}
},
"node_modules/multicodec": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/multicodec/-/multicodec-3.2.1.tgz",
"integrity": "sha512-+expTPftro8VAW8kfvcuNNNBgb9gPeNYV9dn+z1kJRWF2vih+/S79f2RVeIwmrJBUJ6NT9IUPWnZDQvegEh5pw==",
"deprecated": "This module has been superseded by the multiformats module",
"dependencies": {
"uint8arrays": "^3.0.0",
"varint": "^6.0.0"
}
},
"node_modules/multiformats": {
"version": "9.9.0",
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="
},
"node_modules/typescript": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
@ -24,14 +91,85 @@
"engines": {
"node": ">=14.17"
}
},
"node_modules/uint8arrays": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz",
"integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==",
"dependencies": {
"multiformats": "^9.4.2"
}
},
"node_modules/varint": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="
}
},
"dependencies": {
"event-lite": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/event-lite/-/event-lite-0.1.3.tgz",
"integrity": "sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw=="
},
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
},
"int64-buffer": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-0.1.10.tgz",
"integrity": "sha512-v7cSY1J8ydZ0GyjUHqF+1bshJ6cnEVLo9EnjB8p+4HDRPZc9N5jjmvUV7NvEsqQOKyH0pmIBFWXVQbiS0+OBbA=="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
},
"msgpack-lite": {
"version": "0.1.26",
"resolved": "https://registry.npmjs.org/msgpack-lite/-/msgpack-lite-0.1.26.tgz",
"integrity": "sha512-SZ2IxeqZ1oRFGo0xFGbvBJWMp3yLIY9rlIJyxy8CGrwZn1f0ZK4r6jV/AM1r0FZMDUkWkglOk/eeKIL9g77Nxw==",
"requires": {
"event-lite": "^0.1.1",
"ieee754": "^1.1.8",
"int64-buffer": "^0.1.9",
"isarray": "^1.0.0"
}
},
"multicodec": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/multicodec/-/multicodec-3.2.1.tgz",
"integrity": "sha512-+expTPftro8VAW8kfvcuNNNBgb9gPeNYV9dn+z1kJRWF2vih+/S79f2RVeIwmrJBUJ6NT9IUPWnZDQvegEh5pw==",
"requires": {
"uint8arrays": "^3.0.0",
"varint": "^6.0.0"
}
},
"multiformats": {
"version": "9.9.0",
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="
},
"typescript": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true
},
"uint8arrays": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz",
"integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==",
"requires": {
"multiformats": "^9.4.2"
}
},
"varint": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="
}
}
}

View File

@ -13,7 +13,10 @@
"build": "tsc && ./build_wasm.sh"
},
"private": false,
"dependencies": {},
"dependencies": {
"msgpack-lite": "^0.1.26",
"multicodec": "^3.2.1"
},
"devDependencies": {
"typescript": "5.2.2"
}

View File

@ -15,9 +15,16 @@
*/
import { CallResultsArray, InterpreterResult, CallRequest, RunParameters, JSONArray, JSONObject } from './types';
import { JsonRepr } from './formats'
const decoder = new TextDecoder();
const encoder = new TextEncoder();
// Have to match the air-interpreter-interface.
const callRequestsRepr = new JsonRepr();
// Have to match the air-interpreter-interface.
const argumentRepr = new JsonRepr();
// Have to match the air-interpreter-interface.
const tetrapletRepr = new JsonRepr();
// Have to match the air-interpreter-interface.
const callResultsRepr = new JsonRepr();
/**
* Encodes arguments into JSON array suitable for marine-js
@ -44,7 +51,7 @@ export function serializeAvmArgs(
};
}
const encoded = encoder.encode(JSON.stringify(callResultsToPass));
const encodedCallResults = callResultsRepr.toBinary(callResultsToPass)
const runParamsSnakeCase = {
init_peer_id: runParams.initPeerId,
current_peer_id: runParams.currentPeerId,
@ -55,7 +62,7 @@ export function serializeAvmArgs(
particle_id: runParams.particleId,
};
return [air, Array.from(prevData), Array.from(data), runParamsSnakeCase, Array.from(encoded)];
return [air, Array.from(prevData), Array.from(data), runParamsSnakeCase, Array.from(encodedCallResults)];
}
/**
@ -64,16 +71,16 @@ export function serializeAvmArgs(
* @returns structured InterpreterResult
*/
export function deserializeAvmResult(result: any): InterpreterResult {
const callRequestsStr = decoder.decode(new Uint8Array(result.call_requests));
let parsedCallRequests;
const callRequestsBuf = new Uint8Array(result.call_requests);
let parsedCallRequests: object;
try {
if (callRequestsStr.length === 0) {
if (callRequestsBuf.length === 0) {
parsedCallRequests = {};
} else {
parsedCallRequests = JSON.parse(callRequestsStr);
parsedCallRequests = callRequestsRepr.fromBinary(callRequestsBuf);
}
} catch (e) {
throw "Couldn't parse call requests: " + e + '. Original string is: ' + callRequestsStr;
throw "Couldn't parse call requests: " + e + '. Original data is: ' + result.call_requests;
}
let resultCallRequests: Array<[key: number, callRequest: CallRequest]> = [];
@ -83,15 +90,15 @@ export function deserializeAvmResult(result: any): InterpreterResult {
let arguments_;
let tetraplets;
try {
const argumentsStr = decoder.decode(new Uint8Array(callRequest.arguments));
arguments_ = JSON.parse(argumentsStr);
let argumentsBuf = new Uint8Array(callRequest.arguments);
arguments_ = argumentRepr.fromBinary(argumentsBuf);
} catch (e) {
throw "Couldn't parse arguments: " + e + '. Original data is: ' + callRequest.arguments;
}
try {
const tetrapletsStr = decoder.decode(new Uint8Array(callRequest.tetraplets));
tetraplets = JSON.parse(tetrapletsStr);
let tetrapletBuf = new Uint8Array(callRequest.tetraplets);
tetraplets = tetrapletRepr.fromBinary(tetrapletBuf);
} catch (e) {
throw "Couldn't parse tetraplets: " + e + '. Original data is: ' + callRequest.tetraplets;
}

99
avm/client/src/formats.ts Normal file
View File

@ -0,0 +1,99 @@
/*
* 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.
*/
import msgpack from "msgpack-lite"
import multicodec from "multicodec"
const decoder = new TextDecoder();
const encoder = new TextEncoder();
interface Representation {
fromBinary(data: Uint8Array): object
toBinary(obj: object): Uint8Array
}
interface Multiformatable {
get_code(): multicodec.CodecCode
}
/**
* Simple JSON representation.
*/
export class JsonRepr implements Representation, Multiformatable {
fromBinary(data: Uint8Array): object {
let dataStr = decoder.decode(data)
return JSON.parse(dataStr);
}
toBinary(obj: object): Uint8Array {
return encoder.encode(JSON.stringify(obj))
}
get_code(): multicodec.CodecCode {
return multicodec.JSON
}
}
/**
* Simple MessagePack representation.
*/
export class MsgPackRepr implements Representation, Multiformatable {
fromBinary(data: Uint8Array): object {
return msgpack.decode(data)
}
toBinary(obj: object): Uint8Array {
return msgpack.encode(obj)
}
get_code(): multicodec.CodecCode {
return multicodec.MESSAGEPACK
}
}
/**
* Multicodec representation that supports both JSON and MsgPack, but uses only specific representation for encoding.
*/
export class MulticodecRepr implements Representation {
serializer: Representation & Multiformatable
constructor(serializer: Representation & Multiformatable) {
this.serializer = serializer
}
fromBinary(data: Uint8Array): object {
let code = multicodec.getCodeFromData(data)
let repr: Representation | null = null;
if (code == multicodec.JSON) {
repr = new JsonRepr()
} else if (code == multicodec.MESSAGEPACK) {
repr = new MsgPackRepr()
}
if (repr === null) {
throw "Unknown code " + code + "in multiformat data " + data
}
return repr.fromBinary(multicodec.rmPrefix(data))
}
toBinary(obj: object): Uint8Array {
let bareData = this.serializer.toBinary(obj);
let varintCode = multicodec.getVarintFromCode(this.serializer.get_code());
return multicodec.addPrefix(varintCode, bareData)
}
}

View File

@ -16,3 +16,4 @@
export * from './types';
export * from './avmHelpers';
export * from './formats';