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.
- package/README.md +58 -0
- package/dist/cjs/bytecode-browser.d.ts +5 -0
- package/dist/cjs/bytecode-browser.js +42 -0
- package/dist/cjs/bytecode-deno.d.ts +5 -0
- package/dist/cjs/bytecode-deno.js +87 -0
- package/dist/cjs/bytecode-nodejs.d.ts +5 -0
- package/dist/cjs/bytecode-nodejs.js +41 -0
- package/dist/cjs/index-browser.d.ts +2 -2
- package/dist/cjs/index-browser.js +10 -507
- package/dist/cjs/index-deno.d.ts +2 -2
- package/dist/cjs/index-deno.js +10 -283
- package/dist/cjs/index-nodejs.d.ts +2 -2
- package/dist/cjs/index-nodejs.js +10 -211
- package/dist/cjs/internals/bytecode/wasm0.js +4 -0
- package/dist/cjs/internals/bytecode/wasm1.js +4 -0
- package/dist/cjs/internals/bytecode/wasm2.js +4 -0
- package/dist/cjs/{instance/bindings-smoldot-light.d.ts → internals/client.d.ts} +15 -34
- package/dist/cjs/internals/client.js +376 -0
- package/dist/cjs/internals/local-instance.d.ts +144 -0
- package/dist/cjs/internals/local-instance.js +566 -0
- package/dist/cjs/internals/remote-instance.d.ts +38 -0
- package/dist/cjs/internals/remote-instance.js +374 -0
- package/dist/cjs/no-auto-bytecode-browser.d.ts +11 -0
- package/dist/cjs/no-auto-bytecode-browser.js +481 -0
- package/dist/cjs/no-auto-bytecode-deno.d.ts +10 -0
- package/dist/cjs/no-auto-bytecode-deno.js +220 -0
- package/dist/cjs/no-auto-bytecode-nodejs.d.ts +10 -0
- package/dist/cjs/no-auto-bytecode-nodejs.js +189 -0
- package/dist/cjs/{client.d.ts → public-types.d.ts} +51 -3
- package/dist/cjs/public-types.js +76 -0
- package/dist/cjs/worker-browser.d.ts +11 -0
- package/dist/cjs/worker-browser.js +63 -0
- package/dist/cjs/worker-deno.d.ts +11 -0
- package/dist/cjs/worker-deno.js +54 -0
- package/dist/cjs/worker-nodejs.d.ts +11 -0
- package/dist/cjs/worker-nodejs.js +55 -0
- package/dist/mjs/bytecode-browser.d.ts +5 -0
- package/dist/mjs/bytecode-browser.js +38 -0
- package/dist/mjs/bytecode-deno.d.ts +5 -0
- package/dist/mjs/bytecode-deno.js +83 -0
- package/dist/mjs/bytecode-nodejs.d.ts +5 -0
- package/dist/mjs/bytecode-nodejs.js +37 -0
- package/dist/mjs/index-browser.d.ts +2 -2
- package/dist/mjs/index-browser.js +4 -501
- package/dist/mjs/index-deno.d.ts +2 -2
- package/dist/mjs/index-deno.js +4 -277
- package/dist/mjs/index-nodejs.d.ts +2 -2
- package/dist/mjs/index-nodejs.js +4 -205
- package/dist/mjs/internals/bytecode/wasm0.js +1 -0
- package/dist/mjs/internals/bytecode/wasm1.js +1 -0
- package/dist/mjs/internals/bytecode/wasm2.js +1 -0
- package/dist/mjs/{instance/bindings-smoldot-light.d.ts → internals/client.d.ts} +15 -34
- package/dist/mjs/internals/client.js +372 -0
- package/dist/mjs/internals/local-instance.d.ts +144 -0
- package/dist/mjs/internals/local-instance.js +561 -0
- package/dist/mjs/internals/remote-instance.d.ts +38 -0
- package/dist/mjs/internals/remote-instance.js +369 -0
- package/dist/mjs/no-auto-bytecode-browser.d.ts +11 -0
- package/dist/mjs/no-auto-bytecode-browser.js +471 -0
- package/dist/mjs/no-auto-bytecode-deno.d.ts +10 -0
- package/dist/mjs/no-auto-bytecode-deno.js +210 -0
- package/dist/mjs/no-auto-bytecode-nodejs.d.ts +10 -0
- package/dist/mjs/no-auto-bytecode-nodejs.js +179 -0
- package/dist/mjs/{client.d.ts → public-types.d.ts} +51 -3
- package/dist/mjs/public-types.js +67 -0
- package/dist/mjs/worker-browser.d.ts +11 -0
- package/dist/mjs/worker-browser.js +59 -0
- package/dist/mjs/worker-deno.d.ts +11 -0
- package/dist/mjs/worker-deno.js +50 -0
- package/dist/mjs/worker-nodejs.d.ts +11 -0
- package/dist/mjs/worker-nodejs.js +51 -0
- package/package.json +40 -8
- package/dist/cjs/client.js +0 -172
- package/dist/cjs/instance/autogen/wasm0.js +0 -4
- package/dist/cjs/instance/autogen/wasm1.js +0 -4
- package/dist/cjs/instance/autogen/wasm2.js +0 -4
- package/dist/cjs/instance/bindings-smoldot-light.js +0 -285
- package/dist/cjs/instance/bindings-wasi.d.ts +0 -27
- package/dist/cjs/instance/bindings-wasi.js +0 -172
- package/dist/cjs/instance/bindings.d.ts +0 -31
- package/dist/cjs/instance/bindings.js +0 -5
- package/dist/cjs/instance/instance.d.ts +0 -45
- package/dist/cjs/instance/instance.js +0 -277
- package/dist/cjs/instance/raw-instance.d.ts +0 -56
- package/dist/cjs/instance/raw-instance.js +0 -129
- package/dist/mjs/client.js +0 -162
- package/dist/mjs/instance/autogen/wasm0.js +0 -1
- package/dist/mjs/instance/autogen/wasm1.js +0 -1
- package/dist/mjs/instance/autogen/wasm2.js +0 -1
- package/dist/mjs/instance/bindings-smoldot-light.js +0 -280
- package/dist/mjs/instance/bindings-wasi.d.ts +0 -27
- package/dist/mjs/instance/bindings-wasi.js +0 -170
- package/dist/mjs/instance/bindings.d.ts +0 -31
- package/dist/mjs/instance/bindings.js +0 -4
- package/dist/mjs/instance/instance.d.ts +0 -45
- package/dist/mjs/instance/instance.js +0 -269
- package/dist/mjs/instance/raw-instance.d.ts +0 -56
- package/dist/mjs/instance/raw-instance.js +0 -124
- /package/dist/cjs/{base64.d.ts → internals/base64.d.ts} +0 -0
- /package/dist/cjs/{base64.js → internals/base64.js} +0 -0
- /package/dist/cjs/{instance → internals}/buffer.d.ts +0 -0
- /package/dist/cjs/{instance → internals}/buffer.js +0 -0
- /package/dist/cjs/{instance/autogen → internals/bytecode}/wasm.d.ts +0 -0
- /package/dist/cjs/{instance/autogen → internals/bytecode}/wasm.js +0 -0
- /package/dist/cjs/{instance/autogen → internals/bytecode}/wasm0.d.ts +0 -0
- /package/dist/cjs/{instance/autogen → internals/bytecode}/wasm1.d.ts +0 -0
- /package/dist/cjs/{instance/autogen → internals/bytecode}/wasm2.d.ts +0 -0
- /package/dist/mjs/{base64.d.ts → internals/base64.d.ts} +0 -0
- /package/dist/mjs/{base64.js → internals/base64.js} +0 -0
- /package/dist/mjs/{instance → internals}/buffer.d.ts +0 -0
- /package/dist/mjs/{instance → internals}/buffer.js +0 -0
- /package/dist/mjs/{instance/autogen → internals/bytecode}/wasm.d.ts +0 -0
- /package/dist/mjs/{instance/autogen → internals/bytecode}/wasm.js +0 -0
- /package/dist/mjs/{instance/autogen → internals/bytecode}/wasm0.d.ts +0 -0
- /package/dist/mjs/{instance/autogen → internals/bytecode}/wasm1.d.ts +0 -0
- /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>>;
|