Used npm package for client and contract lib examples. (#350)

This commit is contained in:
Ravin Perera
2021-10-11 20:16:50 +05:30
committed by GitHub
parent cd00bab383
commit 9bc9404e07
19 changed files with 57 additions and 10835 deletions

View File

@@ -6,7 +6,6 @@ COPY package*.json ./
RUN npm install
COPY text-client.js ./
COPY hp-client-lib.js ./
ENTRYPOINT ["node", "text-client.js"]

View File

@@ -1,16 +0,0 @@
# Hot Pocket javascript client library and examples
Single-file javascript library to support json and bson protocols in NodeJs and Browser environments.
## NodeJs
1. Run `npm install` to install all the dependencies.
1. `lib/hp-client-lib.js` is the Hot Pocket client library for NodeJs.
1. `text-client.js` is the example for json mode.
1. `file-client.js` is the example for bson mode.
## Browser
1. Run `npm install` to install all the compilation dependencies.
1. Run `npm run build-browser` to produced the minified library for the browser.
1. `browser-example.html` is the simple html/javascript example for json mode.
(For BSON support in browser, a slightly modified version of https://www.npmjs.com/package/bson is used. The minified library includes this bson support library as well)

View File

@@ -3,8 +3,7 @@
<head>
<title>HotPocket browser example</title>
<!--'npm run build-browser' to generate the browser lib.-->
<script src="dist/hp-client-lib.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/hotpocket-js-client/browser.min.js"></script>
<!--Hot Pocket client library requires libsodium js for cryptographic operations.-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/libsodium-wrappers/0.5.4/sodium.min.js"

View File

@@ -2,7 +2,7 @@ const fs = require('fs');
const readline = require('readline');
const bson = require('bson');
var path = require("path");
const HotPocket = require('./lib/hp-client-lib');
const HotPocket = require('hotpocket-js-client');
async function main() {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,11 +2,6 @@
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -18,12 +13,11 @@
"integrity": "sha512-70hmx0lPd6zmtNwxPT4/1P0pqaEUlTJ0noUBvCXPLfMpN0o8PPaK3q7ZlpRIyhrqcXxeMAJSowNm/L9oi/x1XA=="
},
"bson": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.0.4.tgz",
"integrity": "sha512-Ioi3TD0/1V3aI8+hPfC56TetYmzfq2H07jJa9A1lKTxWsFtHtYdLMGMXjtGEg9v0f72NSM07diRQEUNYhLupIA==",
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.5.3.tgz",
"integrity": "sha512-qVX7LX79Mtj7B3NPLzCfBiCP6RAsjiV8N63DjlaVVpZW+PFoDTxQ4SeDbSpcqgE6mXksM5CAwZnXxxxn/XwC0g==",
"requires": {
"buffer": "^5.1.0",
"long": "^4.0.0"
"buffer": "^5.6.0"
}
},
"buffer": {
@@ -35,17 +29,39 @@
"ieee754": "^1.1.13"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
},
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
"hotpocket-js-client": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/hotpocket-js-client/-/hotpocket-js-client-0.5.1.tgz",
"integrity": "sha512-l11Fh6+Qia3gzk/latGf1ePoemIp/XO6L73mvo3b3XEyEF93Ew6+DxQzCsCYmXWEsSp5WgYsma5NOPvVMypDEw==",
"requires": {
"blake3": "2.1.4",
"bson": "4.5.3",
"libsodium-wrappers": "0.7.9",
"ws": "8.2.3"
},
"dependencies": {
"bson": {
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.5.3.tgz",
"integrity": "sha512-qVX7LX79Mtj7B3NPLzCfBiCP6RAsjiV8N63DjlaVVpZW+PFoDTxQ4SeDbSpcqgE6mXksM5CAwZnXxxxn/XwC0g==",
"requires": {
"buffer": "^5.6.0"
}
},
"libsodium-wrappers": {
"version": "0.7.9",
"resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.9.tgz",
"integrity": "sha512-9HaAeBGk1nKTRFRHkt7nzxqCvnkWTjn1pdjKgcUnZxj0FyOP4CnhgFhMdrFfgNsukijBGyBLpP2m2uKT1vuWhQ==",
"requires": {
"libsodium": "^0.7.0"
}
},
"ws": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA=="
}
}
},
"ieee754": {
"version": "1.2.1",
@@ -56,62 +72,6 @@
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.6.tgz",
"integrity": "sha512-hPb/04sEuLcTRdWDtd+xH3RXBihpmbPCsKW/Jtf4PsvdyKh+D6z2D2gvp/5BfoxseP+0FCOg66kE+0oGUE/loQ=="
},
"libsodium-wrappers": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.6.tgz",
"integrity": "sha512-OUO2CWW5bHdLr6hkKLHIKI4raEkZrf3QHkhXsJ1yCh6MZ3JDA7jFD3kCATNquuGSG6MjjPHQIQms0y0gBDzjQg==",
"requires": {
"libsodium": "0.7.6"
}
},
"long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
},
"source-map-support": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
},
"terser": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz",
"integrity": "sha512-HP5/9hp2UaZt5fYkuhNBR8YyRcT8juw8+uFbAme53iN9hblvKnLUTKkmwJG6ocWpIKf8UK4DoeWG4ty0J6S6/g==",
"dev": true,
"requires": {
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.19"
}
},
"ws": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz",
"integrity": "sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==",
"requires": {
"async-limiter": "^1.0.0"
}
}
}
}

View File

@@ -1,17 +1,6 @@
{
"scripts": {
"merge-browser": "rm -f -r dist ; mkdir -p dist && cp lib/bson-browser.js dist/hp-client-lib.js && cat lib/hp-client-lib.js >> dist/hp-client-lib.js",
"minify-browser": "terser --compress --mangle reserved=['Binary','Buffer'] -- dist/hp-client-lib.js > dist/hp-client-lib.min.js && rm dist/hp-client-lib.js",
"build-browser": "npm run merge-browser && npm run minify-browser",
"build-nodejs": "ncc build lib/hp-client-lib.js -o dist/hp-client"
},
"dependencies": {
"libsodium-wrappers": "0.7.6",
"blake3": "2.1.4",
"ws": "7.1.2",
"bson": "4.0.4"
},
"devDependencies": {
"terser": "5.7.0"
"hotpocket-js-client": "0.5.1",
"bson": "4.5.3"
}
}
}

View File

@@ -1,5 +1,5 @@
const readline = require('readline');
const HotPocket = require('./lib/hp-client-lib');
const HotPocket = require('hotpocket-js-client');
async function main() {

View File

@@ -1,4 +1,4 @@
const HotPocket = require("./hp-contract-lib");
const HotPocket = require("hotpocket-nodejs-contract");
const fs = require('fs').promises;
var seedrandom = require('seedrandom');

View File

@@ -1,4 +1,4 @@
const HotPocket = require("./hp-contract-lib");
const HotPocket = require("hotpocket-nodejs-contract");
const fs = require('fs');
const exectsFile = "exects.txt";

View File

@@ -1,4 +1,4 @@
const HotPocket = require("./hp-contract-lib");
const HotPocket = require("hotpocket-nodejs-contract");
const fs = require('fs');
const bson = require('bson');

View File

@@ -1,404 +0,0 @@
const fs = require('fs');
const tty = require('tty');
// Hot Pocket contract library version 0.5.0
const MAX_SEQ_PACKET_SIZE = 128 * 1024;
const controlMessages = {
contractEnd: "contract_end",
unlChangeset: "unl_changeset"
}
Object.freeze(controlMessages);
const clientProtocols = {
json: "json",
bson: "bson"
}
Object.freeze(clientProtocols);
const PATCH_CONFIG_PATH = "../patch.cfg";
const POST_EXEC_SCRIPT_NAME = "post_exec.sh";
/*
* Class members prefixed with '__' represent private members until ES spec fully supports '#' notation.
*/
class HotPocketContract {
constructor() {
this.__controlChannel = null;
this.__clientProtocol = null;
this.__executeContract = (hpargs, contractFunc) => {
// Keeps track of all the tasks (promises) that must be awaited before the termination.
const pendingTasks = [];
const nplChannel = new NplChannel(hpargs.npl_fd);
const users = new UsersCollection(hpargs.user_in_fd, hpargs.users, this.__clientProtocol);
const unl = new UnlCollection(hpargs.readonly, hpargs.unl, nplChannel, pendingTasks);
const executionContext = new ContractContext(hpargs, users, unl, this.__controlChannel);
invokeCallback(contractFunc, executionContext).catch(errHandler).finally(() => {
// Wait for any pending tasks added during execution.
Promise.all(pendingTasks).catch(errHandler).finally(() => {
nplChannel.close();
this.__terminate();
});
});
}
this.__terminate = () => {
this.__controlChannel.send({ type: controlMessages.contractEnd });
this.__controlChannel.close();
}
}
init(contractFunc, clientProtocol = clientProtocols.json) {
return new Promise(resolve => {
if (this.__controlChannel) { // Already initialized.
resolve(false);
return;
}
this.__clientProtocol = clientProtocol;
// Check whether we are running on a console and provide error.
if (tty.isatty(process.stdin.fd)) {
console.error("Error: Hot Pocket smart contracts must be executed via Hot Pocket.");
resolve(false);
return;
}
// Parse HotPocket args.
fs.readFile(process.stdin.fd, 'utf8', (err, argsJson) => {
const hpargs = JSON.parse(argsJson);
this.__controlChannel = new ControlChannel(hpargs.control_fd);
this.__executeContract(hpargs, contractFunc);
resolve(true);
});
});
}
}
class ContractContext {
constructor(hpargs, users, unl, controlChannel) {
this.__controlChannel = controlChannel;
this.readonly = hpargs.readonly;
this.timestamp = hpargs.timestamp;
this.users = users;
this.unl = unl; // Not available in readonly mode.
this.lcl_seq_no = hpargs.lcl_seq_no; // Not available in readonly mode.
this.lcl_hash = hpargs.lcl_hash; // Not available in readonly mode.
this.__patchConfig = new PatchConfig();
}
// Returns the config values in patch config.
getConfig() {
return this.__patchConfig.getConfig();
}
// Updates the config with given config object and save the patch config.
updateConfig(config) {
return this.__patchConfig.updateConfig(config);
}
}
// Handles patch config manipulation.
class PatchConfig {
// Loads the config value if there's a patch config file. Otherwise throw error.
getConfig() {
if (!fs.existsSync(PATCH_CONFIG_PATH))
throw "Patch config file does not exist.";
return new Promise((resolve, reject) => {
fs.readFile(PATCH_CONFIG_PATH, 'utf8', function (err, data) {
if (err) reject(err);
else resolve(JSON.parse(data));
});
});
}
updateConfig(config) {
this.validateConfig(config);
return new Promise((resolve, reject) => {
// Format json to match with the patch.cfg json format created by HP at the startup.
fs.writeFile(PATCH_CONFIG_PATH, JSON.stringify(config, null, 4), (err) => {
if (err) reject(err);
else resolve();
});
});
}
validateConfig(config) {
// Validate all config fields.
if (!config.version)
throw "Contract version is not specified.";
if (!config.unl || !config.unl.length)
throw "UNL list cannot be empty.";
for (let pubKey of config.unl) {
// Pubkeys are validated against length, ed prefix and hex characters.
if (!pubKey.length)
throw "UNL pubKey not specified.";
else if (!(/^(e|E)(d|D)[0-9a-fA-F]{64}$/g.test(pubKey)))
throw "Invalid UNL pubKey specified.";
}
if (!config.bin_path || !config.bin_path.length)
throw "Binary path cannot be empty.";
if (config.roundtime < 1 && config.roundtime > 3600000)
throw "Round time must be between 1 and 3600000ms inclusive.";
if (config.stage_slice < 1 || config.stage_slice > 33)
throw "Stage slice must be between 1 and 33 percent inclusive.";
if (config.consensus != "public" && config.consensus != "private")
throw "Invalid consensus flag configured in patch file. Valid values: public|private";
if (config.npl != "public" && config.npl != "private")
throw "Invalid npl flag configured in patch file. Valid values: public|private";
if (config.round_limits.user_input_bytes < 0 || config.round_limits.user_output_bytes < 0 || config.round_limits.npl_output_bytes < 0 ||
config.round_limits.proc_cpu_seconds < 0 || config.round_limits.proc_mem_bytes < 0 || config.round_limits.proc_ofd_count < 0)
throw "Invalid round limits.";
if (config.max_input_ledger_offset < 0)
throw "Invalid max input ledger offset";
}
}
class UsersCollection {
constructor(userInputsFd, usersObj, clientProtocol) {
this.__users = {};
this.__infd = userInputsFd;
Object.entries(usersObj).forEach(([pubKey, arr]) => {
const outfd = arr[0]; // First array element is the output fd.
arr.splice(0, 1); // Remove first element (output fd). The rest are pairs of msg offset/length tuples.
const channel = new UserChannel(outfd, clientProtocol);
this.__users[pubKey] = new User(pubKey, channel, arr);
});
}
// Returns the User for the specified pubkey. Returns null if not found.
find(pubKey) {
return this.__users[pubKey]
}
// Returns all the currently connected users.
list() {
return Object.values(this.__users);
}
count() {
return Object.keys(this.__users).length;
}
async read(input) {
const [offset, size] = input;
const buf = Buffer.alloc(size);
await readAsync(this.__infd, buf, offset, size);
return buf;
}
}
class User {
constructor(pubKey, channel, inputs) {
this.pubKey = pubKey;
this.inputs = inputs;
this.__channel = channel;
}
async send(msg) {
await this.__channel.send(msg);
}
}
class UserChannel {
constructor(outfd, clientProtocol) {
this.__outfd = outfd;
this.__clientProtocol = clientProtocol;
}
send(msg) {
const messageBuf = this.serialize(msg);
let headerBuf = Buffer.alloc(4);
// Writing message length in big endian format.
headerBuf.writeUInt32BE(messageBuf.byteLength)
return writevAsync(this.__outfd, [headerBuf, messageBuf]);
}
serialize(msg) {
if (!msg)
throw "Cannot serialize null content.";
if (Buffer.isBuffer(msg))
return msg;
if (this.__clientProtocol == clientProtocols.bson) {
return Buffer.from(msg);
}
else { // json
// In JSON, we need to ensure that the final buffer contains a string.
if (typeof msg === "string" || msg instanceof String)
return Buffer.from(msg);
else
return Buffer.from(JSON.stringify(msg));
}
}
}
class UnlCollection {
constructor(readonly, unl, channel, pendingTasks) {
this.nodes = {};
this.__readonly = readonly;
this.__pendingTasks = pendingTasks;
if (!readonly) {
unl.forEach(pubKey => {
this.nodes[pubKey] = new UnlNode(pubKey);
});
this.__channel = channel;
}
}
// Returns the unl node for the specified pubkey. Returns null if not found.
find(pubKey) {
return this.nodes[pubKey];
}
// Returns all the unl nodes.
list() {
return Object.values(this.nodes);
}
count() {
return Object.keys(this.nodes).length;
}
// Registers for NPL messages.
onMessage(callback) {
if (this.__readonly)
throw "NPL messages not available in readonly mode.";
this.__channel.consume((pubKey, msg) => {
this.__pendingTasks.push(invokeCallback(callback, this.nodes[pubKey], msg));
});
}
// Broadcasts a message to all unl nodes (including self if self is part of unl).
async send(msg) {
if (this.__readonly)
throw "NPL messages not available in readonly mode.";
await this.__channel.send(msg);
}
}
// Represents a node that's part of unl.
class UnlNode {
constructor(pubKey) {
this.pubKey = pubKey;
}
}
// Represents the node-party-line that can be used to communicate with unl nodes.
class NplChannel {
constructor(fd) {
this.__fd = fd;
this.__readStream = null;
}
consume(onMessage) {
this.__readStream = fs.createReadStream(null, { fd: this.__fd, highWaterMark: MAX_SEQ_PACKET_SIZE });
// From the hotpocket when sending the npl messages first it sends the pubkey of the particular node
// and then the message, First data buffer is taken as pubkey and the second one as message,
// then npl message object is constructed and the event is emmited.
let pubKey = null;
this.__readStream.on("data", (data) => {
if (!pubKey) {
pubKey = data.toString();
}
else {
onMessage(pubKey, data);
pubKey = null;
}
});
this.__readStream.on("error", (err) => { });
}
send(msg) {
const buf = Buffer.from(msg);
if (buf.length > MAX_SEQ_PACKET_SIZE)
throw ("NPL message exceeds max size " + MAX_SEQ_PACKET_SIZE);
return writeAsync(this.__fd, buf);
}
close() {
this.__readStream && this.__readStream.close();
}
}
class ControlChannel {
constructor(fd) {
this.__fd = fd;
this.__readStream = null;
}
consume(onMessage) {
this.__readStream = fs.createReadStream(null, { fd: this.__fd, highWaterMark: MAX_SEQ_PACKET_SIZE });
this.__readStream.on("data", onMessage);
this.__readStream.on("error", (err) => { });
}
send(obj) {
const buf = Buffer.from(JSON.stringify(obj));
if (buf.length > MAX_SEQ_PACKET_SIZE)
throw ("Control message exceeds max size " + MAX_SEQ_PACKET_SIZE);
return writeAsync(this.__fd, buf);
}
close() {
this.__readStream && this.__readStream.close();
}
}
const writeAsync = (fd, buf) => new Promise(resolve => fs.write(fd, buf, resolve));
const writevAsync = (fd, bufList) => new Promise(resolve => fs.writev(fd, bufList, resolve));
const readAsync = (fd, buf, offset, size) => new Promise(resolve => fs.read(fd, buf, 0, size, offset, resolve));
const invokeCallback = async (callback, ...args) => {
if (!callback)
return;
if (callback.constructor.name === 'AsyncFunction') {
await callback(...args).catch(errHandler);
}
else {
callback(...args);
}
}
const errHandler = (err) => console.log(err);
module.exports = {
Contract: HotPocketContract,
clientProtocols,
POST_EXEC_SCRIPT_NAME,
}

View File

@@ -25,6 +25,11 @@
"ieee754": "^1.1.4"
}
},
"hotpocket-nodejs-contract": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/hotpocket-nodejs-contract/-/hotpocket-nodejs-contract-0.5.0.tgz",
"integrity": "sha512-KSxlnmgu9AAZEChom/Q8NxAiTUE47xDGgt5eHMEWf0U+95ezr8yxLjZEyoIWrhFZGNMvjQ+7ITjMmskSF3onQA=="
},
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",

View File

@@ -5,6 +5,7 @@
"build-diag": "ncc build diagnostic_contract.js -o dist/diagnostic-contract"
},
"dependencies": {
"hotpocket-nodejs-contract": "0.5.0",
"bson": "4.0.4",
"seedrandom": "3.0.5"
}

View File

@@ -1,7 +1,7 @@
// HotPocket test client to collect metrics.
// This assumes the HotPocket server we are connecting to is hosting the echo contract.
const HotPocket = require('../../examples/js_client/lib/hp-client-lib');
const HotPocket = require('hotpocket-js-client');
let server = 'wss://localhost:8080';
if (process.argv.length == 3) server = 'wss://localhost:' + process.argv[2];

View File

@@ -1,5 +1,6 @@
{
"dependencies": {
"hotpocket-js-client": "0.5.1",
"libsodium-wrappers": "0.7.6",
"blake3": "2.1.4",
"ws": "7.1.2"

View File

@@ -1,9 +1,7 @@
{
"dependencies": {
"hotpocket-js-client": "0.5.1",
"azure-storage": "2.10.4",
"blake3": "2.1.4",
"libsodium-wrappers": "0.7.6",
"ws": "7.1.2",
"node-fetch": "2.6.1"
}
}
}

View File

@@ -1,4 +1,4 @@
const HotPocket = require('../../examples/js_client/lib/hp-client-lib');
const HotPocket = require('hotpocket-js-client');
const azure = require('azure-storage');
const fs = require('fs').promises;
const https = require('https');