smoldot 1.0.2 → 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 +15 -18
- 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 +7 -6
- package/dist/cjs/instance/bindings-smoldot-light.js +6 -7
- package/dist/cjs/instance/bindings-wasi.d.ts +4 -0
- package/dist/cjs/instance/bindings-wasi.js +30 -0
- package/dist/cjs/instance/bindings.d.ts +3 -3
- package/dist/cjs/instance/buffer.d.ts +1 -0
- package/dist/cjs/instance/buffer.js +13 -1
- 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 +65 -9
- package/dist/mjs/client.d.ts +1 -1
- package/dist/mjs/client.js +15 -18
- 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 +7 -6
- package/dist/mjs/instance/bindings-smoldot-light.js +6 -7
- package/dist/mjs/instance/bindings-wasi.d.ts +4 -0
- package/dist/mjs/instance/bindings-wasi.js +30 -0
- package/dist/mjs/instance/bindings.d.ts +3 -3
- package/dist/mjs/instance/buffer.d.ts +1 -0
- package/dist/mjs/instance/buffer.js +11 -0
- 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 +65 -9
- package/package.json +2 -2
|
@@ -7,10 +7,6 @@ export interface Config {
|
|
|
7
7
|
* When `buffer_size` or `buffer_index` are called, the buffer is found here.
|
|
8
8
|
*/
|
|
9
9
|
bufferIndices: Array<Uint8Array>;
|
|
10
|
-
/**
|
|
11
|
-
* Returns the number of milliseconds since an arbitrary epoch.
|
|
12
|
-
*/
|
|
13
|
-
performanceNow: () => number;
|
|
14
10
|
/**
|
|
15
11
|
* Tries to open a new connection using the given configuration.
|
|
16
12
|
*
|
|
@@ -26,6 +22,7 @@ export interface Config {
|
|
|
26
22
|
onPanic: (message: string) => never;
|
|
27
23
|
logCallback: (level: number, target: string, message: string) => void;
|
|
28
24
|
jsonRpcResponsesNonEmptyCallback: (chainId: number) => void;
|
|
25
|
+
advanceExecutionReadyCallback: () => void;
|
|
29
26
|
currentTaskCallback?: (taskName: string | null) => void;
|
|
30
27
|
}
|
|
31
28
|
/**
|
|
@@ -71,7 +68,7 @@ export interface Connection {
|
|
|
71
68
|
*
|
|
72
69
|
* The connection and stream must currently be in the `Open` state.
|
|
73
70
|
*
|
|
74
|
-
* The number of bytes
|
|
71
|
+
* The number of bytes must never exceed the number of "writable bytes" of the stream.
|
|
75
72
|
* `onWritableBytes` can be used in order to notify that more writable bytes are available.
|
|
76
73
|
*
|
|
77
74
|
* The `streamId` must be provided if and only if the connection is of type "multi-stream".
|
|
@@ -160,7 +157,8 @@ export interface ConnectionConfig {
|
|
|
160
157
|
*/
|
|
161
158
|
onStreamReset: (streamId: number) => void;
|
|
162
159
|
/**
|
|
163
|
-
* Callback called when
|
|
160
|
+
* Callback called when some data sent using {@link Connection.send} has effectively been
|
|
161
|
+
* written on the stream, meaning that some buffer space is now free.
|
|
164
162
|
*
|
|
165
163
|
* Can only happen while the connection is in the `Open` state.
|
|
166
164
|
*
|
|
@@ -168,6 +166,9 @@ export interface ConnectionConfig {
|
|
|
168
166
|
*
|
|
169
167
|
* The `streamId` parameter must be provided if and only if the connection is of type
|
|
170
168
|
* "multi-stream".
|
|
169
|
+
*
|
|
170
|
+
* Only a number of bytes equal to the size of the data provided to {@link Connection.send}
|
|
171
|
+
* must be reported. In other words, the `initialWritableBytes` must never be exceeded.
|
|
171
172
|
*/
|
|
172
173
|
onWritableBytes: (numExtra: number, streamId?: number) => void;
|
|
173
174
|
/**
|
|
@@ -65,6 +65,9 @@ function default_1(config) {
|
|
|
65
65
|
const buf = config.bufferIndices[bufferIndex];
|
|
66
66
|
new Uint8Array(instance.exports.memory.buffer).set(buf, targetPtr);
|
|
67
67
|
},
|
|
68
|
+
advance_execution_ready: () => {
|
|
69
|
+
config.advanceExecutionReadyCallback();
|
|
70
|
+
},
|
|
68
71
|
// Used by the Rust side to notify that a JSON-RPC response or subscription notification
|
|
69
72
|
// is available in the queue of JSON-RPC responses.
|
|
70
73
|
json_rpc_responses_non_empty: (chainId) => {
|
|
@@ -89,12 +92,8 @@ function default_1(config) {
|
|
|
89
92
|
config.logCallback(level, target, message);
|
|
90
93
|
}
|
|
91
94
|
},
|
|
92
|
-
// Must return the UNIX time in milliseconds.
|
|
93
|
-
unix_time_ms: () => Date.now(),
|
|
94
|
-
// Must return the value of a monotonic clock in milliseconds.
|
|
95
|
-
monotonic_clock_ms: () => config.performanceNow(),
|
|
96
95
|
// Must call `timer_finished` after the given number of milliseconds has elapsed.
|
|
97
|
-
start_timer: (
|
|
96
|
+
start_timer: (ms) => {
|
|
98
97
|
if (killedTracked.killed)
|
|
99
98
|
return;
|
|
100
99
|
const instance = config.instance;
|
|
@@ -112,7 +111,7 @@ function default_1(config) {
|
|
|
112
111
|
if (killedTracked.killed)
|
|
113
112
|
return;
|
|
114
113
|
try {
|
|
115
|
-
instance.exports.timer_finished(
|
|
114
|
+
instance.exports.timer_finished();
|
|
116
115
|
}
|
|
117
116
|
catch (_error) { }
|
|
118
117
|
});
|
|
@@ -122,7 +121,7 @@ function default_1(config) {
|
|
|
122
121
|
if (killedTracked.killed)
|
|
123
122
|
return;
|
|
124
123
|
try {
|
|
125
|
-
instance.exports.timer_finished(
|
|
124
|
+
instance.exports.timer_finished();
|
|
126
125
|
}
|
|
127
126
|
catch (_error) { }
|
|
128
127
|
}, ms);
|
|
@@ -5,6 +5,10 @@ export interface Config {
|
|
|
5
5
|
* Fills the given buffer with randomly-generated bytes.
|
|
6
6
|
*/
|
|
7
7
|
getRandomValues: (buffer: Uint8Array) => void;
|
|
8
|
+
/**
|
|
9
|
+
* Returns the number of milliseconds since an arbitrary epoch.
|
|
10
|
+
*/
|
|
11
|
+
performanceNow: () => number;
|
|
8
12
|
/**
|
|
9
13
|
* List of environment variables to feed to the Rust program. An array of strings.
|
|
10
14
|
* Example: `["RUST_BACKTRACE=1", "RUST_LOG=foo"];`
|
|
@@ -41,6 +41,36 @@ exports.default = (config) => {
|
|
|
41
41
|
}
|
|
42
42
|
return 0;
|
|
43
43
|
},
|
|
44
|
+
clock_time_get: (clockId, _precision, outPtr) => {
|
|
45
|
+
// See <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/wasi/time.rs>
|
|
46
|
+
// and <docs.rs/wasi/> for help.
|
|
47
|
+
const instance = config.instance;
|
|
48
|
+
const mem = new Uint8Array(instance.exports.memory.buffer);
|
|
49
|
+
outPtr >>>= 0;
|
|
50
|
+
// We ignore the precision, as it can't be implemented anyway.
|
|
51
|
+
switch (clockId) {
|
|
52
|
+
case 0: {
|
|
53
|
+
// Realtime clock.
|
|
54
|
+
const now = BigInt(Math.floor(Date.now())) * BigInt(1000000);
|
|
55
|
+
buffer.writeUInt64LE(mem, outPtr, now);
|
|
56
|
+
// Success.
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
case 1: {
|
|
60
|
+
// Monotonic clock.
|
|
61
|
+
const nowMs = config.performanceNow();
|
|
62
|
+
const nowMsInt = Math.floor(nowMs);
|
|
63
|
+
const now = BigInt(nowMsInt) * BigInt(1000000) +
|
|
64
|
+
BigInt(Math.floor(((nowMs - nowMsInt) * 1000000)));
|
|
65
|
+
buffer.writeUInt64LE(mem, outPtr, now);
|
|
66
|
+
// Success.
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
default:
|
|
70
|
+
// Return an `EINVAL` error.
|
|
71
|
+
return 28;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
44
74
|
// Writing to a file descriptor is used in order to write to stdout/stderr.
|
|
45
75
|
fd_write: (fd, addr, num, outPtr) => {
|
|
46
76
|
const instance = config.instance;
|
|
@@ -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;
|
|
@@ -8,3 +8,4 @@ export declare function readUInt32LE(buffer: Uint8Array, offset: number): number
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function writeUInt8(buffer: Uint8Array, offset: number, value: number): void;
|
|
10
10
|
export declare function writeUInt32LE(buffer: Uint8Array, offset: number, value: number): void;
|
|
11
|
+
export declare function writeUInt64LE(buffer: Uint8Array, offset: number, value: bigint): void;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
|
|
4
4
|
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.writeUInt32LE = exports.writeUInt8 = exports.readUInt32LE = exports.utf8BytesToString = void 0;
|
|
6
|
+
exports.writeUInt64LE = exports.writeUInt32LE = exports.writeUInt8 = exports.readUInt32LE = exports.utf8BytesToString = void 0;
|
|
7
7
|
// This program is free software: you can redistribute it and/or modify
|
|
8
8
|
// it under the terms of the GNU General Public License as published by
|
|
9
9
|
// the Free Software Foundation, either version 3 of the License, or
|
|
@@ -45,6 +45,18 @@ function writeUInt32LE(buffer, offset, value) {
|
|
|
45
45
|
buffer[offset] = value & 0xff;
|
|
46
46
|
}
|
|
47
47
|
exports.writeUInt32LE = writeUInt32LE;
|
|
48
|
+
function writeUInt64LE(buffer, offset, value) {
|
|
49
|
+
checkRange(buffer, offset, 8);
|
|
50
|
+
buffer[offset + 7] = Number((value >> BigInt(56)) & BigInt(0xff));
|
|
51
|
+
buffer[offset + 6] = Number((value >> BigInt(48)) & BigInt(0xff));
|
|
52
|
+
buffer[offset + 5] = Number((value >> BigInt(40)) & BigInt(0xff));
|
|
53
|
+
buffer[offset + 4] = Number((value >> BigInt(32)) & BigInt(0xff));
|
|
54
|
+
buffer[offset + 3] = Number((value >> BigInt(24)) & BigInt(0xff));
|
|
55
|
+
buffer[offset + 2] = Number((value >> BigInt(16)) & BigInt(0xff));
|
|
56
|
+
buffer[offset + 1] = Number((value >> BigInt(8)) & BigInt(0xff));
|
|
57
|
+
buffer[offset] = Number(value & BigInt(0xff));
|
|
58
|
+
}
|
|
59
|
+
exports.writeUInt64LE = writeUInt64LE;
|
|
48
60
|
function checkRange(buffer, offset, length) {
|
|
49
61
|
if (!Number.isInteger(offset) || offset < 0)
|
|
50
62
|
throw new RangeError();
|
|
@@ -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 {
|
|
@@ -69,65 +69,49 @@ function start(configMessage, platformBindings) {
|
|
|
69
69
|
const printError = { printError: true };
|
|
70
70
|
// Contains the information of each chain that is currently alive.
|
|
71
71
|
let chains = new Map();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
logCallback: (level, target, message) => {
|
|
92
|
-
configMessage.logCallback(level, target, message);
|
|
93
|
-
},
|
|
94
|
-
jsonRpcResponsesNonEmptyCallback: (chainId) => {
|
|
95
|
-
// Notify every single promise found in `jsonRpcResponsesPromises`.
|
|
96
|
-
const promises = chains.get(chainId).jsonRpcResponsesPromises;
|
|
97
|
-
while (promises.length !== 0) {
|
|
98
|
-
promises.shift().resolve();
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
currentTaskCallback: (taskName) => {
|
|
102
|
-
currentTask.name = taskName;
|
|
103
|
-
},
|
|
104
|
-
cpuRateLimit: configMessage.cpuRateLimit,
|
|
105
|
-
};
|
|
106
|
-
state = {
|
|
107
|
-
initialized: false, promise: instance.startInstance(config, platformBindings).then(([instance, bufferIndices]) => {
|
|
108
|
-
// `config.cpuRateLimit` is a floating point that should be between 0 and 1, while the value
|
|
109
|
-
// to pass as parameter must be between `0` and `2^32-1`.
|
|
110
|
-
// The few lines of code below should handle all possible values of `number`, including
|
|
111
|
-
// infinites and NaN.
|
|
112
|
-
let cpuRateLimit = Math.round(config.cpuRateLimit * 4294967295); // `2^32 - 1`
|
|
113
|
-
if (cpuRateLimit < 0)
|
|
114
|
-
cpuRateLimit = 0;
|
|
115
|
-
if (cpuRateLimit > 4294967295)
|
|
116
|
-
cpuRateLimit = 4294967295;
|
|
117
|
-
if (!Number.isFinite(cpuRateLimit))
|
|
118
|
-
cpuRateLimit = 4294967295; // User might have passed NaN
|
|
119
|
-
// Smoldot requires an initial call to the `init` function in order to do its internal
|
|
120
|
-
// configuration.
|
|
121
|
-
const [periodicallyYield, unregisterCallback] = platformBindings.registerShouldPeriodicallyYield((newValue) => {
|
|
122
|
-
if (state.initialized && !crashError.error) {
|
|
123
|
-
try {
|
|
124
|
-
state.instance.exports.set_periodically_yield(newValue ? 1 : 0);
|
|
72
|
+
const initPromise = (() => __awaiter(this, void 0, void 0, function* () {
|
|
73
|
+
const module = yield configMessage.wasmModule;
|
|
74
|
+
// Start initialization of the Wasm VM.
|
|
75
|
+
const config = {
|
|
76
|
+
onWasmPanic: (message) => {
|
|
77
|
+
// TODO: consider obtaining a backtrace here
|
|
78
|
+
crashError.error = new CrashError(message);
|
|
79
|
+
if (!printError.printError)
|
|
80
|
+
return;
|
|
81
|
+
console.error("Smoldot has panicked" +
|
|
82
|
+
(currentTask.name ? (" while executing task `" + currentTask.name + "`") : "") +
|
|
83
|
+
". This is a bug in smoldot. Please open an issue at " +
|
|
84
|
+
"https://github.com/smol-dot/smoldot/issues with the following message:\n" +
|
|
85
|
+
message);
|
|
86
|
+
for (const chain of Array.from(chains.values())) {
|
|
87
|
+
for (const promise of chain.jsonRpcResponsesPromises) {
|
|
88
|
+
promise.reject(crashError.error);
|
|
125
89
|
}
|
|
126
|
-
|
|
90
|
+
chain.jsonRpcResponsesPromises = [];
|
|
127
91
|
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
92
|
+
},
|
|
93
|
+
logCallback: (level, target, message) => {
|
|
94
|
+
configMessage.logCallback(level, target, message);
|
|
95
|
+
},
|
|
96
|
+
wasmModule: module,
|
|
97
|
+
jsonRpcResponsesNonEmptyCallback: (chainId) => {
|
|
98
|
+
// Notify every single promise found in `jsonRpcResponsesPromises`.
|
|
99
|
+
const promises = chains.get(chainId).jsonRpcResponsesPromises;
|
|
100
|
+
while (promises.length !== 0) {
|
|
101
|
+
promises.shift().resolve();
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
currentTaskCallback: (taskName) => {
|
|
105
|
+
currentTask.name = taskName;
|
|
106
|
+
},
|
|
107
|
+
cpuRateLimit: configMessage.cpuRateLimit,
|
|
108
|
+
maxLogLevel: configMessage.maxLogLevel,
|
|
109
|
+
};
|
|
110
|
+
return yield instance.startInstance(config, platformBindings);
|
|
111
|
+
}))();
|
|
112
|
+
state = {
|
|
113
|
+
initialized: false, promise: initPromise.then(([instance, bufferIndices]) => {
|
|
114
|
+
state = { initialized: true, instance, bufferIndices };
|
|
131
115
|
return [instance, bufferIndices];
|
|
132
116
|
})
|
|
133
117
|
};
|
|
@@ -280,8 +264,6 @@ function start(configMessage, platformBindings) {
|
|
|
280
264
|
// exception when the user wants the shutdown to happen.
|
|
281
265
|
if (crashError.error)
|
|
282
266
|
return;
|
|
283
|
-
if (state.initialized)
|
|
284
|
-
state.unregisterCallback();
|
|
285
267
|
try {
|
|
286
268
|
printError.printError = false;
|
|
287
269
|
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
|
*/
|
|
@@ -25,28 +25,29 @@ exports.startInstance = exports.ConnectionError = void 0;
|
|
|
25
25
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
26
26
|
const bindings_smoldot_light_js_1 = require("./bindings-smoldot-light.js");
|
|
27
27
|
const bindings_wasi_js_1 = require("./bindings-wasi.js");
|
|
28
|
-
const wasm_js_1 = require("./autogen/wasm.js");
|
|
29
28
|
var bindings_smoldot_light_js_2 = require("./bindings-smoldot-light.js");
|
|
30
29
|
Object.defineProperty(exports, "ConnectionError", { enumerable: true, get: function () { return bindings_smoldot_light_js_2.ConnectionError; } });
|
|
31
30
|
function startInstance(config, platformBindings) {
|
|
32
31
|
return __awaiter(this, void 0, void 0, function* () {
|
|
33
|
-
// The actual Wasm bytecode is base64-decoded then deflate-decoded from a constant found in a
|
|
34
|
-
// different file.
|
|
35
|
-
// This is suboptimal compared to using `instantiateStreaming`, but it is the most
|
|
36
|
-
// cross-platform cross-bundler approach.
|
|
37
|
-
const wasmBytecode = yield platformBindings.trustedBase64DecodeAndZlibInflate(wasm_js_1.default);
|
|
38
32
|
let killAll;
|
|
39
33
|
const bufferIndices = new Array;
|
|
34
|
+
// Callback called when `advance_execution_ready` is called by the Rust code, if any.
|
|
35
|
+
const advanceExecutionPromise = { value: null };
|
|
40
36
|
// Used to bind with the smoldot-light bindings. See the `bindings-smoldot-light.js` file.
|
|
41
|
-
const smoldotJsConfig = Object.assign({ bufferIndices,
|
|
37
|
+
const smoldotJsConfig = Object.assign({ bufferIndices, connect: platformBindings.connect, onPanic: (message) => {
|
|
42
38
|
killAll();
|
|
43
39
|
config.onWasmPanic(message);
|
|
44
40
|
throw new Error();
|
|
41
|
+
}, advanceExecutionReadyCallback: () => {
|
|
42
|
+
if (advanceExecutionPromise.value)
|
|
43
|
+
advanceExecutionPromise.value();
|
|
44
|
+
advanceExecutionPromise.value = null;
|
|
45
45
|
} }, config);
|
|
46
46
|
// Used to bind with the Wasi bindings. See the `bindings-wasi.js` file.
|
|
47
47
|
const wasiConfig = {
|
|
48
48
|
envVars: [],
|
|
49
49
|
getRandomValues: platformBindings.getRandomValues,
|
|
50
|
+
performanceNow: platformBindings.performanceNow,
|
|
50
51
|
onProcExit: (retCode) => {
|
|
51
52
|
killAll();
|
|
52
53
|
config.onWasmPanic(`proc_exit called: ${retCode}`);
|
|
@@ -58,15 +59,70 @@ function startInstance(config, platformBindings) {
|
|
|
58
59
|
// Start the Wasm virtual machine.
|
|
59
60
|
// The Rust code defines a list of imports that must be fulfilled by the environment. The second
|
|
60
61
|
// parameter provides their implementations.
|
|
61
|
-
const result = yield WebAssembly.instantiate(
|
|
62
|
+
const result = yield WebAssembly.instantiate(config.wasmModule, {
|
|
62
63
|
// The functions with the "smoldot" prefix are specific to smoldot.
|
|
63
64
|
"smoldot": smoldotBindings,
|
|
64
65
|
// As the Rust code is compiled for wasi, some more wasi-specific imports exist.
|
|
65
66
|
"wasi_snapshot_preview1": (0, bindings_wasi_js_1.default)(wasiConfig),
|
|
66
67
|
});
|
|
67
|
-
const instance = result
|
|
68
|
+
const instance = result;
|
|
68
69
|
smoldotJsConfig.instance = instance;
|
|
69
70
|
wasiConfig.instance = instance;
|
|
71
|
+
// Smoldot requires an initial call to the `init` function in order to do its internal
|
|
72
|
+
// configuration.
|
|
73
|
+
instance.exports.init(config.maxLogLevel);
|
|
74
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
75
|
+
// In order to avoid calling `setTimeout` too often, we accumulate sleep up until
|
|
76
|
+
// a certain threshold.
|
|
77
|
+
let missingSleep = 0;
|
|
78
|
+
// Extract (to make sure the value doesn't change) and sanitize `cpuRateLimit`.
|
|
79
|
+
let cpuRateLimit = config.cpuRateLimit;
|
|
80
|
+
if (isNaN(cpuRateLimit))
|
|
81
|
+
cpuRateLimit = 1.0;
|
|
82
|
+
if (cpuRateLimit > 1.0)
|
|
83
|
+
cpuRateLimit = 1.0;
|
|
84
|
+
if (cpuRateLimit < 0.0)
|
|
85
|
+
cpuRateLimit = 0.0;
|
|
86
|
+
const periodicallyYield = { value: false };
|
|
87
|
+
const [periodicallyYieldInit, unregisterCallback] = platformBindings.registerShouldPeriodicallyYield((newValue) => {
|
|
88
|
+
periodicallyYield.value = newValue;
|
|
89
|
+
});
|
|
90
|
+
periodicallyYield.value = periodicallyYieldInit;
|
|
91
|
+
let now = platformBindings.performanceNow();
|
|
92
|
+
while (true) {
|
|
93
|
+
const whenReadyAgain = new Promise((resolve) => advanceExecutionPromise.value = resolve);
|
|
94
|
+
const outcome = instance.exports.advance_execution();
|
|
95
|
+
if (outcome === 0) {
|
|
96
|
+
unregisterCallback();
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
const afterExec = platformBindings.performanceNow();
|
|
100
|
+
const elapsed = afterExec - now;
|
|
101
|
+
now = afterExec;
|
|
102
|
+
// In order to enforce the rate limiting, we stop executing for a certain
|
|
103
|
+
// amount of time.
|
|
104
|
+
// The base equation here is: `(sleep + elapsed) * rateLimit == elapsed`,
|
|
105
|
+
// from which the calculation below is derived.
|
|
106
|
+
const sleep = elapsed * (1.0 / cpuRateLimit - 1.0);
|
|
107
|
+
missingSleep += sleep;
|
|
108
|
+
if (missingSleep > (periodicallyYield ? 5 : 1000)) {
|
|
109
|
+
// `setTimeout` has a maximum value, after which it will overflow. 🤦
|
|
110
|
+
// See <https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value>
|
|
111
|
+
// While adding a cap technically skews the CPU rate limiting algorithm, we don't
|
|
112
|
+
// really care for such extreme values.
|
|
113
|
+
if (missingSleep > 2147483646) // Doc says `> 2147483647`, but I don't really trust their pedanticism so let's be safe
|
|
114
|
+
missingSleep = 2147483646;
|
|
115
|
+
yield new Promise((resolve) => setTimeout(resolve, missingSleep));
|
|
116
|
+
missingSleep = 0;
|
|
117
|
+
}
|
|
118
|
+
yield whenReadyAgain;
|
|
119
|
+
const afterWait = platformBindings.performanceNow();
|
|
120
|
+
missingSleep -= (afterWait - now);
|
|
121
|
+
if (missingSleep < 0)
|
|
122
|
+
missingSleep = 0;
|
|
123
|
+
now = afterWait;
|
|
124
|
+
}
|
|
125
|
+
}))();
|
|
70
126
|
return [instance, bufferIndices];
|
|
71
127
|
});
|
|
72
128
|
}
|
package/dist/mjs/client.d.ts
CHANGED
|
@@ -317,4 +317,4 @@ export interface AddChainOptions {
|
|
|
317
317
|
*/
|
|
318
318
|
disableJsonRpc?: boolean;
|
|
319
319
|
}
|
|
320
|
-
export declare function start(options: ClientOptions, platformBindings: PlatformBindings): Client;
|
|
320
|
+
export declare function start(options: ClientOptions, wasmModule: Promise<WebAssembly.Module>, platformBindings: PlatformBindings): Client;
|
package/dist/mjs/client.js
CHANGED
|
@@ -53,7 +53,7 @@ export class JsonRpcDisabledError extends Error {
|
|
|
53
53
|
// This function is similar to the `start` function found in `index.ts`, except with an extra
|
|
54
54
|
// parameter containing the platform-specific bindings.
|
|
55
55
|
// Contrary to the one within `index.js`, this function is not supposed to be directly used.
|
|
56
|
-
export function start(options, platformBindings) {
|
|
56
|
+
export function start(options, wasmModule, platformBindings) {
|
|
57
57
|
const logCallback = options.logCallback || ((level, target, message) => {
|
|
58
58
|
// The first parameter of the methods of `console` has some printf-like substitution
|
|
59
59
|
// capabilities. We don't really need to use this, but not using it means that the logs might
|
|
@@ -77,25 +77,22 @@ export function start(options, platformBindings) {
|
|
|
77
77
|
// For each chain object returned by `addChain`, the associated internal chain id.
|
|
78
78
|
//
|
|
79
79
|
// Immediately cleared when `remove()` is called on a chain.
|
|
80
|
-
|
|
80
|
+
const chainIds = new WeakMap();
|
|
81
81
|
// If `Client.terminate()̀ is called, this error is set to a value.
|
|
82
82
|
// All the functions of the public API check if this contains a value.
|
|
83
|
-
|
|
83
|
+
const alreadyDestroyedError = { value: null };
|
|
84
84
|
const instance = startInstance({
|
|
85
|
+
wasmModule,
|
|
85
86
|
// Maximum level of log entries sent by the client.
|
|
86
87
|
// 0 = Logging disabled, 1 = Error, 2 = Warn, 3 = Info, 4 = Debug, 5 = Trace
|
|
87
88
|
maxLogLevel: options.maxLogLevel || 3,
|
|
88
89
|
logCallback,
|
|
89
|
-
// `enableCurrentTask` adds a small performance hit, but adds some additional information to
|
|
90
|
-
// crash reports. Whether this should be enabled is very opiniated and not that important. At
|
|
91
|
-
// the moment, we enable it all the time, except if the user has logging disabled altogether.
|
|
92
|
-
enableCurrentTask: options.maxLogLevel ? options.maxLogLevel >= 1 : true,
|
|
93
90
|
cpuRateLimit: options.cpuRateLimit || 1.0,
|
|
94
91
|
}, platformBindings);
|
|
95
92
|
return {
|
|
96
93
|
addChain: (options) => __awaiter(this, void 0, void 0, function* () {
|
|
97
|
-
if (alreadyDestroyedError)
|
|
98
|
-
throw alreadyDestroyedError;
|
|
94
|
+
if (alreadyDestroyedError.value)
|
|
95
|
+
throw alreadyDestroyedError.value;
|
|
99
96
|
// Passing a JSON object for the chain spec is an easy mistake, so we provide a more
|
|
100
97
|
// readable error.
|
|
101
98
|
if (!(typeof options.chainSpec === 'string'))
|
|
@@ -120,8 +117,8 @@ export function start(options, platformBindings) {
|
|
|
120
117
|
// Resolve the promise that `addChain` returned to the user.
|
|
121
118
|
const newChain = {
|
|
122
119
|
sendJsonRpc: (request) => {
|
|
123
|
-
if (alreadyDestroyedError)
|
|
124
|
-
throw alreadyDestroyedError;
|
|
120
|
+
if (alreadyDestroyedError.value)
|
|
121
|
+
throw alreadyDestroyedError.value;
|
|
125
122
|
if (wasDestroyed.destroyed)
|
|
126
123
|
throw new AlreadyDestroyedError();
|
|
127
124
|
if (options.disableJsonRpc)
|
|
@@ -133,8 +130,8 @@ export function start(options, platformBindings) {
|
|
|
133
130
|
instance.request(request, chainId);
|
|
134
131
|
},
|
|
135
132
|
nextJsonRpcResponse: () => {
|
|
136
|
-
if (alreadyDestroyedError)
|
|
137
|
-
return Promise.reject(alreadyDestroyedError);
|
|
133
|
+
if (alreadyDestroyedError.value)
|
|
134
|
+
return Promise.reject(alreadyDestroyedError.value);
|
|
138
135
|
if (wasDestroyed.destroyed)
|
|
139
136
|
return Promise.reject(new AlreadyDestroyedError());
|
|
140
137
|
if (options.disableJsonRpc)
|
|
@@ -142,8 +139,8 @@ export function start(options, platformBindings) {
|
|
|
142
139
|
return instance.nextJsonRpcResponse(chainId);
|
|
143
140
|
},
|
|
144
141
|
remove: () => {
|
|
145
|
-
if (alreadyDestroyedError)
|
|
146
|
-
throw alreadyDestroyedError;
|
|
142
|
+
if (alreadyDestroyedError.value)
|
|
143
|
+
throw alreadyDestroyedError.value;
|
|
147
144
|
if (wasDestroyed.destroyed)
|
|
148
145
|
throw new AlreadyDestroyedError();
|
|
149
146
|
wasDestroyed.destroyed = true;
|
|
@@ -156,9 +153,9 @@ export function start(options, platformBindings) {
|
|
|
156
153
|
return newChain;
|
|
157
154
|
}),
|
|
158
155
|
terminate: () => __awaiter(this, void 0, void 0, function* () {
|
|
159
|
-
if (alreadyDestroyedError)
|
|
160
|
-
throw alreadyDestroyedError;
|
|
161
|
-
alreadyDestroyedError = new AlreadyDestroyedError();
|
|
156
|
+
if (alreadyDestroyedError.value)
|
|
157
|
+
throw alreadyDestroyedError.value;
|
|
158
|
+
alreadyDestroyedError.value = new AlreadyDestroyedError();
|
|
162
159
|
instance.startShutdown();
|
|
163
160
|
})
|
|
164
161
|
};
|
|
@@ -25,6 +25,7 @@ import { start as innerStart } from './client.js';
|
|
|
25
25
|
import { ConnectionError } from './instance/instance.js';
|
|
26
26
|
import { classicDecode, multibaseBase64Decode } from './base64.js';
|
|
27
27
|
import { inflate } from 'pako';
|
|
28
|
+
import { default as wasmBase64 } from './instance/autogen/wasm.js';
|
|
28
29
|
export { AddChainError, AlreadyDestroyedError, CrashError, JsonRpcDisabledError, MalformedJsonRpcError, QueueFullError } from './client.js';
|
|
29
30
|
/**
|
|
30
31
|
* Initializes a new client. This is a pre-requisite to connecting to a blockchain.
|
|
@@ -35,10 +36,12 @@ export { AddChainError, AlreadyDestroyedError, CrashError, JsonRpcDisabledError,
|
|
|
35
36
|
*/
|
|
36
37
|
export function start(options) {
|
|
37
38
|
options = options || {};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
// The actual Wasm bytecode is base64-decoded then deflate-decoded from a constant found in a
|
|
40
|
+
// different file.
|
|
41
|
+
// This is suboptimal compared to using `instantiateStreaming`, but it is the most
|
|
42
|
+
// cross-platform cross-bundler approach.
|
|
43
|
+
const wasmModule = WebAssembly.compile(inflate(classicDecode(wasmBase64)));
|
|
44
|
+
return innerStart(options, wasmModule, {
|
|
42
45
|
registerShouldPeriodicallyYield: (callback) => {
|
|
43
46
|
if (typeof document === 'undefined') // We might be in a web worker.
|
|
44
47
|
return [false, () => { }];
|
|
@@ -53,7 +56,16 @@ export function start(options) {
|
|
|
53
56
|
const crypto = globalThis.crypto;
|
|
54
57
|
if (!crypto)
|
|
55
58
|
throw new Error('randomness not available');
|
|
56
|
-
|
|
59
|
+
// Browsers have this completely undocumented behavior (it's not even part of a spec)
|
|
60
|
+
// that for some reason `getRandomValues` can't be called on arrayviews back by
|
|
61
|
+
// `SharedArrayBuffer`s and they throw an exception if you try.
|
|
62
|
+
if (buffer.buffer instanceof ArrayBuffer)
|
|
63
|
+
crypto.getRandomValues(buffer);
|
|
64
|
+
else {
|
|
65
|
+
const tmpArray = new Uint8Array(buffer.length);
|
|
66
|
+
crypto.getRandomValues(tmpArray);
|
|
67
|
+
buffer.set(tmpArray);
|
|
68
|
+
}
|
|
57
69
|
},
|
|
58
70
|
connect: (config) => {
|
|
59
71
|
return connect(config, (options === null || options === void 0 ? void 0 : options.forbidWs) || false, (options === null || options === void 0 ? void 0 : options.forbidNonLocalWs) || false, (options === null || options === void 0 ? void 0 : options.forbidWss) || false, (options === null || options === void 0 ? void 0 : options.forbidWebRtc) || false);
|