Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

smoldot

Package Overview
Dependencies
Maintainers
1
Versions
104
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

smoldot - npm Package Compare versions

Comparing version 1.0.3 to 1.0.4

2

dist/cjs/client.d.ts

@@ -320,2 +320,2 @@ import { PlatformBindings } from './instance/instance.js';

}
export declare function start(options: ClientOptions, platformBindings: PlatformBindings): Client;
export declare function start(options: ClientOptions, wasmModule: Promise<WebAssembly.Module>, platformBindings: PlatformBindings): Client;

@@ -65,3 +65,3 @@ "use strict";

// Contrary to the one within `index.js`, this function is not supposed to be directly used.
function start(options, platformBindings) {
function start(options, wasmModule, platformBindings) {
const logCallback = options.logCallback || ((level, target, message) => {

@@ -95,2 +95,3 @@ // The first parameter of the methods of `console` has some printf-like substitution

const instance = (0, instance_js_1.start)({
wasmModule,
// Maximum level of log entries sent by the client.

@@ -100,6 +101,2 @@ // 0 = Logging disabled, 1 = Error, 2 = Warn, 3 = Info, 4 = Debug, 5 = Trace

logCallback,
// `enableCurrentTask` adds a small performance hit, but adds some additional information to
// crash reports. Whether this should be enabled is very opiniated and not that important. At
// the moment, we enable it all the time, except if the user has logging disabled altogether.
enableCurrentTask: options.maxLogLevel ? options.maxLogLevel >= 1 : true,
cpuRateLimit: options.cpuRateLimit || 1.0,

@@ -106,0 +103,0 @@ }, platformBindings);

@@ -31,2 +31,3 @@ "use strict";

const pako_1 = require("pako");
const wasm_js_1 = require("./instance/autogen/wasm.js");
var client_js_2 = require("./client.js");

@@ -48,6 +49,8 @@ Object.defineProperty(exports, "AddChainError", { enumerable: true, get: function () { return client_js_2.AddChainError; } });

options = options || {};
return (0, client_js_1.start)(options, {
trustedBase64DecodeAndZlibInflate: (input) => {
return Promise.resolve((0, pako_1.inflate)((0, base64_js_1.classicDecode)(input)));
},
// The actual Wasm bytecode is base64-decoded then deflate-decoded from a constant found in a
// different file.
// This is suboptimal compared to using `instantiateStreaming`, but it is the most
// cross-platform cross-bundler approach.
const wasmModule = WebAssembly.compile((0, pako_1.inflate)((0, base64_js_1.classicDecode)(wasm_js_1.default)));
return (0, client_js_1.start)(options, wasmModule, {
registerShouldPeriodicallyYield: (callback) => {

@@ -67,3 +70,12 @@ if (typeof document === 'undefined') // We might be in a web worker.

throw new Error('randomness not available');
crypto.getRandomValues(buffer);
// Browsers have this completely undocumented behavior (it's not even part of a spec)
// that for some reason `getRandomValues` can't be called on arrayviews back by
// `SharedArrayBuffer`s and they throw an exception if you try.
if (buffer.buffer instanceof ArrayBuffer)
crypto.getRandomValues(buffer);
else {
const tmpArray = new Uint8Array(buffer.length);
crypto.getRandomValues(tmpArray);
buffer.set(tmpArray);
}
},

@@ -70,0 +82,0 @@ connect: (config) => {

@@ -28,2 +28,3 @@ "use strict";

const instance_js_1 = require("./instance/instance.js");
const wasm_js_1 = require("./instance/autogen/wasm.js");
var client_js_2 = require("./client.js");

@@ -45,29 +46,8 @@ Object.defineProperty(exports, "AddChainError", { enumerable: true, get: function () { return client_js_2.AddChainError; } });

options = options || {};
return (0, client_js_1.start)(options || {}, {
trustedBase64DecodeAndZlibInflate: (input) => __awaiter(this, void 0, void 0, function* () {
const buffer = trustedBase64Decode(input);
// This code has been copy-pasted from the official streams draft specification.
// At the moment, it is found here: https://wicg.github.io/compression/#example-deflate-compress
const ds = new DecompressionStream('deflate');
const writer = ds.writable.getWriter();
writer.write(buffer);
writer.close();
const output = [];
const reader = ds.readable.getReader();
let totalSize = 0;
while (true) {
const { value, done } = yield reader.read();
if (done)
break;
output.push(value);
totalSize += value.byteLength;
}
const concatenated = new Uint8Array(totalSize);
let offset = 0;
for (const array of output) {
concatenated.set(array, offset);
offset += array.byteLength;
}
return concatenated;
}),
// The actual Wasm bytecode is base64-decoded then deflate-decoded from a constant found in a
// different file.
// This is suboptimal compared to using `instantiateStreaming`, but it is the most
// cross-platform cross-bundler approach.
const wasmModule = zlibInflate(trustedBase64Decode(wasm_js_1.default)).then(((bytecode) => WebAssembly.compile(bytecode)));
return (0, client_js_1.start)(options || {}, wasmModule, {
registerShouldPeriodicallyYield: (_callback) => {

@@ -92,2 +72,32 @@ return [true, () => { }];

/**
* Applies the zlib inflate algorithm on the buffer.
*/
function zlibInflate(buffer) {
return __awaiter(this, void 0, void 0, function* () {
// This code has been copy-pasted from the official streams draft specification.
// At the moment, it is found here: https://wicg.github.io/compression/#example-deflate-compress
const ds = new DecompressionStream('deflate');
const writer = ds.writable.getWriter();
writer.write(buffer);
writer.close();
const output = [];
const reader = ds.readable.getReader();
let totalSize = 0;
while (true) {
const { value, done } = yield reader.read();
if (done)
break;
output.push(value);
totalSize += value.byteLength;
}
const concatenated = new Uint8Array(totalSize);
let offset = 0;
for (const array of output) {
concatenated.set(array, offset);
offset += array.byteLength;
}
return concatenated;
});
}
/**
* Decodes a base64 string.

@@ -243,3 +253,3 @@ *

});
read(new Uint8Array(1024));
read(new Uint8Array(32768));
return established;

@@ -246,0 +256,0 @@ });

@@ -22,2 +22,3 @@ "use strict";

const instance_js_1 = require("./instance/instance.js");
const wasm_js_1 = require("./instance/autogen/wasm.js");
const ws_1 = require("ws");

@@ -44,6 +45,8 @@ const pako_1 = require("pako");

options = options || {};
return (0, client_js_1.start)(options || {}, {
trustedBase64DecodeAndZlibInflate: (input) => {
return Promise.resolve((0, pako_1.inflate)(Buffer.from(input, 'base64')));
},
// The actual Wasm bytecode is base64-decoded then deflate-decoded from a constant found in a
// different file.
// This is suboptimal compared to using `instantiateStreaming`, but it is the most
// cross-platform cross-bundler approach.
const wasmModule = WebAssembly.compile((0, pako_1.inflate)(Buffer.from(wasm_js_1.default, 'base64')));
return (0, client_js_1.start)(options || {}, wasmModule, {
registerShouldPeriodicallyYield: (_callback) => {

@@ -50,0 +53,0 @@ return [true, () => { }];

@@ -25,2 +25,3 @@ import type { SmoldotWasmInstance } from './bindings.js';

jsonRpcResponsesNonEmptyCallback: (chainId: number) => void;
advanceExecutionReadyCallback: () => void;
currentTaskCallback?: (taskName: string | null) => void;

@@ -27,0 +28,0 @@ }

@@ -68,2 +68,5 @@ "use strict";

},
advance_execution_ready: () => {
config.advanceExecutionReadyCallback();
},
// Used by the Rust side to notify that a JSON-RPC response or subscription notification

@@ -94,3 +97,3 @@ // is available in the queue of JSON-RPC responses.

// Must call `timer_finished` after the given number of milliseconds has elapsed.
start_timer: (id, ms) => {
start_timer: (ms) => {
if (killedTracked.killed)

@@ -113,3 +116,3 @@ return;

try {
instance.exports.timer_finished(id);
instance.exports.timer_finished();
}

@@ -124,3 +127,3 @@ catch (_error) { }

try {
instance.exports.timer_finished(id);
instance.exports.timer_finished();
}

@@ -127,0 +130,0 @@ catch (_error) { }

@@ -9,4 +9,4 @@ /**

memory: WebAssembly.Memory;
init: (maxLogLevel: number, enableCurrentTask: number, cpuRateLimit: number, periodicallyYield: number) => void;
set_periodically_yield: (periodicallyYield: number) => void;
init: (maxLogLevel: number) => void;
advance_execution: () => number;
start_shutdown: () => void;

@@ -21,3 +21,3 @@ add_chain: (chainSpecBufferIndex: number, databaseContentBufferIndex: number, jsonRpcRunning: number, potentialRelayChainsBufferIndex: number) => number;

json_rpc_responses_pop: (chainId: number) => void;
timer_finished: (timerId: number) => void;
timer_finished: () => void;
connection_open_single_stream: (connectionId: number, handshakeTy: number, initialWritableBytes: number, writeClosable: number) => void;

@@ -24,0 +24,0 @@ connection_open_multi_stream: (connectionId: number, handshakeTyBufferIndex: number) => void;

@@ -27,5 +27,5 @@ import * as instance from './raw-instance.js';

export interface Config {
wasmModule: Promise<WebAssembly.Module>;
logCallback: (level: number, target: string, message: string) => void;
maxLogLevel: number;
enableCurrentTask: boolean;
cpuRateLimit: number;

@@ -32,0 +32,0 @@ }

@@ -72,61 +72,45 @@ "use strict";

let chains = new Map();
// Start initialization of the Wasm VM.
const config = {
onWasmPanic: (message) => {
// TODO: consider obtaining a backtrace here
crashError.error = new CrashError(message);
if (!printError.printError)
return;
console.error("Smoldot has panicked" +
(currentTask.name ? (" while executing task `" + currentTask.name + "`") : "") +
". This is a bug in smoldot. Please open an issue at " +
"https://github.com/smol-dot/smoldot/issues with the following message:\n" +
message);
for (const chain of Array.from(chains.values())) {
for (const promise of chain.jsonRpcResponsesPromises) {
promise.reject(crashError.error);
const initPromise = (() => __awaiter(this, void 0, void 0, function* () {
const module = yield configMessage.wasmModule;
// Start initialization of the Wasm VM.
const config = {
onWasmPanic: (message) => {
// TODO: consider obtaining a backtrace here
crashError.error = new CrashError(message);
if (!printError.printError)
return;
console.error("Smoldot has panicked" +
(currentTask.name ? (" while executing task `" + currentTask.name + "`") : "") +
". This is a bug in smoldot. Please open an issue at " +
"https://github.com/smol-dot/smoldot/issues with the following message:\n" +
message);
for (const chain of Array.from(chains.values())) {
for (const promise of chain.jsonRpcResponsesPromises) {
promise.reject(crashError.error);
}
chain.jsonRpcResponsesPromises = [];
}
chain.jsonRpcResponsesPromises = [];
}
},
logCallback: (level, target, message) => {
configMessage.logCallback(level, target, message);
},
jsonRpcResponsesNonEmptyCallback: (chainId) => {
// Notify every single promise found in `jsonRpcResponsesPromises`.
const promises = chains.get(chainId).jsonRpcResponsesPromises;
while (promises.length !== 0) {
promises.shift().resolve();
}
},
currentTaskCallback: (taskName) => {
currentTask.name = taskName;
},
cpuRateLimit: configMessage.cpuRateLimit,
};
},
logCallback: (level, target, message) => {
configMessage.logCallback(level, target, message);
},
wasmModule: module,
jsonRpcResponsesNonEmptyCallback: (chainId) => {
// Notify every single promise found in `jsonRpcResponsesPromises`.
const promises = chains.get(chainId).jsonRpcResponsesPromises;
while (promises.length !== 0) {
promises.shift().resolve();
}
},
currentTaskCallback: (taskName) => {
currentTask.name = taskName;
},
cpuRateLimit: configMessage.cpuRateLimit,
maxLogLevel: configMessage.maxLogLevel,
};
return yield instance.startInstance(config, platformBindings);
}))();
state = {
initialized: false, promise: instance.startInstance(config, platformBindings).then(([instance, bufferIndices]) => {
// `config.cpuRateLimit` is a floating point that should be between 0 and 1, while the value
// to pass as parameter must be between `0` and `2^32-1`.
// The few lines of code below should handle all possible values of `number`, including
// infinites and NaN.
let cpuRateLimit = Math.round(config.cpuRateLimit * 4294967295); // `2^32 - 1`
if (cpuRateLimit < 0)
cpuRateLimit = 0;
if (cpuRateLimit > 4294967295)
cpuRateLimit = 4294967295;
if (!Number.isFinite(cpuRateLimit))
cpuRateLimit = 4294967295; // User might have passed NaN
// Smoldot requires an initial call to the `init` function in order to do its internal
// configuration.
const [periodicallyYield, unregisterCallback] = platformBindings.registerShouldPeriodicallyYield((newValue) => {
if (state.initialized && !crashError.error) {
try {
state.instance.exports.set_periodically_yield(newValue ? 1 : 0);
}
catch (_error) { }
}
});
instance.exports.init(configMessage.maxLogLevel, configMessage.enableCurrentTask ? 1 : 0, cpuRateLimit, periodicallyYield ? 1 : 0);
state = { initialized: true, instance, bufferIndices, unregisterCallback };
initialized: false, promise: initPromise.then(([instance, bufferIndices]) => {
state = { initialized: true, instance, bufferIndices };
return [instance, bufferIndices];

@@ -284,4 +268,2 @@ })

return;
if (state.initialized)
state.unregisterCallback();
try {

@@ -288,0 +270,0 @@ printError.printError = false;

@@ -18,4 +18,6 @@ import { ConnectionConfig, Connection } from './bindings-smoldot-light.js';

logCallback: (level: number, target: string, message: string) => void;
wasmModule: WebAssembly.Module;
jsonRpcResponsesNonEmptyCallback: (chainId: number) => void;
currentTaskCallback?: (taskName: string | null) => void;
maxLogLevel: number;
cpuRateLimit: number;

@@ -28,13 +30,2 @@ }

/**
* Base64-decode the given buffer then decompress its content using the inflate algorithm
* with zlib header.
*
* The input is considered trusted. In other words, the implementation doesn't have to
* resist malicious input.
*
* This function is asynchronous because implementations might use the compression streams
* Web API, which for whatever reason is asynchronous.
*/
trustedBase64DecodeAndZlibInflate: (input: string) => Promise<Uint8Array>;
/**
* Returns the number of milliseconds since an arbitrary epoch.

@@ -41,0 +32,0 @@ */

@@ -28,3 +28,2 @@ "use strict";

const bindings_wasi_js_1 = require("./bindings-wasi.js");
const wasm_js_1 = require("./autogen/wasm.js");
var bindings_smoldot_light_js_2 = require("./bindings-smoldot-light.js");

@@ -34,9 +33,6 @@ Object.defineProperty(exports, "ConnectionError", { enumerable: true, get: function () { return bindings_smoldot_light_js_2.ConnectionError; } });

return __awaiter(this, void 0, void 0, function* () {
// The actual Wasm bytecode is base64-decoded then deflate-decoded from a constant found in a
// different file.
// This is suboptimal compared to using `instantiateStreaming`, but it is the most
// cross-platform cross-bundler approach.
const wasmBytecode = yield platformBindings.trustedBase64DecodeAndZlibInflate(wasm_js_1.default);
let killAll;
const bufferIndices = new Array;
// Callback called when `advance_execution_ready` is called by the Rust code, if any.
const advanceExecutionPromise = { value: null };
// Used to bind with the smoldot-light bindings. See the `bindings-smoldot-light.js` file.

@@ -47,2 +43,6 @@ const smoldotJsConfig = Object.assign({ bufferIndices, connect: platformBindings.connect, onPanic: (message) => {

throw new Error();
}, advanceExecutionReadyCallback: () => {
if (advanceExecutionPromise.value)
advanceExecutionPromise.value();
advanceExecutionPromise.value = null;
} }, config);

@@ -65,3 +65,3 @@ // Used to bind with the Wasi bindings. See the `bindings-wasi.js` file.

// parameter provides their implementations.
const result = yield WebAssembly.instantiate(wasmBytecode, {
const result = yield WebAssembly.instantiate(config.wasmModule, {
// The functions with the "smoldot" prefix are specific to smoldot.

@@ -72,5 +72,60 @@ "smoldot": smoldotBindings,

});
const instance = result.instance;
const instance = result;
smoldotJsConfig.instance = instance;
wasiConfig.instance = instance;
// Smoldot requires an initial call to the `init` function in order to do its internal
// configuration.
instance.exports.init(config.maxLogLevel);
(() => __awaiter(this, void 0, void 0, function* () {
// In order to avoid calling `setTimeout` too often, we accumulate sleep up until
// a certain threshold.
let missingSleep = 0;
// Extract (to make sure the value doesn't change) and sanitize `cpuRateLimit`.
let cpuRateLimit = config.cpuRateLimit;
if (isNaN(cpuRateLimit))
cpuRateLimit = 1.0;
if (cpuRateLimit > 1.0)
cpuRateLimit = 1.0;
if (cpuRateLimit < 0.0)
cpuRateLimit = 0.0;
const periodicallyYield = { value: false };
const [periodicallyYieldInit, unregisterCallback] = platformBindings.registerShouldPeriodicallyYield((newValue) => {
periodicallyYield.value = newValue;
});
periodicallyYield.value = periodicallyYieldInit;
let now = platformBindings.performanceNow();
while (true) {
const whenReadyAgain = new Promise((resolve) => advanceExecutionPromise.value = resolve);
const outcome = instance.exports.advance_execution();
if (outcome === 0) {
unregisterCallback();
break;
}
const afterExec = platformBindings.performanceNow();
const elapsed = afterExec - now;
now = afterExec;
// In order to enforce the rate limiting, we stop executing for a certain
// amount of time.
// The base equation here is: `(sleep + elapsed) * rateLimit == elapsed`,
// from which the calculation below is derived.
const sleep = elapsed * (1.0 / cpuRateLimit - 1.0);
missingSleep += sleep;
if (missingSleep > (periodicallyYield ? 5 : 1000)) {
// `setTimeout` has a maximum value, after which it will overflow. 🤦
// See <https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value>
// While adding a cap technically skews the CPU rate limiting algorithm, we don't
// really care for such extreme values.
if (missingSleep > 2147483646) // Doc says `> 2147483647`, but I don't really trust their pedanticism so let's be safe
missingSleep = 2147483646;
yield new Promise((resolve) => setTimeout(resolve, missingSleep));
missingSleep = 0;
}
yield whenReadyAgain;
const afterWait = platformBindings.performanceNow();
missingSleep -= (afterWait - now);
if (missingSleep < 0)
missingSleep = 0;
now = afterWait;
}
}))();
return [instance, bufferIndices];

@@ -77,0 +132,0 @@ });

@@ -320,2 +320,2 @@ import { PlatformBindings } from './instance/instance.js';

}
export declare function start(options: ClientOptions, platformBindings: PlatformBindings): Client;
export declare function start(options: ClientOptions, wasmModule: Promise<WebAssembly.Module>, platformBindings: PlatformBindings): Client;

@@ -56,3 +56,3 @@ // Smoldot

// Contrary to the one within `index.js`, this function is not supposed to be directly used.
export function start(options, platformBindings) {
export function start(options, wasmModule, platformBindings) {
const logCallback = options.logCallback || ((level, target, message) => {

@@ -86,2 +86,3 @@ // The first parameter of the methods of `console` has some printf-like substitution

const instance = startInstance({
wasmModule,
// Maximum level of log entries sent by the client.

@@ -91,6 +92,2 @@ // 0 = Logging disabled, 1 = Error, 2 = Warn, 3 = Info, 4 = Debug, 5 = Trace

logCallback,
// `enableCurrentTask` adds a small performance hit, but adds some additional information to
// crash reports. Whether this should be enabled is very opiniated and not that important. At
// the moment, we enable it all the time, except if the user has logging disabled altogether.
enableCurrentTask: options.maxLogLevel ? options.maxLogLevel >= 1 : true,
cpuRateLimit: options.cpuRateLimit || 1.0,

@@ -97,0 +94,0 @@ }, platformBindings);

@@ -28,2 +28,3 @@ // Smoldot

import { inflate } from 'pako';
import { default as wasmBase64 } from './instance/autogen/wasm.js';
export { AddChainError, AlreadyDestroyedError, CrashError, JsonRpcDisabledError, MalformedJsonRpcError, QueueFullError } from './client.js';

@@ -39,6 +40,8 @@ /**

options = options || {};
return innerStart(options, {
trustedBase64DecodeAndZlibInflate: (input) => {
return Promise.resolve(inflate(classicDecode(input)));
},
// The actual Wasm bytecode is base64-decoded then deflate-decoded from a constant found in a
// different file.
// This is suboptimal compared to using `instantiateStreaming`, but it is the most
// cross-platform cross-bundler approach.
const wasmModule = WebAssembly.compile(inflate(classicDecode(wasmBase64)));
return innerStart(options, wasmModule, {
registerShouldPeriodicallyYield: (callback) => {

@@ -58,3 +61,12 @@ if (typeof document === 'undefined') // We might be in a web worker.

throw new Error('randomness not available');
crypto.getRandomValues(buffer);
// Browsers have this completely undocumented behavior (it's not even part of a spec)
// that for some reason `getRandomValues` can't be called on arrayviews back by
// `SharedArrayBuffer`s and they throw an exception if you try.
if (buffer.buffer instanceof ArrayBuffer)
crypto.getRandomValues(buffer);
else {
const tmpArray = new Uint8Array(buffer.length);
crypto.getRandomValues(tmpArray);
buffer.set(tmpArray);
}
},

@@ -61,0 +73,0 @@ connect: (config) => {

@@ -25,2 +25,3 @@ // Smoldot

import { ConnectionError } from './instance/instance.js';
import { default as wasmBase64 } from './instance/autogen/wasm.js';
export { AddChainError, AlreadyDestroyedError, CrashError, MalformedJsonRpcError, QueueFullError, JsonRpcDisabledError } from './client.js';

@@ -36,29 +37,8 @@ /**

options = options || {};
return innerStart(options || {}, {
trustedBase64DecodeAndZlibInflate: (input) => __awaiter(this, void 0, void 0, function* () {
const buffer = trustedBase64Decode(input);
// This code has been copy-pasted from the official streams draft specification.
// At the moment, it is found here: https://wicg.github.io/compression/#example-deflate-compress
const ds = new DecompressionStream('deflate');
const writer = ds.writable.getWriter();
writer.write(buffer);
writer.close();
const output = [];
const reader = ds.readable.getReader();
let totalSize = 0;
while (true) {
const { value, done } = yield reader.read();
if (done)
break;
output.push(value);
totalSize += value.byteLength;
}
const concatenated = new Uint8Array(totalSize);
let offset = 0;
for (const array of output) {
concatenated.set(array, offset);
offset += array.byteLength;
}
return concatenated;
}),
// The actual Wasm bytecode is base64-decoded then deflate-decoded from a constant found in a
// different file.
// This is suboptimal compared to using `instantiateStreaming`, but it is the most
// cross-platform cross-bundler approach.
const wasmModule = zlibInflate(trustedBase64Decode(wasmBase64)).then(((bytecode) => WebAssembly.compile(bytecode)));
return innerStart(options || {}, wasmModule, {
registerShouldPeriodicallyYield: (_callback) => {

@@ -82,2 +62,32 @@ return [true, () => { }];

/**
* Applies the zlib inflate algorithm on the buffer.
*/
function zlibInflate(buffer) {
return __awaiter(this, void 0, void 0, function* () {
// This code has been copy-pasted from the official streams draft specification.
// At the moment, it is found here: https://wicg.github.io/compression/#example-deflate-compress
const ds = new DecompressionStream('deflate');
const writer = ds.writable.getWriter();
writer.write(buffer);
writer.close();
const output = [];
const reader = ds.readable.getReader();
let totalSize = 0;
while (true) {
const { value, done } = yield reader.read();
if (done)
break;
output.push(value);
totalSize += value.byteLength;
}
const concatenated = new Uint8Array(totalSize);
let offset = 0;
for (const array of output) {
concatenated.set(array, offset);
offset += array.byteLength;
}
return concatenated;
});
}
/**
* Decodes a base64 string.

@@ -233,3 +243,3 @@ *

});
read(new Uint8Array(1024));
read(new Uint8Array(32768));
return established;

@@ -236,0 +246,0 @@ });

@@ -19,2 +19,3 @@ // Smoldot

import { ConnectionError } from './instance/instance.js';
import { default as wasmBase64 } from './instance/autogen/wasm.js';
import { WebSocket } from 'ws';

@@ -35,6 +36,8 @@ import { inflate } from 'pako';

options = options || {};
return innerStart(options || {}, {
trustedBase64DecodeAndZlibInflate: (input) => {
return Promise.resolve(inflate(Buffer.from(input, 'base64')));
},
// The actual Wasm bytecode is base64-decoded then deflate-decoded from a constant found in a
// different file.
// This is suboptimal compared to using `instantiateStreaming`, but it is the most
// cross-platform cross-bundler approach.
const wasmModule = WebAssembly.compile(inflate(Buffer.from(wasmBase64, 'base64')));
return innerStart(options || {}, wasmModule, {
registerShouldPeriodicallyYield: (_callback) => {

@@ -41,0 +44,0 @@ return [true, () => { }];

@@ -25,2 +25,3 @@ import type { SmoldotWasmInstance } from './bindings.js';

jsonRpcResponsesNonEmptyCallback: (chainId: number) => void;
advanceExecutionReadyCallback: () => void;
currentTaskCallback?: (taskName: string | null) => void;

@@ -27,0 +28,0 @@ }

@@ -64,2 +64,5 @@ // Smoldot

},
advance_execution_ready: () => {
config.advanceExecutionReadyCallback();
},
// Used by the Rust side to notify that a JSON-RPC response or subscription notification

@@ -90,3 +93,3 @@ // is available in the queue of JSON-RPC responses.

// Must call `timer_finished` after the given number of milliseconds has elapsed.
start_timer: (id, ms) => {
start_timer: (ms) => {
if (killedTracked.killed)

@@ -109,3 +112,3 @@ return;

try {
instance.exports.timer_finished(id);
instance.exports.timer_finished();
}

@@ -120,3 +123,3 @@ catch (_error) { }

try {
instance.exports.timer_finished(id);
instance.exports.timer_finished();
}

@@ -123,0 +126,0 @@ catch (_error) { }

@@ -9,4 +9,4 @@ /**

memory: WebAssembly.Memory;
init: (maxLogLevel: number, enableCurrentTask: number, cpuRateLimit: number, periodicallyYield: number) => void;
set_periodically_yield: (periodicallyYield: number) => void;
init: (maxLogLevel: number) => void;
advance_execution: () => number;
start_shutdown: () => void;

@@ -21,3 +21,3 @@ add_chain: (chainSpecBufferIndex: number, databaseContentBufferIndex: number, jsonRpcRunning: number, potentialRelayChainsBufferIndex: number) => number;

json_rpc_responses_pop: (chainId: number) => void;
timer_finished: (timerId: number) => void;
timer_finished: () => void;
connection_open_single_stream: (connectionId: number, handshakeTy: number, initialWritableBytes: number, writeClosable: number) => void;

@@ -24,0 +24,0 @@ connection_open_multi_stream: (connectionId: number, handshakeTyBufferIndex: number) => void;

@@ -27,5 +27,5 @@ import * as instance from './raw-instance.js';

export interface Config {
wasmModule: Promise<WebAssembly.Module>;
logCallback: (level: number, target: string, message: string) => void;
maxLogLevel: number;
enableCurrentTask: boolean;
cpuRateLimit: number;

@@ -32,0 +32,0 @@ }

@@ -65,61 +65,45 @@ // Smoldot

let chains = new Map();
// Start initialization of the Wasm VM.
const config = {
onWasmPanic: (message) => {
// TODO: consider obtaining a backtrace here
crashError.error = new CrashError(message);
if (!printError.printError)
return;
console.error("Smoldot has panicked" +
(currentTask.name ? (" while executing task `" + currentTask.name + "`") : "") +
". This is a bug in smoldot. Please open an issue at " +
"https://github.com/smol-dot/smoldot/issues with the following message:\n" +
message);
for (const chain of Array.from(chains.values())) {
for (const promise of chain.jsonRpcResponsesPromises) {
promise.reject(crashError.error);
const initPromise = (() => __awaiter(this, void 0, void 0, function* () {
const module = yield configMessage.wasmModule;
// Start initialization of the Wasm VM.
const config = {
onWasmPanic: (message) => {
// TODO: consider obtaining a backtrace here
crashError.error = new CrashError(message);
if (!printError.printError)
return;
console.error("Smoldot has panicked" +
(currentTask.name ? (" while executing task `" + currentTask.name + "`") : "") +
". This is a bug in smoldot. Please open an issue at " +
"https://github.com/smol-dot/smoldot/issues with the following message:\n" +
message);
for (const chain of Array.from(chains.values())) {
for (const promise of chain.jsonRpcResponsesPromises) {
promise.reject(crashError.error);
}
chain.jsonRpcResponsesPromises = [];
}
chain.jsonRpcResponsesPromises = [];
}
},
logCallback: (level, target, message) => {
configMessage.logCallback(level, target, message);
},
jsonRpcResponsesNonEmptyCallback: (chainId) => {
// Notify every single promise found in `jsonRpcResponsesPromises`.
const promises = chains.get(chainId).jsonRpcResponsesPromises;
while (promises.length !== 0) {
promises.shift().resolve();
}
},
currentTaskCallback: (taskName) => {
currentTask.name = taskName;
},
cpuRateLimit: configMessage.cpuRateLimit,
};
},
logCallback: (level, target, message) => {
configMessage.logCallback(level, target, message);
},
wasmModule: module,
jsonRpcResponsesNonEmptyCallback: (chainId) => {
// Notify every single promise found in `jsonRpcResponsesPromises`.
const promises = chains.get(chainId).jsonRpcResponsesPromises;
while (promises.length !== 0) {
promises.shift().resolve();
}
},
currentTaskCallback: (taskName) => {
currentTask.name = taskName;
},
cpuRateLimit: configMessage.cpuRateLimit,
maxLogLevel: configMessage.maxLogLevel,
};
return yield instance.startInstance(config, platformBindings);
}))();
state = {
initialized: false, promise: instance.startInstance(config, platformBindings).then(([instance, bufferIndices]) => {
// `config.cpuRateLimit` is a floating point that should be between 0 and 1, while the value
// to pass as parameter must be between `0` and `2^32-1`.
// The few lines of code below should handle all possible values of `number`, including
// infinites and NaN.
let cpuRateLimit = Math.round(config.cpuRateLimit * 4294967295); // `2^32 - 1`
if (cpuRateLimit < 0)
cpuRateLimit = 0;
if (cpuRateLimit > 4294967295)
cpuRateLimit = 4294967295;
if (!Number.isFinite(cpuRateLimit))
cpuRateLimit = 4294967295; // User might have passed NaN
// Smoldot requires an initial call to the `init` function in order to do its internal
// configuration.
const [periodicallyYield, unregisterCallback] = platformBindings.registerShouldPeriodicallyYield((newValue) => {
if (state.initialized && !crashError.error) {
try {
state.instance.exports.set_periodically_yield(newValue ? 1 : 0);
}
catch (_error) { }
}
});
instance.exports.init(configMessage.maxLogLevel, configMessage.enableCurrentTask ? 1 : 0, cpuRateLimit, periodicallyYield ? 1 : 0);
state = { initialized: true, instance, bufferIndices, unregisterCallback };
initialized: false, promise: initPromise.then(([instance, bufferIndices]) => {
state = { initialized: true, instance, bufferIndices };
return [instance, bufferIndices];

@@ -277,4 +261,2 @@ })

return;
if (state.initialized)
state.unregisterCallback();
try {

@@ -281,0 +263,0 @@ printError.printError = false;

@@ -18,4 +18,6 @@ import { ConnectionConfig, Connection } from './bindings-smoldot-light.js';

logCallback: (level: number, target: string, message: string) => void;
wasmModule: WebAssembly.Module;
jsonRpcResponsesNonEmptyCallback: (chainId: number) => void;
currentTaskCallback?: (taskName: string | null) => void;
maxLogLevel: number;
cpuRateLimit: number;

@@ -28,13 +30,2 @@ }

/**
* Base64-decode the given buffer then decompress its content using the inflate algorithm
* with zlib header.
*
* The input is considered trusted. In other words, the implementation doesn't have to
* resist malicious input.
*
* This function is asynchronous because implementations might use the compression streams
* Web API, which for whatever reason is asynchronous.
*/
trustedBase64DecodeAndZlibInflate: (input: string) => Promise<Uint8Array>;
/**
* Returns the number of milliseconds since an arbitrary epoch.

@@ -41,0 +32,0 @@ */

@@ -25,13 +25,9 @@ // Smoldot

import { default as wasiBindingsBuilder } from './bindings-wasi.js';
import { default as wasmBase64 } from './autogen/wasm.js';
export { ConnectionError } from './bindings-smoldot-light.js';
export function startInstance(config, platformBindings) {
return __awaiter(this, void 0, void 0, function* () {
// The actual Wasm bytecode is base64-decoded then deflate-decoded from a constant found in a
// different file.
// This is suboptimal compared to using `instantiateStreaming`, but it is the most
// cross-platform cross-bundler approach.
const wasmBytecode = yield platformBindings.trustedBase64DecodeAndZlibInflate(wasmBase64);
let killAll;
const bufferIndices = new Array;
// Callback called when `advance_execution_ready` is called by the Rust code, if any.
const advanceExecutionPromise = { value: null };
// Used to bind with the smoldot-light bindings. See the `bindings-smoldot-light.js` file.

@@ -42,2 +38,6 @@ const smoldotJsConfig = Object.assign({ bufferIndices, connect: platformBindings.connect, onPanic: (message) => {

throw new Error();
}, advanceExecutionReadyCallback: () => {
if (advanceExecutionPromise.value)
advanceExecutionPromise.value();
advanceExecutionPromise.value = null;
} }, config);

@@ -60,3 +60,3 @@ // Used to bind with the Wasi bindings. See the `bindings-wasi.js` file.

// parameter provides their implementations.
const result = yield WebAssembly.instantiate(wasmBytecode, {
const result = yield WebAssembly.instantiate(config.wasmModule, {
// The functions with the "smoldot" prefix are specific to smoldot.

@@ -67,7 +67,62 @@ "smoldot": smoldotBindings,

});
const instance = result.instance;
const instance = result;
smoldotJsConfig.instance = instance;
wasiConfig.instance = instance;
// Smoldot requires an initial call to the `init` function in order to do its internal
// configuration.
instance.exports.init(config.maxLogLevel);
(() => __awaiter(this, void 0, void 0, function* () {
// In order to avoid calling `setTimeout` too often, we accumulate sleep up until
// a certain threshold.
let missingSleep = 0;
// Extract (to make sure the value doesn't change) and sanitize `cpuRateLimit`.
let cpuRateLimit = config.cpuRateLimit;
if (isNaN(cpuRateLimit))
cpuRateLimit = 1.0;
if (cpuRateLimit > 1.0)
cpuRateLimit = 1.0;
if (cpuRateLimit < 0.0)
cpuRateLimit = 0.0;
const periodicallyYield = { value: false };
const [periodicallyYieldInit, unregisterCallback] = platformBindings.registerShouldPeriodicallyYield((newValue) => {
periodicallyYield.value = newValue;
});
periodicallyYield.value = periodicallyYieldInit;
let now = platformBindings.performanceNow();
while (true) {
const whenReadyAgain = new Promise((resolve) => advanceExecutionPromise.value = resolve);
const outcome = instance.exports.advance_execution();
if (outcome === 0) {
unregisterCallback();
break;
}
const afterExec = platformBindings.performanceNow();
const elapsed = afterExec - now;
now = afterExec;
// In order to enforce the rate limiting, we stop executing for a certain
// amount of time.
// The base equation here is: `(sleep + elapsed) * rateLimit == elapsed`,
// from which the calculation below is derived.
const sleep = elapsed * (1.0 / cpuRateLimit - 1.0);
missingSleep += sleep;
if (missingSleep > (periodicallyYield ? 5 : 1000)) {
// `setTimeout` has a maximum value, after which it will overflow. 🤦
// See <https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value>
// While adding a cap technically skews the CPU rate limiting algorithm, we don't
// really care for such extreme values.
if (missingSleep > 2147483646) // Doc says `> 2147483647`, but I don't really trust their pedanticism so let's be safe
missingSleep = 2147483646;
yield new Promise((resolve) => setTimeout(resolve, missingSleep));
missingSleep = 0;
}
yield whenReadyAgain;
const afterWait = platformBindings.performanceNow();
missingSleep -= (afterWait - now);
if (missingSleep < 0)
missingSleep = 0;
now = afterWait;
}
}))();
return [instance, bufferIndices];
});
}
{
"name": "smoldot",
"version": "1.0.3",
"version": "1.0.4",
"description": "Light client that connects to Polkadot and Substrate-based blockchains",

@@ -5,0 +5,0 @@ "author": "Parity Technologies <admin@parity.io>",

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc