smoldot 1.0.4 → 1.0.6

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.
Files changed (116) hide show
  1. package/README.md +58 -0
  2. package/dist/cjs/bytecode-browser.d.ts +5 -0
  3. package/dist/cjs/bytecode-browser.js +42 -0
  4. package/dist/cjs/bytecode-deno.d.ts +5 -0
  5. package/dist/cjs/bytecode-deno.js +87 -0
  6. package/dist/cjs/bytecode-nodejs.d.ts +5 -0
  7. package/dist/cjs/bytecode-nodejs.js +41 -0
  8. package/dist/cjs/index-browser.d.ts +2 -2
  9. package/dist/cjs/index-browser.js +10 -507
  10. package/dist/cjs/index-deno.d.ts +2 -2
  11. package/dist/cjs/index-deno.js +10 -283
  12. package/dist/cjs/index-nodejs.d.ts +2 -2
  13. package/dist/cjs/index-nodejs.js +10 -211
  14. package/dist/cjs/internals/bytecode/wasm0.js +4 -0
  15. package/dist/cjs/internals/bytecode/wasm1.js +4 -0
  16. package/dist/cjs/internals/bytecode/wasm2.js +4 -0
  17. package/dist/cjs/{instance/bindings-smoldot-light.d.ts → internals/client.d.ts} +15 -34
  18. package/dist/cjs/internals/client.js +376 -0
  19. package/dist/cjs/internals/local-instance.d.ts +144 -0
  20. package/dist/cjs/internals/local-instance.js +566 -0
  21. package/dist/cjs/internals/remote-instance.d.ts +38 -0
  22. package/dist/cjs/internals/remote-instance.js +374 -0
  23. package/dist/cjs/no-auto-bytecode-browser.d.ts +11 -0
  24. package/dist/cjs/no-auto-bytecode-browser.js +481 -0
  25. package/dist/cjs/no-auto-bytecode-deno.d.ts +10 -0
  26. package/dist/cjs/no-auto-bytecode-deno.js +220 -0
  27. package/dist/cjs/no-auto-bytecode-nodejs.d.ts +10 -0
  28. package/dist/cjs/no-auto-bytecode-nodejs.js +189 -0
  29. package/dist/cjs/{client.d.ts → public-types.d.ts} +51 -3
  30. package/dist/cjs/public-types.js +76 -0
  31. package/dist/cjs/worker-browser.d.ts +11 -0
  32. package/dist/cjs/worker-browser.js +63 -0
  33. package/dist/cjs/worker-deno.d.ts +11 -0
  34. package/dist/cjs/worker-deno.js +54 -0
  35. package/dist/cjs/worker-nodejs.d.ts +11 -0
  36. package/dist/cjs/worker-nodejs.js +55 -0
  37. package/dist/mjs/bytecode-browser.d.ts +5 -0
  38. package/dist/mjs/bytecode-browser.js +38 -0
  39. package/dist/mjs/bytecode-deno.d.ts +5 -0
  40. package/dist/mjs/bytecode-deno.js +83 -0
  41. package/dist/mjs/bytecode-nodejs.d.ts +5 -0
  42. package/dist/mjs/bytecode-nodejs.js +37 -0
  43. package/dist/mjs/index-browser.d.ts +2 -2
  44. package/dist/mjs/index-browser.js +4 -501
  45. package/dist/mjs/index-deno.d.ts +2 -2
  46. package/dist/mjs/index-deno.js +4 -277
  47. package/dist/mjs/index-nodejs.d.ts +2 -2
  48. package/dist/mjs/index-nodejs.js +4 -205
  49. package/dist/mjs/internals/bytecode/wasm0.js +1 -0
  50. package/dist/mjs/internals/bytecode/wasm1.js +1 -0
  51. package/dist/mjs/internals/bytecode/wasm2.js +1 -0
  52. package/dist/mjs/{instance/bindings-smoldot-light.d.ts → internals/client.d.ts} +15 -34
  53. package/dist/mjs/internals/client.js +372 -0
  54. package/dist/mjs/internals/local-instance.d.ts +144 -0
  55. package/dist/mjs/internals/local-instance.js +561 -0
  56. package/dist/mjs/internals/remote-instance.d.ts +38 -0
  57. package/dist/mjs/internals/remote-instance.js +369 -0
  58. package/dist/mjs/no-auto-bytecode-browser.d.ts +11 -0
  59. package/dist/mjs/no-auto-bytecode-browser.js +471 -0
  60. package/dist/mjs/no-auto-bytecode-deno.d.ts +10 -0
  61. package/dist/mjs/no-auto-bytecode-deno.js +210 -0
  62. package/dist/mjs/no-auto-bytecode-nodejs.d.ts +10 -0
  63. package/dist/mjs/no-auto-bytecode-nodejs.js +179 -0
  64. package/dist/mjs/{client.d.ts → public-types.d.ts} +51 -3
  65. package/dist/mjs/public-types.js +67 -0
  66. package/dist/mjs/worker-browser.d.ts +11 -0
  67. package/dist/mjs/worker-browser.js +59 -0
  68. package/dist/mjs/worker-deno.d.ts +11 -0
  69. package/dist/mjs/worker-deno.js +50 -0
  70. package/dist/mjs/worker-nodejs.d.ts +11 -0
  71. package/dist/mjs/worker-nodejs.js +51 -0
  72. package/package.json +40 -8
  73. package/dist/cjs/client.js +0 -172
  74. package/dist/cjs/instance/autogen/wasm0.js +0 -4
  75. package/dist/cjs/instance/autogen/wasm1.js +0 -4
  76. package/dist/cjs/instance/autogen/wasm2.js +0 -4
  77. package/dist/cjs/instance/bindings-smoldot-light.js +0 -285
  78. package/dist/cjs/instance/bindings-wasi.d.ts +0 -27
  79. package/dist/cjs/instance/bindings-wasi.js +0 -172
  80. package/dist/cjs/instance/bindings.d.ts +0 -31
  81. package/dist/cjs/instance/bindings.js +0 -5
  82. package/dist/cjs/instance/instance.d.ts +0 -45
  83. package/dist/cjs/instance/instance.js +0 -277
  84. package/dist/cjs/instance/raw-instance.d.ts +0 -56
  85. package/dist/cjs/instance/raw-instance.js +0 -129
  86. package/dist/mjs/client.js +0 -162
  87. package/dist/mjs/instance/autogen/wasm0.js +0 -1
  88. package/dist/mjs/instance/autogen/wasm1.js +0 -1
  89. package/dist/mjs/instance/autogen/wasm2.js +0 -1
  90. package/dist/mjs/instance/bindings-smoldot-light.js +0 -280
  91. package/dist/mjs/instance/bindings-wasi.d.ts +0 -27
  92. package/dist/mjs/instance/bindings-wasi.js +0 -170
  93. package/dist/mjs/instance/bindings.d.ts +0 -31
  94. package/dist/mjs/instance/bindings.js +0 -4
  95. package/dist/mjs/instance/instance.d.ts +0 -45
  96. package/dist/mjs/instance/instance.js +0 -269
  97. package/dist/mjs/instance/raw-instance.d.ts +0 -56
  98. package/dist/mjs/instance/raw-instance.js +0 -124
  99. /package/dist/cjs/{base64.d.ts → internals/base64.d.ts} +0 -0
  100. /package/dist/cjs/{base64.js → internals/base64.js} +0 -0
  101. /package/dist/cjs/{instance → internals}/buffer.d.ts +0 -0
  102. /package/dist/cjs/{instance → internals}/buffer.js +0 -0
  103. /package/dist/cjs/{instance/autogen → internals/bytecode}/wasm.d.ts +0 -0
  104. /package/dist/cjs/{instance/autogen → internals/bytecode}/wasm.js +0 -0
  105. /package/dist/cjs/{instance/autogen → internals/bytecode}/wasm0.d.ts +0 -0
  106. /package/dist/cjs/{instance/autogen → internals/bytecode}/wasm1.d.ts +0 -0
  107. /package/dist/cjs/{instance/autogen → internals/bytecode}/wasm2.d.ts +0 -0
  108. /package/dist/mjs/{base64.d.ts → internals/base64.d.ts} +0 -0
  109. /package/dist/mjs/{base64.js → internals/base64.js} +0 -0
  110. /package/dist/mjs/{instance → internals}/buffer.d.ts +0 -0
  111. /package/dist/mjs/{instance → internals}/buffer.js +0 -0
  112. /package/dist/mjs/{instance/autogen → internals/bytecode}/wasm.d.ts +0 -0
  113. /package/dist/mjs/{instance/autogen → internals/bytecode}/wasm.js +0 -0
  114. /package/dist/mjs/{instance/autogen → internals/bytecode}/wasm0.d.ts +0 -0
  115. /package/dist/mjs/{instance/autogen → internals/bytecode}/wasm1.d.ts +0 -0
  116. /package/dist/mjs/{instance/autogen → internals/bytecode}/wasm2.d.ts +0 -0
@@ -0,0 +1,566 @@
1
+ "use strict";
2
+ // Smoldot
3
+ // Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
4
+ // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
6
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
7
+ return new (P || (P = Promise))(function (resolve, reject) {
8
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
9
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
10
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
11
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
12
+ });
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.parseMultiaddr = exports.startLocalInstance = void 0;
16
+ // This program is free software: you can redistribute it and/or modify
17
+ // it under the terms of the GNU General Public License as published by
18
+ // the Free Software Foundation, either version 3 of the License, or
19
+ // (at your option) any later version.
20
+ // This program is distributed in the hope that it will be useful,
21
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
22
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
+ // GNU General Public License for more details.
24
+ // You should have received a copy of the GNU General Public License
25
+ // along with this program. If not, see <http://www.gnu.org/licenses/>.
26
+ const buffer = require("./buffer.js");
27
+ /**
28
+ * Starts a new instance using the given configuration.
29
+ *
30
+ * Even though this function doesn't do anything asynchronous, it needs to be asynchronous due to
31
+ * the fact that `WebAssembly.instantiate` is for some reason asynchronous.
32
+ *
33
+ * After this function returns, the execution of CPU-heavy tasks of smoldot will happen
34
+ * asynchronously in the background.
35
+ *
36
+ * This instance is low-level in the sense that invalid input can lead to crashes and that input
37
+ * isn't sanitized. In other words, you know what you're doing.
38
+ */
39
+ function startLocalInstance(config, wasmModule, eventCallback) {
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ const state = {
42
+ instance: null,
43
+ bufferIndices: new Array(),
44
+ advanceExecutionPromise: null,
45
+ stdoutBuffer: "",
46
+ stderrBuffer: "",
47
+ onShutdownExecutorOrWasmPanic: () => { }
48
+ };
49
+ const smoldotJsBindings = {
50
+ // Must exit with an error. A human-readable message can be found in the WebAssembly
51
+ // memory in the given buffer.
52
+ panic: (ptr, len) => {
53
+ const instance = state.instance;
54
+ state.instance = null;
55
+ ptr >>>= 0;
56
+ len >>>= 0;
57
+ const message = buffer.utf8BytesToString(new Uint8Array(instance.exports.memory.buffer), ptr, len);
58
+ eventCallback({ ty: "wasm-panic", message });
59
+ state.onShutdownExecutorOrWasmPanic();
60
+ state.onShutdownExecutorOrWasmPanic = () => { };
61
+ throw new Error();
62
+ },
63
+ buffer_size: (bufferIndex) => {
64
+ const buf = state.bufferIndices[bufferIndex];
65
+ return buf.byteLength;
66
+ },
67
+ buffer_copy: (bufferIndex, targetPtr) => {
68
+ const instance = state.instance;
69
+ targetPtr = targetPtr >>> 0;
70
+ const buf = state.bufferIndices[bufferIndex];
71
+ new Uint8Array(instance.exports.memory.buffer).set(buf, targetPtr);
72
+ },
73
+ advance_execution_ready: () => {
74
+ if (state.advanceExecutionPromise)
75
+ state.advanceExecutionPromise();
76
+ state.advanceExecutionPromise = null;
77
+ },
78
+ // Used by the Rust side to notify that a JSON-RPC response or subscription notification
79
+ // is available in the queue of JSON-RPC responses.
80
+ json_rpc_responses_non_empty: (chainId) => {
81
+ eventCallback({ ty: "json-rpc-responses-non-empty", chainId });
82
+ },
83
+ // Used by the Rust side to emit a log entry.
84
+ // See also the `max_log_level` parameter in the configuration.
85
+ log: (level, targetPtr, targetLen, messagePtr, messageLen) => {
86
+ const instance = state.instance;
87
+ targetPtr >>>= 0;
88
+ targetLen >>>= 0;
89
+ messagePtr >>>= 0;
90
+ messageLen >>>= 0;
91
+ const mem = new Uint8Array(instance.exports.memory.buffer);
92
+ let target = buffer.utf8BytesToString(mem, targetPtr, targetLen);
93
+ let message = buffer.utf8BytesToString(mem, messagePtr, messageLen);
94
+ eventCallback({ ty: "log", level, message, target });
95
+ },
96
+ // Must call `timer_finished` after the given number of milliseconds has elapsed.
97
+ start_timer: (ms) => {
98
+ const instance = state.instance;
99
+ // In both NodeJS and browsers, if `setTimeout` is called with a value larger than
100
+ // 2147483647, the delay is for some reason instead set to 1.
101
+ // As mentioned in the documentation of `start_timer`, it is acceptable to end the
102
+ // timer before the given number of milliseconds has passed.
103
+ if (ms > 2147483647)
104
+ ms = 2147483647;
105
+ // In browsers, `setTimeout` works as expected when `ms` equals 0. However, NodeJS
106
+ // requires a minimum of 1 millisecond (if `0` is passed, it is automatically replaced
107
+ // with `1`) and wants you to use `setImmediate` instead.
108
+ if (ms < 1 && typeof setImmediate === "function") {
109
+ setImmediate(() => {
110
+ if (!state.instance)
111
+ return;
112
+ try {
113
+ instance.exports.timer_finished();
114
+ }
115
+ catch (_error) { }
116
+ });
117
+ }
118
+ else {
119
+ setTimeout(() => {
120
+ if (!state.instance)
121
+ return;
122
+ try {
123
+ instance.exports.timer_finished();
124
+ }
125
+ catch (_error) { }
126
+ }, ms);
127
+ }
128
+ },
129
+ // Must create a new connection object. This implementation stores the created object in
130
+ // `connections`.
131
+ connection_new: (connectionId, addrPtr, addrLen, errorBufferIndexPtr) => {
132
+ const instance = state.instance;
133
+ addrPtr >>>= 0;
134
+ addrLen >>>= 0;
135
+ errorBufferIndexPtr >>>= 0;
136
+ const address = buffer.utf8BytesToString(new Uint8Array(instance.exports.memory.buffer), addrPtr, addrLen);
137
+ // TODO: consider extracting config options so user can't change the fields dynamically
138
+ const result = parseMultiaddr(address, config.forbidTcp, config.forbidWs, config.forbidNonLocalWs, config.forbidWss, config.forbidWebRtc);
139
+ if (result.success) {
140
+ eventCallback({ ty: "new-connection", connectionId, address: result.address });
141
+ return 0;
142
+ }
143
+ else {
144
+ const mem = new Uint8Array(instance.exports.memory.buffer);
145
+ state.bufferIndices[0] = new TextEncoder().encode(result.error);
146
+ buffer.writeUInt32LE(mem, errorBufferIndexPtr, 0);
147
+ buffer.writeUInt8(mem, errorBufferIndexPtr + 4, 1); // TODO: remove isBadAddress param since it's always true
148
+ return 1;
149
+ }
150
+ },
151
+ // Must close and destroy the connection object.
152
+ reset_connection: (connectionId) => {
153
+ eventCallback({ ty: "connection-reset", connectionId });
154
+ },
155
+ // Opens a new substream on a multi-stream connection.
156
+ connection_stream_open: (connectionId) => {
157
+ eventCallback({ ty: "connection-stream-open", connectionId });
158
+ },
159
+ // Closes a substream on a multi-stream connection.
160
+ connection_stream_reset: (connectionId, streamId) => {
161
+ eventCallback({ ty: "connection-stream-reset", connectionId, streamId });
162
+ },
163
+ // Must queue the data found in the WebAssembly memory at the given pointer. It is assumed
164
+ // that this function is called only when the connection is in an open state.
165
+ stream_send: (connectionId, streamId, ptr, len) => {
166
+ const instance = state.instance;
167
+ ptr >>>= 0;
168
+ len >>>= 0;
169
+ const data = new Uint8Array(instance.exports.memory.buffer).slice(ptr, ptr + len);
170
+ // TODO: docs says the streamId is provided only for multi-stream connections, but here it's always provided
171
+ eventCallback({ ty: "stream-send", connectionId, streamId, data });
172
+ },
173
+ stream_send_close: (connectionId, streamId) => {
174
+ // TODO: docs says the streamId is provided only for multi-stream connections, but here it's always provided
175
+ eventCallback({ ty: "stream-send-close", connectionId, streamId });
176
+ },
177
+ current_task_entered: (ptr, len) => {
178
+ ptr >>>= 0;
179
+ len >>>= 0;
180
+ const taskName = buffer.utf8BytesToString(new Uint8Array(state.instance.exports.memory.buffer), ptr, len);
181
+ eventCallback({ ty: "current-task", taskName });
182
+ },
183
+ current_task_exit: () => {
184
+ eventCallback({ ty: "current-task", taskName: null });
185
+ }
186
+ };
187
+ const wasiBindings = {
188
+ // Need to fill the buffer described by `ptr` and `len` with random data.
189
+ // This data will be used in order to generate secrets. Do not use a dummy implementation!
190
+ random_get: (ptr, len) => {
191
+ const instance = state.instance;
192
+ ptr >>>= 0;
193
+ len >>>= 0;
194
+ const baseBuffer = new Uint8Array(instance.exports.memory.buffer)
195
+ .subarray(ptr, ptr + len);
196
+ for (let iter = 0; iter < len; iter += 65536) {
197
+ // `baseBuffer.subarray` automatically saturates at the end of the buffer
198
+ config.getRandomValues(baseBuffer.subarray(iter, iter + 65536));
199
+ }
200
+ return 0;
201
+ },
202
+ clock_time_get: (clockId, _precision, outPtr) => {
203
+ // See <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/wasi/time.rs>
204
+ // and <docs.rs/wasi/> for help.
205
+ const instance = state.instance;
206
+ const mem = new Uint8Array(instance.exports.memory.buffer);
207
+ outPtr >>>= 0;
208
+ // We ignore the precision, as it can't be implemented anyway.
209
+ switch (clockId) {
210
+ case 0: {
211
+ // Realtime clock.
212
+ const now = BigInt(Math.floor(Date.now())) * BigInt(1000000);
213
+ buffer.writeUInt64LE(mem, outPtr, now);
214
+ // Success.
215
+ return 0;
216
+ }
217
+ case 1: {
218
+ // Monotonic clock.
219
+ const nowMs = config.performanceNow();
220
+ const nowMsInt = Math.floor(nowMs);
221
+ const now = BigInt(nowMsInt) * BigInt(1000000) +
222
+ BigInt(Math.floor(((nowMs - nowMsInt) * 1000000)));
223
+ buffer.writeUInt64LE(mem, outPtr, now);
224
+ // Success.
225
+ return 0;
226
+ }
227
+ default:
228
+ // Return an `EINVAL` error.
229
+ return 28;
230
+ }
231
+ },
232
+ // Writing to a file descriptor is used in order to write to stdout/stderr.
233
+ fd_write: (fd, addr, num, outPtr) => {
234
+ const instance = state.instance;
235
+ outPtr >>>= 0;
236
+ // Only stdout and stderr are open for writing.
237
+ if (fd != 1 && fd != 2) {
238
+ return 8;
239
+ }
240
+ const mem = new Uint8Array(instance.exports.memory.buffer);
241
+ // `fd_write` passes a buffer containing itself a list of pointers and lengths to the
242
+ // actual buffers. See writev(2).
243
+ let toWrite = "";
244
+ let totalLength = 0;
245
+ for (let i = 0; i < num; i++) {
246
+ const buf = buffer.readUInt32LE(mem, addr + 4 * i * 2);
247
+ const bufLen = buffer.readUInt32LE(mem, addr + 4 * (i * 2 + 1));
248
+ toWrite += buffer.utf8BytesToString(mem, buf, bufLen);
249
+ totalLength += bufLen;
250
+ }
251
+ const flushBuffer = (string) => {
252
+ // As documented in the documentation of `println!`, lines are always split by a
253
+ // single `\n` in Rust.
254
+ while (true) {
255
+ const index = string.indexOf('\n');
256
+ if (index != -1) {
257
+ // Note that it is questionnable to use `console.log` from within a
258
+ // library. However this simply reflects the usage of `println!` in the
259
+ // Rust code. In other words, it is `println!` that shouldn't be used in
260
+ // the first place. The harm of not showing text printed with `println!`
261
+ // at all is greater than the harm possibly caused by accidentally leaving
262
+ // a `println!` in the code.
263
+ console.log(string.substring(0, index));
264
+ string = string.substring(index + 1);
265
+ }
266
+ else {
267
+ return string;
268
+ }
269
+ }
270
+ };
271
+ // Append the newly-written data to either `stdout_buffer` or `stderr_buffer`, and
272
+ // print their content if necessary.
273
+ if (fd == 1) {
274
+ state.stdoutBuffer += toWrite;
275
+ state.stdoutBuffer = flushBuffer(state.stdoutBuffer);
276
+ }
277
+ else if (fd == 2) {
278
+ state.stderrBuffer += toWrite;
279
+ state.stderrBuffer = flushBuffer(state.stderrBuffer);
280
+ }
281
+ // Need to write in `out_ptr` how much data was "written".
282
+ buffer.writeUInt32LE(mem, outPtr, totalLength);
283
+ return 0;
284
+ },
285
+ // It's unclear how to properly implement yielding, but a no-op works fine as well.
286
+ sched_yield: () => {
287
+ return 0;
288
+ },
289
+ // Used by Rust in catastrophic situations, such as a double panic.
290
+ proc_exit: (retCode) => {
291
+ state.instance = null;
292
+ eventCallback({ ty: "wasm-panic", message: `proc_exit called: ${retCode}` });
293
+ state.onShutdownExecutorOrWasmPanic();
294
+ state.onShutdownExecutorOrWasmPanic = () => { };
295
+ throw new Error();
296
+ },
297
+ // Return the number of environment variables and the total size of all environment
298
+ // variables. This is called in order to initialize buffers before `environ_get`.
299
+ environ_sizes_get: (argcOut, argvBufSizeOut) => {
300
+ const instance = state.instance;
301
+ argcOut >>>= 0;
302
+ argvBufSizeOut >>>= 0;
303
+ let totalLen = 0;
304
+ config.envVars.forEach(e => totalLen += new TextEncoder().encode(e).length + 1); // +1 for trailing \0
305
+ const mem = new Uint8Array(instance.exports.memory.buffer);
306
+ buffer.writeUInt32LE(mem, argcOut, config.envVars.length);
307
+ buffer.writeUInt32LE(mem, argvBufSizeOut, totalLen);
308
+ return 0;
309
+ },
310
+ // Write the environment variables to the given pointers.
311
+ // `argv` is a pointer to a buffer that must be overwritten with a list of pointers to
312
+ // environment variables, and `argvBuf` is a pointer to a buffer where to actually store
313
+ // the environment variables.
314
+ // The sizes of the buffers were determined by calling `environ_sizes_get`.
315
+ environ_get: (argv, argvBuf) => {
316
+ const instance = state.instance;
317
+ argv >>>= 0;
318
+ argvBuf >>>= 0;
319
+ const mem = new Uint8Array(instance.exports.memory.buffer);
320
+ let argvPos = 0;
321
+ let argvBufPos = 0;
322
+ config.envVars.forEach(envVar => {
323
+ const encoded = new TextEncoder().encode(envVar);
324
+ buffer.writeUInt32LE(mem, argv + argvPos, argvBuf + argvBufPos);
325
+ argvPos += 4;
326
+ mem.set(encoded, argvBuf + argvBufPos);
327
+ argvBufPos += encoded.length;
328
+ buffer.writeUInt8(mem, argvBuf + argvBufPos, 0);
329
+ argvBufPos += 1;
330
+ });
331
+ return 0;
332
+ },
333
+ };
334
+ // Start the Wasm virtual machine.
335
+ // The Rust code defines a list of imports that must be fulfilled by the environment. The second
336
+ // parameter provides their implementations.
337
+ const result = yield WebAssembly.instantiate(wasmModule, {
338
+ // The functions with the "smoldot" prefix are specific to smoldot.
339
+ "smoldot": smoldotJsBindings,
340
+ // As the Rust code is compiled for wasi, some more wasi-specific imports exist.
341
+ "wasi_snapshot_preview1": wasiBindings,
342
+ });
343
+ state.instance = result;
344
+ // Smoldot requires an initial call to the `init` function in order to do its internal
345
+ // configuration.
346
+ state.instance.exports.init(config.maxLogLevel);
347
+ // Promise that is notified when the `shutdownExecutor` function is called or when a Wasm
348
+ // panic happens.
349
+ const shutdownExecutorOrWasmPanicPromise = new Promise((resolve) => state.onShutdownExecutorOrWasmPanic = () => resolve("stop"));
350
+ (() => __awaiter(this, void 0, void 0, function* () {
351
+ const cpuRateLimit = config.cpuRateLimit;
352
+ // In order to avoid calling `setTimeout` too often, we accumulate sleep up until
353
+ // a certain threshold.
354
+ let missingSleep = 0;
355
+ let now = config.performanceNow();
356
+ while (true) {
357
+ const whenReadyAgain = new Promise((resolve) => state.advanceExecutionPromise = () => resolve("ready"));
358
+ if (!state.instance)
359
+ break;
360
+ state.instance.exports.advance_execution();
361
+ const afterExec = config.performanceNow();
362
+ const elapsed = afterExec - now;
363
+ now = afterExec;
364
+ // In order to enforce the rate limiting, we stop executing for a certain
365
+ // amount of time.
366
+ // The base equation here is: `(sleep + elapsed) * rateLimit == elapsed`,
367
+ // from which the calculation below is derived.
368
+ const sleep = elapsed * (1.0 / cpuRateLimit - 1.0);
369
+ missingSleep += sleep;
370
+ if (missingSleep > 5) {
371
+ // `setTimeout` has a maximum value, after which it will overflow. 🤦
372
+ // See <https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value>
373
+ // While adding a cap technically skews the CPU rate limiting algorithm, we don't
374
+ // really care for such extreme values.
375
+ if (missingSleep > 2147483646) // Doc says `> 2147483647`, but I don't really trust their pedanticism so let's be safe
376
+ missingSleep = 2147483646;
377
+ const sleepFinished = new Promise((resolve) => setTimeout(() => resolve("timeout"), missingSleep));
378
+ if ((yield Promise.race([sleepFinished, shutdownExecutorOrWasmPanicPromise])) === "stop")
379
+ break;
380
+ }
381
+ if ((yield Promise.race([whenReadyAgain, shutdownExecutorOrWasmPanicPromise])) === "stop")
382
+ break;
383
+ const afterWait = config.performanceNow();
384
+ // `afterWait - now` is equal to how long we've waited for the `setTimeout` callback to
385
+ // trigger. While in principle `afterWait - now` should be roughly equal to
386
+ // `missingSleep`, in reality `setTimeout` can take much longer than the parameter
387
+ // provided. See <https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#timeouts_in_inactive_tabs>.
388
+ // For this reason, `missingSleep` can become negative here. This is intended.
389
+ // However, we don't want to accumulate too much sleep. There should be a maximum
390
+ // amount of time during which the CPU executes without yielding. For this reason, we
391
+ // add a minimum bound for `missingSleep`.
392
+ missingSleep -= (afterWait - now);
393
+ if (missingSleep < -10000)
394
+ missingSleep = -10000;
395
+ now = afterWait;
396
+ }
397
+ if (!state.instance)
398
+ return;
399
+ eventCallback({ ty: "executor-shutdown" });
400
+ }))();
401
+ return {
402
+ request: (request, chainId) => {
403
+ if (!state.instance)
404
+ return 2; // TODO: return a different error code? should be documented
405
+ state.bufferIndices[0] = new TextEncoder().encode(request);
406
+ return state.instance.exports.json_rpc_send(0, chainId) >>> 0;
407
+ },
408
+ peekJsonRpcResponse: (chainId) => {
409
+ if (!state.instance)
410
+ return null;
411
+ const mem = new Uint8Array(state.instance.exports.memory.buffer);
412
+ const responseInfo = state.instance.exports.json_rpc_responses_peek(chainId) >>> 0;
413
+ const ptr = buffer.readUInt32LE(mem, responseInfo) >>> 0;
414
+ const len = buffer.readUInt32LE(mem, responseInfo + 4) >>> 0;
415
+ // `len === 0` means "queue is empty" according to the API.
416
+ // In that situation, queue the resolve/reject.
417
+ if (len !== 0) {
418
+ const message = buffer.utf8BytesToString(mem, ptr, len);
419
+ state.instance.exports.json_rpc_responses_pop(chainId);
420
+ return message;
421
+ }
422
+ else {
423
+ return null;
424
+ }
425
+ },
426
+ addChain: (chainSpec, databaseContent, potentialRelayChains, disableJsonRpc) => {
427
+ if (!state.instance) {
428
+ eventCallback({ ty: "add-chain-result", success: false, error: "Smoldot has crashed" });
429
+ return;
430
+ }
431
+ // `add_chain` unconditionally allocates a chain id. If an error occurs, however, this chain
432
+ // id will refer to an *erroneous* chain. `chain_is_ok` is used below to determine whether it
433
+ // has succeeeded or not.
434
+ state.bufferIndices[0] = new TextEncoder().encode(chainSpec);
435
+ state.bufferIndices[1] = new TextEncoder().encode(databaseContent);
436
+ const potentialRelayChainsEncoded = new Uint8Array(potentialRelayChains.length * 4);
437
+ for (let idx = 0; idx < potentialRelayChains.length; ++idx) {
438
+ buffer.writeUInt32LE(potentialRelayChainsEncoded, idx * 4, potentialRelayChains[idx]);
439
+ }
440
+ state.bufferIndices[2] = potentialRelayChainsEncoded;
441
+ const chainId = state.instance.exports.add_chain(0, 1, disableJsonRpc ? 0 : 1, 2);
442
+ delete state.bufferIndices[0];
443
+ delete state.bufferIndices[1];
444
+ delete state.bufferIndices[2];
445
+ if (state.instance.exports.chain_is_ok(chainId) != 0) {
446
+ eventCallback({ ty: "add-chain-result", success: true, chainId });
447
+ }
448
+ else {
449
+ const errorMsgLen = state.instance.exports.chain_error_len(chainId) >>> 0;
450
+ const errorMsgPtr = state.instance.exports.chain_error_ptr(chainId) >>> 0;
451
+ const errorMsg = buffer.utf8BytesToString(new Uint8Array(state.instance.exports.memory.buffer), errorMsgPtr, errorMsgLen);
452
+ state.instance.exports.remove_chain(chainId);
453
+ eventCallback({ ty: "add-chain-result", success: false, error: errorMsg });
454
+ }
455
+ },
456
+ removeChain: (chainId) => {
457
+ if (!state.instance)
458
+ return;
459
+ state.instance.exports.remove_chain(chainId);
460
+ },
461
+ shutdownExecutor: () => {
462
+ if (!state.instance)
463
+ return;
464
+ const cb = state.onShutdownExecutorOrWasmPanic;
465
+ state.onShutdownExecutorOrWasmPanic = () => { };
466
+ cb();
467
+ },
468
+ connectionOpened: (connectionId, info) => {
469
+ if (!state.instance)
470
+ return;
471
+ switch (info.type) {
472
+ case 'single-stream': {
473
+ state.instance.exports.connection_open_single_stream(connectionId, 0, info.initialWritableBytes, info.writeClosable ? 1 : 0);
474
+ break;
475
+ }
476
+ case 'multi-stream': {
477
+ const handshakeTy = new Uint8Array(1 + info.localTlsCertificateMultihash.length + info.remoteTlsCertificateMultihash.length);
478
+ buffer.writeUInt8(handshakeTy, 0, 0);
479
+ handshakeTy.set(info.localTlsCertificateMultihash, 1);
480
+ handshakeTy.set(info.remoteTlsCertificateMultihash, 1 + info.localTlsCertificateMultihash.length);
481
+ state.bufferIndices[0] = handshakeTy;
482
+ state.instance.exports.connection_open_multi_stream(connectionId, 0);
483
+ delete state.bufferIndices[0];
484
+ break;
485
+ }
486
+ }
487
+ },
488
+ connectionReset: (connectionId, message) => {
489
+ if (!state.instance)
490
+ return;
491
+ state.bufferIndices[0] = new TextEncoder().encode(message);
492
+ state.instance.exports.connection_reset(connectionId, 0);
493
+ delete state.bufferIndices[0];
494
+ },
495
+ streamWritableBytes: (connectionId, numExtra, streamId) => {
496
+ if (!state.instance)
497
+ return;
498
+ state.instance.exports.stream_writable_bytes(connectionId, streamId || 0, numExtra);
499
+ },
500
+ streamMessage: (connectionId, message, streamId) => {
501
+ if (!state.instance)
502
+ return;
503
+ state.bufferIndices[0] = message;
504
+ state.instance.exports.stream_message(connectionId, streamId || 0, 0);
505
+ delete state.bufferIndices[0];
506
+ },
507
+ streamOpened: (connectionId, streamId, direction, initialWritableBytes) => {
508
+ if (!state.instance)
509
+ return;
510
+ state.instance.exports.connection_stream_opened(connectionId, streamId, direction === 'outbound' ? 1 : 0, initialWritableBytes);
511
+ },
512
+ streamReset: (connectionId, streamId) => {
513
+ if (!state.instance)
514
+ return;
515
+ state.instance.exports.stream_reset(connectionId, streamId);
516
+ },
517
+ };
518
+ });
519
+ }
520
+ exports.startLocalInstance = startLocalInstance;
521
+ // TODO: consider moving this function somewhere else or something
522
+ function parseMultiaddr(address, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRtc) {
523
+ const tcpParsed = address.match(/^\/(ip4|ip6|dns4|dns6|dns)\/(.*?)\/tcp\/(.*?)$/);
524
+ // TODO: remove support for `/wss` in a long time (https://github.com/paritytech/smoldot/issues/1940)
525
+ const wsParsed = address.match(/^\/(ip4|ip6|dns4|dns6|dns)\/(.*?)\/tcp\/(.*?)\/(ws|wss|tls\/ws)$/);
526
+ const webRTCParsed = address.match(/^\/(ip4|ip6)\/(.*?)\/udp\/(.*?)\/webrtc-direct\/certhash\/(.*?)$/);
527
+ if (wsParsed != null) {
528
+ const proto = (wsParsed[4] == 'ws') ? 'ws' : 'wss';
529
+ if (proto == 'ws' && forbidWs) {
530
+ return { success: false, error: 'WebSocket connections not available' };
531
+ }
532
+ if (proto == 'wss' && forbidWss) {
533
+ return { success: false, error: 'WebSocket secure connections not available' };
534
+ }
535
+ if (proto == 'ws' && wsParsed[2] != 'localhost' && wsParsed[2] != '127.0.0.1' && forbidNonLocalWs) {
536
+ return { success: false, error: 'Non-local WebSocket connections not available' };
537
+ }
538
+ const url = (wsParsed[1] == 'ip6') ?
539
+ (proto + "://[" + wsParsed[2] + "]:" + wsParsed[3]) :
540
+ (proto + "://" + wsParsed[2] + ":" + wsParsed[3]);
541
+ return { success: true, address: { ty: "websocket", url } };
542
+ }
543
+ else if (tcpParsed != null) {
544
+ if (forbidTcp) {
545
+ return { success: false, error: 'TCP connections not available' };
546
+ }
547
+ return { success: true, address: { ty: "tcp", hostname: tcpParsed[2], port: parseInt(tcpParsed[3]) } };
548
+ }
549
+ else if (webRTCParsed != null) {
550
+ const targetPort = webRTCParsed[3];
551
+ if (forbidWebRtc) {
552
+ return { success: false, error: 'WebRTC connections not available' };
553
+ }
554
+ if (targetPort === '0') {
555
+ return { success: false, error: 'Invalid WebRTC target port' };
556
+ }
557
+ const ipVersion = webRTCParsed[1] == 'ip4' ? '4' : '6';
558
+ const targetIp = webRTCParsed[2];
559
+ const remoteCertMultibase = webRTCParsed[4];
560
+ return { success: true, address: { ty: "webrtc", targetPort, ipVersion, targetIp, remoteCertMultibase } };
561
+ }
562
+ else {
563
+ return { success: false, error: 'Unrecognized multiaddr format' };
564
+ }
565
+ }
566
+ exports.parseMultiaddr = parseMultiaddr;
@@ -0,0 +1,38 @@
1
+ import * as instance from './local-instance.js';
2
+ export interface ConnectConfig {
3
+ wasmModule: Promise<WebAssembly.Module>;
4
+ forbidTcp: boolean;
5
+ forbidWs: boolean;
6
+ forbidNonLocalWs: boolean;
7
+ forbidWss: boolean;
8
+ forbidWebRtc: boolean;
9
+ maxLogLevel: number;
10
+ cpuRateLimit: number;
11
+ portToServer: MessagePort;
12
+ eventCallback: (event: instance.Event) => void;
13
+ }
14
+ export declare function connectToInstanceServer(config: ConnectConfig): Promise<instance.Instance>;
15
+ /**
16
+ * Configuration for {@link startInstanceServer}.
17
+ */
18
+ export interface ServerConfig {
19
+ /**
20
+ * Environment variables that the instance can pull.
21
+ */
22
+ envVars: string[];
23
+ /**
24
+ * Returns the number of milliseconds since an arbitrary epoch.
25
+ */
26
+ performanceNow: () => number;
27
+ /**
28
+ * Fills the given buffer with randomly-generated bytes.
29
+ */
30
+ getRandomValues: (buffer: Uint8Array) => void;
31
+ }
32
+ /**
33
+ * Returns a `Promise` that resolves when the instance shuts down. Since the function is also
34
+ * an asynchronous function, the actual return type is `Promise<Promise<void>>`. That is, the
35
+ * outer `Promise` yields once the instance starts, and the inner `Promise` yields once the
36
+ * instance shuts down.
37
+ */
38
+ export declare function startInstanceServer(config: ServerConfig, initPortToClient: MessagePort): Promise<Promise<void>>;