smoldot 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/client.d.ts +1 -1
- package/dist/cjs/client.js +2 -5
- package/dist/cjs/index-browser.js +17 -5
- package/dist/cjs/index-deno.js +38 -28
- package/dist/cjs/index-nodejs.js +7 -4
- package/dist/cjs/instance/autogen/wasm0.js +1 -1
- package/dist/cjs/instance/autogen/wasm1.js +1 -1
- package/dist/cjs/instance/autogen/wasm2.js +1 -1
- package/dist/cjs/instance/bindings-smoldot-light.d.ts +1 -0
- package/dist/cjs/instance/bindings-smoldot-light.js +6 -3
- package/dist/cjs/instance/bindings.d.ts +3 -3
- package/dist/cjs/instance/instance.d.ts +1 -1
- package/dist/cjs/instance/instance.js +41 -59
- package/dist/cjs/instance/raw-instance.d.ts +2 -11
- package/dist/cjs/instance/raw-instance.js +63 -8
- package/dist/mjs/client.d.ts +1 -1
- package/dist/mjs/client.js +2 -5
- package/dist/mjs/index-browser.js +17 -5
- package/dist/mjs/index-deno.js +38 -28
- package/dist/mjs/index-nodejs.js +7 -4
- package/dist/mjs/instance/autogen/wasm0.js +1 -1
- package/dist/mjs/instance/autogen/wasm1.js +1 -1
- package/dist/mjs/instance/autogen/wasm2.js +1 -1
- package/dist/mjs/instance/bindings-smoldot-light.d.ts +1 -0
- package/dist/mjs/instance/bindings-smoldot-light.js +6 -3
- package/dist/mjs/instance/bindings.d.ts +3 -3
- package/dist/mjs/instance/instance.d.ts +1 -1
- package/dist/mjs/instance/instance.js +41 -59
- package/dist/mjs/instance/raw-instance.d.ts +2 -11
- package/dist/mjs/instance/raw-instance.js +63 -8
- package/package.json +1 -1
|
@@ -22,6 +22,7 @@ export interface Config {
|
|
|
22
22
|
onPanic: (message: string) => never;
|
|
23
23
|
logCallback: (level: number, target: string, message: string) => void;
|
|
24
24
|
jsonRpcResponsesNonEmptyCallback: (chainId: number) => void;
|
|
25
|
+
advanceExecutionReadyCallback: () => void;
|
|
25
26
|
currentTaskCallback?: (taskName: string | null) => void;
|
|
26
27
|
}
|
|
27
28
|
/**
|
|
@@ -61,6 +61,9 @@ export default function (config) {
|
|
|
61
61
|
const buf = config.bufferIndices[bufferIndex];
|
|
62
62
|
new Uint8Array(instance.exports.memory.buffer).set(buf, targetPtr);
|
|
63
63
|
},
|
|
64
|
+
advance_execution_ready: () => {
|
|
65
|
+
config.advanceExecutionReadyCallback();
|
|
66
|
+
},
|
|
64
67
|
// Used by the Rust side to notify that a JSON-RPC response or subscription notification
|
|
65
68
|
// is available in the queue of JSON-RPC responses.
|
|
66
69
|
json_rpc_responses_non_empty: (chainId) => {
|
|
@@ -86,7 +89,7 @@ export default function (config) {
|
|
|
86
89
|
}
|
|
87
90
|
},
|
|
88
91
|
// Must call `timer_finished` after the given number of milliseconds has elapsed.
|
|
89
|
-
start_timer: (
|
|
92
|
+
start_timer: (ms) => {
|
|
90
93
|
if (killedTracked.killed)
|
|
91
94
|
return;
|
|
92
95
|
const instance = config.instance;
|
|
@@ -104,7 +107,7 @@ export default function (config) {
|
|
|
104
107
|
if (killedTracked.killed)
|
|
105
108
|
return;
|
|
106
109
|
try {
|
|
107
|
-
instance.exports.timer_finished(
|
|
110
|
+
instance.exports.timer_finished();
|
|
108
111
|
}
|
|
109
112
|
catch (_error) { }
|
|
110
113
|
});
|
|
@@ -114,7 +117,7 @@ export default function (config) {
|
|
|
114
117
|
if (killedTracked.killed)
|
|
115
118
|
return;
|
|
116
119
|
try {
|
|
117
|
-
instance.exports.timer_finished(
|
|
120
|
+
instance.exports.timer_finished();
|
|
118
121
|
}
|
|
119
122
|
catch (_error) { }
|
|
120
123
|
}, ms);
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export interface SmoldotWasmExports extends WebAssembly.Exports {
|
|
8
8
|
memory: WebAssembly.Memory;
|
|
9
|
-
init: (maxLogLevel: number
|
|
10
|
-
|
|
9
|
+
init: (maxLogLevel: number) => void;
|
|
10
|
+
advance_execution: () => number;
|
|
11
11
|
start_shutdown: () => void;
|
|
12
12
|
add_chain: (chainSpecBufferIndex: number, databaseContentBufferIndex: number, jsonRpcRunning: number, potentialRelayChainsBufferIndex: number) => number;
|
|
13
13
|
remove_chain: (chainId: number) => void;
|
|
@@ -17,7 +17,7 @@ export interface SmoldotWasmExports extends WebAssembly.Exports {
|
|
|
17
17
|
json_rpc_send: (textBufferIndex: number, chainId: number) => number;
|
|
18
18
|
json_rpc_responses_peek: (chainId: number) => number;
|
|
19
19
|
json_rpc_responses_pop: (chainId: number) => void;
|
|
20
|
-
timer_finished: (
|
|
20
|
+
timer_finished: () => void;
|
|
21
21
|
connection_open_single_stream: (connectionId: number, handshakeTy: number, initialWritableBytes: number, writeClosable: number) => void;
|
|
22
22
|
connection_open_multi_stream: (connectionId: number, handshakeTyBufferIndex: number) => void;
|
|
23
23
|
stream_writable_bytes: (connectionId: number, streamId: number, numBytes: number) => void;
|
|
@@ -24,9 +24,9 @@ export declare class QueueFullError extends Error {
|
|
|
24
24
|
* Contains the configuration of the instance.
|
|
25
25
|
*/
|
|
26
26
|
export interface Config {
|
|
27
|
+
wasmModule: Promise<WebAssembly.Module>;
|
|
27
28
|
logCallback: (level: number, target: string, message: string) => void;
|
|
28
29
|
maxLogLevel: number;
|
|
29
|
-
enableCurrentTask: boolean;
|
|
30
30
|
cpuRateLimit: number;
|
|
31
31
|
}
|
|
32
32
|
export interface Instance {
|
|
@@ -62,65 +62,49 @@ export function start(configMessage, platformBindings) {
|
|
|
62
62
|
const printError = { printError: true };
|
|
63
63
|
// Contains the information of each chain that is currently alive.
|
|
64
64
|
let chains = new Map();
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
logCallback: (level, target, message) => {
|
|
85
|
-
configMessage.logCallback(level, target, message);
|
|
86
|
-
},
|
|
87
|
-
jsonRpcResponsesNonEmptyCallback: (chainId) => {
|
|
88
|
-
// Notify every single promise found in `jsonRpcResponsesPromises`.
|
|
89
|
-
const promises = chains.get(chainId).jsonRpcResponsesPromises;
|
|
90
|
-
while (promises.length !== 0) {
|
|
91
|
-
promises.shift().resolve();
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
currentTaskCallback: (taskName) => {
|
|
95
|
-
currentTask.name = taskName;
|
|
96
|
-
},
|
|
97
|
-
cpuRateLimit: configMessage.cpuRateLimit,
|
|
98
|
-
};
|
|
99
|
-
state = {
|
|
100
|
-
initialized: false, promise: instance.startInstance(config, platformBindings).then(([instance, bufferIndices]) => {
|
|
101
|
-
// `config.cpuRateLimit` is a floating point that should be between 0 and 1, while the value
|
|
102
|
-
// to pass as parameter must be between `0` and `2^32-1`.
|
|
103
|
-
// The few lines of code below should handle all possible values of `number`, including
|
|
104
|
-
// infinites and NaN.
|
|
105
|
-
let cpuRateLimit = Math.round(config.cpuRateLimit * 4294967295); // `2^32 - 1`
|
|
106
|
-
if (cpuRateLimit < 0)
|
|
107
|
-
cpuRateLimit = 0;
|
|
108
|
-
if (cpuRateLimit > 4294967295)
|
|
109
|
-
cpuRateLimit = 4294967295;
|
|
110
|
-
if (!Number.isFinite(cpuRateLimit))
|
|
111
|
-
cpuRateLimit = 4294967295; // User might have passed NaN
|
|
112
|
-
// Smoldot requires an initial call to the `init` function in order to do its internal
|
|
113
|
-
// configuration.
|
|
114
|
-
const [periodicallyYield, unregisterCallback] = platformBindings.registerShouldPeriodicallyYield((newValue) => {
|
|
115
|
-
if (state.initialized && !crashError.error) {
|
|
116
|
-
try {
|
|
117
|
-
state.instance.exports.set_periodically_yield(newValue ? 1 : 0);
|
|
65
|
+
const initPromise = (() => __awaiter(this, void 0, void 0, function* () {
|
|
66
|
+
const module = yield configMessage.wasmModule;
|
|
67
|
+
// Start initialization of the Wasm VM.
|
|
68
|
+
const config = {
|
|
69
|
+
onWasmPanic: (message) => {
|
|
70
|
+
// TODO: consider obtaining a backtrace here
|
|
71
|
+
crashError.error = new CrashError(message);
|
|
72
|
+
if (!printError.printError)
|
|
73
|
+
return;
|
|
74
|
+
console.error("Smoldot has panicked" +
|
|
75
|
+
(currentTask.name ? (" while executing task `" + currentTask.name + "`") : "") +
|
|
76
|
+
". This is a bug in smoldot. Please open an issue at " +
|
|
77
|
+
"https://github.com/smol-dot/smoldot/issues with the following message:\n" +
|
|
78
|
+
message);
|
|
79
|
+
for (const chain of Array.from(chains.values())) {
|
|
80
|
+
for (const promise of chain.jsonRpcResponsesPromises) {
|
|
81
|
+
promise.reject(crashError.error);
|
|
118
82
|
}
|
|
119
|
-
|
|
83
|
+
chain.jsonRpcResponsesPromises = [];
|
|
120
84
|
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
85
|
+
},
|
|
86
|
+
logCallback: (level, target, message) => {
|
|
87
|
+
configMessage.logCallback(level, target, message);
|
|
88
|
+
},
|
|
89
|
+
wasmModule: module,
|
|
90
|
+
jsonRpcResponsesNonEmptyCallback: (chainId) => {
|
|
91
|
+
// Notify every single promise found in `jsonRpcResponsesPromises`.
|
|
92
|
+
const promises = chains.get(chainId).jsonRpcResponsesPromises;
|
|
93
|
+
while (promises.length !== 0) {
|
|
94
|
+
promises.shift().resolve();
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
currentTaskCallback: (taskName) => {
|
|
98
|
+
currentTask.name = taskName;
|
|
99
|
+
},
|
|
100
|
+
cpuRateLimit: configMessage.cpuRateLimit,
|
|
101
|
+
maxLogLevel: configMessage.maxLogLevel,
|
|
102
|
+
};
|
|
103
|
+
return yield instance.startInstance(config, platformBindings);
|
|
104
|
+
}))();
|
|
105
|
+
state = {
|
|
106
|
+
initialized: false, promise: initPromise.then(([instance, bufferIndices]) => {
|
|
107
|
+
state = { initialized: true, instance, bufferIndices };
|
|
124
108
|
return [instance, bufferIndices];
|
|
125
109
|
})
|
|
126
110
|
};
|
|
@@ -273,8 +257,6 @@ export function start(configMessage, platformBindings) {
|
|
|
273
257
|
// exception when the user wants the shutdown to happen.
|
|
274
258
|
if (crashError.error)
|
|
275
259
|
return;
|
|
276
|
-
if (state.initialized)
|
|
277
|
-
state.unregisterCallback();
|
|
278
260
|
try {
|
|
279
261
|
printError.printError = false;
|
|
280
262
|
instance.exports.start_shutdown();
|
|
@@ -15,25 +15,16 @@ export interface Config {
|
|
|
15
15
|
*/
|
|
16
16
|
onWasmPanic: (message: string) => void;
|
|
17
17
|
logCallback: (level: number, target: string, message: string) => void;
|
|
18
|
+
wasmModule: WebAssembly.Module;
|
|
18
19
|
jsonRpcResponsesNonEmptyCallback: (chainId: number) => void;
|
|
19
20
|
currentTaskCallback?: (taskName: string | null) => void;
|
|
21
|
+
maxLogLevel: number;
|
|
20
22
|
cpuRateLimit: number;
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
23
25
|
* Contains functions that the client will use when it needs to leverage the platform.
|
|
24
26
|
*/
|
|
25
27
|
export interface PlatformBindings {
|
|
26
|
-
/**
|
|
27
|
-
* Base64-decode the given buffer then decompress its content using the inflate algorithm
|
|
28
|
-
* with zlib header.
|
|
29
|
-
*
|
|
30
|
-
* The input is considered trusted. In other words, the implementation doesn't have to
|
|
31
|
-
* resist malicious input.
|
|
32
|
-
*
|
|
33
|
-
* This function is asynchronous because implementations might use the compression streams
|
|
34
|
-
* Web API, which for whatever reason is asynchronous.
|
|
35
|
-
*/
|
|
36
|
-
trustedBase64DecodeAndZlibInflate: (input: string) => Promise<Uint8Array>;
|
|
37
28
|
/**
|
|
38
29
|
* Returns the number of milliseconds since an arbitrary epoch.
|
|
39
30
|
*/
|
|
@@ -22,22 +22,22 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
22
22
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
23
|
import { default as smoldotLightBindingsBuilder } from './bindings-smoldot-light.js';
|
|
24
24
|
import { default as wasiBindingsBuilder } from './bindings-wasi.js';
|
|
25
|
-
import { default as wasmBase64 } from './autogen/wasm.js';
|
|
26
25
|
export { ConnectionError } from './bindings-smoldot-light.js';
|
|
27
26
|
export function startInstance(config, platformBindings) {
|
|
28
27
|
return __awaiter(this, void 0, void 0, function* () {
|
|
29
|
-
// The actual Wasm bytecode is base64-decoded then deflate-decoded from a constant found in a
|
|
30
|
-
// different file.
|
|
31
|
-
// This is suboptimal compared to using `instantiateStreaming`, but it is the most
|
|
32
|
-
// cross-platform cross-bundler approach.
|
|
33
|
-
const wasmBytecode = yield platformBindings.trustedBase64DecodeAndZlibInflate(wasmBase64);
|
|
34
28
|
let killAll;
|
|
35
29
|
const bufferIndices = new Array;
|
|
30
|
+
// Callback called when `advance_execution_ready` is called by the Rust code, if any.
|
|
31
|
+
const advanceExecutionPromise = { value: null };
|
|
36
32
|
// Used to bind with the smoldot-light bindings. See the `bindings-smoldot-light.js` file.
|
|
37
33
|
const smoldotJsConfig = Object.assign({ bufferIndices, connect: platformBindings.connect, onPanic: (message) => {
|
|
38
34
|
killAll();
|
|
39
35
|
config.onWasmPanic(message);
|
|
40
36
|
throw new Error();
|
|
37
|
+
}, advanceExecutionReadyCallback: () => {
|
|
38
|
+
if (advanceExecutionPromise.value)
|
|
39
|
+
advanceExecutionPromise.value();
|
|
40
|
+
advanceExecutionPromise.value = null;
|
|
41
41
|
} }, config);
|
|
42
42
|
// Used to bind with the Wasi bindings. See the `bindings-wasi.js` file.
|
|
43
43
|
const wasiConfig = {
|
|
@@ -55,15 +55,70 @@ export function startInstance(config, platformBindings) {
|
|
|
55
55
|
// Start the Wasm virtual machine.
|
|
56
56
|
// The Rust code defines a list of imports that must be fulfilled by the environment. The second
|
|
57
57
|
// parameter provides their implementations.
|
|
58
|
-
const result = yield WebAssembly.instantiate(
|
|
58
|
+
const result = yield WebAssembly.instantiate(config.wasmModule, {
|
|
59
59
|
// The functions with the "smoldot" prefix are specific to smoldot.
|
|
60
60
|
"smoldot": smoldotBindings,
|
|
61
61
|
// As the Rust code is compiled for wasi, some more wasi-specific imports exist.
|
|
62
62
|
"wasi_snapshot_preview1": wasiBindingsBuilder(wasiConfig),
|
|
63
63
|
});
|
|
64
|
-
const instance = result
|
|
64
|
+
const instance = result;
|
|
65
65
|
smoldotJsConfig.instance = instance;
|
|
66
66
|
wasiConfig.instance = instance;
|
|
67
|
+
// Smoldot requires an initial call to the `init` function in order to do its internal
|
|
68
|
+
// configuration.
|
|
69
|
+
instance.exports.init(config.maxLogLevel);
|
|
70
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
// In order to avoid calling `setTimeout` too often, we accumulate sleep up until
|
|
72
|
+
// a certain threshold.
|
|
73
|
+
let missingSleep = 0;
|
|
74
|
+
// Extract (to make sure the value doesn't change) and sanitize `cpuRateLimit`.
|
|
75
|
+
let cpuRateLimit = config.cpuRateLimit;
|
|
76
|
+
if (isNaN(cpuRateLimit))
|
|
77
|
+
cpuRateLimit = 1.0;
|
|
78
|
+
if (cpuRateLimit > 1.0)
|
|
79
|
+
cpuRateLimit = 1.0;
|
|
80
|
+
if (cpuRateLimit < 0.0)
|
|
81
|
+
cpuRateLimit = 0.0;
|
|
82
|
+
const periodicallyYield = { value: false };
|
|
83
|
+
const [periodicallyYieldInit, unregisterCallback] = platformBindings.registerShouldPeriodicallyYield((newValue) => {
|
|
84
|
+
periodicallyYield.value = newValue;
|
|
85
|
+
});
|
|
86
|
+
periodicallyYield.value = periodicallyYieldInit;
|
|
87
|
+
let now = platformBindings.performanceNow();
|
|
88
|
+
while (true) {
|
|
89
|
+
const whenReadyAgain = new Promise((resolve) => advanceExecutionPromise.value = resolve);
|
|
90
|
+
const outcome = instance.exports.advance_execution();
|
|
91
|
+
if (outcome === 0) {
|
|
92
|
+
unregisterCallback();
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
const afterExec = platformBindings.performanceNow();
|
|
96
|
+
const elapsed = afterExec - now;
|
|
97
|
+
now = afterExec;
|
|
98
|
+
// In order to enforce the rate limiting, we stop executing for a certain
|
|
99
|
+
// amount of time.
|
|
100
|
+
// The base equation here is: `(sleep + elapsed) * rateLimit == elapsed`,
|
|
101
|
+
// from which the calculation below is derived.
|
|
102
|
+
const sleep = elapsed * (1.0 / cpuRateLimit - 1.0);
|
|
103
|
+
missingSleep += sleep;
|
|
104
|
+
if (missingSleep > (periodicallyYield ? 5 : 1000)) {
|
|
105
|
+
// `setTimeout` has a maximum value, after which it will overflow. 🤦
|
|
106
|
+
// See <https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value>
|
|
107
|
+
// While adding a cap technically skews the CPU rate limiting algorithm, we don't
|
|
108
|
+
// really care for such extreme values.
|
|
109
|
+
if (missingSleep > 2147483646) // Doc says `> 2147483647`, but I don't really trust their pedanticism so let's be safe
|
|
110
|
+
missingSleep = 2147483646;
|
|
111
|
+
yield new Promise((resolve) => setTimeout(resolve, missingSleep));
|
|
112
|
+
missingSleep = 0;
|
|
113
|
+
}
|
|
114
|
+
yield whenReadyAgain;
|
|
115
|
+
const afterWait = platformBindings.performanceNow();
|
|
116
|
+
missingSleep -= (afterWait - now);
|
|
117
|
+
if (missingSleep < 0)
|
|
118
|
+
missingSleep = 0;
|
|
119
|
+
now = afterWait;
|
|
120
|
+
}
|
|
121
|
+
}))();
|
|
67
122
|
return [instance, bufferIndices];
|
|
68
123
|
});
|
|
69
124
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smoldot",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Light client that connects to Polkadot and Substrate-based blockchains",
|
|
5
5
|
"author": "Parity Technologies <admin@parity.io>",
|
|
6
6
|
"license": "GPL-3.0-or-later WITH Classpath-exception-2.0",
|