smoldot 0.3.6 → 0.7.11
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 +18 -21
- package/dist/cjs/base64.d.ts +22 -0
- package/dist/cjs/base64.js +104 -0
- package/dist/cjs/client.d.ts +287 -0
- package/dist/cjs/client.js +175 -0
- package/dist/cjs/index-browser.d.ts +11 -0
- package/dist/cjs/index-browser.js +465 -0
- package/dist/cjs/index-deno.d.ts +10 -0
- package/dist/cjs/index-deno.js +263 -0
- package/dist/cjs/index-nodejs.d.ts +10 -0
- package/dist/cjs/index-nodejs.js +174 -0
- package/dist/cjs/instance/autogen/wasm.d.ts +2 -0
- package/dist/cjs/instance/autogen/wasm.js +6 -0
- package/dist/cjs/instance/autogen/wasm0.d.ts +1 -0
- package/dist/cjs/instance/autogen/wasm0.js +4 -0
- package/dist/cjs/instance/autogen/wasm1.d.ts +1 -0
- package/dist/cjs/instance/autogen/wasm1.js +4 -0
- package/dist/cjs/instance/autogen/wasm2.d.ts +1 -0
- package/dist/cjs/instance/autogen/wasm2.js +4 -0
- package/dist/cjs/instance/bindings-smoldot-light.d.ts +154 -0
- package/dist/cjs/instance/bindings-smoldot-light.js +266 -0
- package/dist/cjs/instance/bindings-wasi.d.ts +23 -0
- package/dist/cjs/instance/bindings-wasi.js +142 -0
- package/dist/cjs/instance/bindings.d.ts +31 -0
- package/dist/cjs/instance/bindings.js +5 -0
- package/dist/cjs/instance/buffer.d.ts +10 -0
- package/dist/cjs/instance/buffer.js +53 -0
- package/dist/cjs/instance/instance.d.ts +45 -0
- package/dist/cjs/instance/instance.js +301 -0
- package/dist/cjs/instance/raw-instance.d.ts +65 -0
- package/dist/cjs/instance/raw-instance.js +72 -0
- package/dist/cjs/package.json +3 -0
- package/dist/mjs/base64.d.ts +22 -0
- package/dist/mjs/base64.js +97 -0
- package/dist/mjs/client.d.ts +287 -0
- package/dist/mjs/client.js +165 -0
- package/dist/mjs/index-browser.d.ts +11 -0
- package/dist/mjs/index-browser.js +455 -0
- package/dist/mjs/index-deno.d.ts +10 -0
- package/dist/mjs/index-deno.js +253 -0
- package/dist/mjs/index-nodejs.d.ts +10 -0
- package/dist/mjs/index-nodejs.js +164 -0
- package/dist/mjs/instance/autogen/wasm.d.ts +2 -0
- package/dist/mjs/instance/autogen/wasm.js +4 -0
- package/dist/mjs/instance/autogen/wasm0.d.ts +1 -0
- package/dist/mjs/instance/autogen/wasm0.js +1 -0
- package/dist/mjs/instance/autogen/wasm1.d.ts +1 -0
- package/dist/mjs/instance/autogen/wasm1.js +1 -0
- package/dist/mjs/instance/autogen/wasm2.d.ts +1 -0
- package/dist/mjs/instance/autogen/wasm2.js +1 -0
- package/dist/mjs/instance/bindings-smoldot-light.d.ts +154 -0
- package/dist/mjs/instance/bindings-smoldot-light.js +261 -0
- package/dist/mjs/instance/bindings-wasi.d.ts +23 -0
- package/{src → dist/mjs/instance}/bindings-wasi.js +47 -58
- package/dist/mjs/instance/bindings.d.ts +31 -0
- package/dist/mjs/instance/bindings.js +4 -0
- package/dist/mjs/instance/buffer.d.ts +10 -0
- package/dist/mjs/instance/buffer.js +46 -0
- package/dist/mjs/instance/instance.d.ts +45 -0
- package/dist/mjs/instance/instance.js +293 -0
- package/dist/mjs/instance/raw-instance.d.ts +65 -0
- package/dist/mjs/instance/raw-instance.js +67 -0
- package/dist/mjs/package.json +3 -0
- package/package.json +35 -19
- package/src/autogen/wasm.js +0 -1
- package/src/bindings-smoldot-js.js +0 -223
- package/src/compat-browser.js +0 -25
- package/src/compat-nodejs.js +0 -30
- package/src/health.js +0 -206
- package/src/index.d.ts +0 -68
- package/src/index.js +0 -192
- package/src/index.test-d.ts +0 -36
- package/src/tsconfig.json +0 -13
- package/src/worker.js +0 -151
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.writeUInt32LE = exports.writeUInt8 = exports.readUInt32LE = exports.utf8BytesToString = void 0;
|
|
7
|
+
// This program is free software: you can redistribute it and/or modify
|
|
8
|
+
// it under the terms of the GNU General Public License as published by
|
|
9
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
// (at your option) any later version.
|
|
11
|
+
// This program is distributed in the hope that it will be useful,
|
|
12
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
// GNU General Public License for more details.
|
|
15
|
+
// You should have received a copy of the GNU General Public License
|
|
16
|
+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
function utf8BytesToString(buffer, offset, length) {
|
|
18
|
+
checkRange(buffer, offset, length);
|
|
19
|
+
// The `TextDecoder` API is supported by all major browsers and by NodeJS.
|
|
20
|
+
// <https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder>
|
|
21
|
+
return new TextDecoder().decode(buffer.slice(offset, offset + length));
|
|
22
|
+
}
|
|
23
|
+
exports.utf8BytesToString = utf8BytesToString;
|
|
24
|
+
function readUInt32LE(buffer, offset) {
|
|
25
|
+
checkRange(buffer, offset, 4);
|
|
26
|
+
return (buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16)) + (buffer[offset + 3] * 0x1000000);
|
|
27
|
+
}
|
|
28
|
+
exports.readUInt32LE = readUInt32LE;
|
|
29
|
+
/**
|
|
30
|
+
* Sets the value of a given byte in the buffer.
|
|
31
|
+
*
|
|
32
|
+
* This function is equivalent to `buffer[offset] = value`, except that an exception is thrown
|
|
33
|
+
* if `offset` is out of range.
|
|
34
|
+
*/
|
|
35
|
+
function writeUInt8(buffer, offset, value) {
|
|
36
|
+
checkRange(buffer, offset, 1);
|
|
37
|
+
buffer[offset] = value & 0xff;
|
|
38
|
+
}
|
|
39
|
+
exports.writeUInt8 = writeUInt8;
|
|
40
|
+
function writeUInt32LE(buffer, offset, value) {
|
|
41
|
+
checkRange(buffer, offset, 4);
|
|
42
|
+
buffer[offset + 3] = (value >>> 24) & 0xff;
|
|
43
|
+
buffer[offset + 2] = (value >>> 16) & 0xff;
|
|
44
|
+
buffer[offset + 1] = (value >>> 8) & 0xff;
|
|
45
|
+
buffer[offset] = value & 0xff;
|
|
46
|
+
}
|
|
47
|
+
exports.writeUInt32LE = writeUInt32LE;
|
|
48
|
+
function checkRange(buffer, offset, length) {
|
|
49
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
50
|
+
throw new RangeError();
|
|
51
|
+
if (offset + length > buffer.length)
|
|
52
|
+
throw new RangeError();
|
|
53
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as instance from './raw-instance.js';
|
|
2
|
+
export { PlatformBindings, ConnectionError, ConnectionConfig, Connection } from './raw-instance.js';
|
|
3
|
+
/**
|
|
4
|
+
* Thrown in case the underlying client encounters an unexpected crash.
|
|
5
|
+
*
|
|
6
|
+
* This is always an internal bug in smoldot and is never supposed to happen.
|
|
7
|
+
*/
|
|
8
|
+
export declare class CrashError extends Error {
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Thrown in case a malformed JSON-RPC request is sent.
|
|
13
|
+
*/
|
|
14
|
+
export declare class MalformedJsonRpcError extends Error {
|
|
15
|
+
constructor();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Thrown in case the buffer of JSON-RPC requests is full and cannot accept any more request.
|
|
19
|
+
*/
|
|
20
|
+
export declare class QueueFullError extends Error {
|
|
21
|
+
constructor();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Contains the configuration of the instance.
|
|
25
|
+
*/
|
|
26
|
+
export interface Config {
|
|
27
|
+
logCallback: (level: number, target: string, message: string) => void;
|
|
28
|
+
maxLogLevel: number;
|
|
29
|
+
enableCurrentTask: boolean;
|
|
30
|
+
cpuRateLimit: number;
|
|
31
|
+
}
|
|
32
|
+
export interface Instance {
|
|
33
|
+
request: (request: string, chainId: number) => void;
|
|
34
|
+
nextJsonRpcResponse: (chainId: number) => Promise<string>;
|
|
35
|
+
addChain: (chainSpec: string, databaseContent: string, potentialRelayChains: number[], disableJsonRpc: boolean) => Promise<{
|
|
36
|
+
success: true;
|
|
37
|
+
chainId: number;
|
|
38
|
+
} | {
|
|
39
|
+
success: false;
|
|
40
|
+
error: string;
|
|
41
|
+
}>;
|
|
42
|
+
removeChain: (chainId: number) => void;
|
|
43
|
+
startShutdown: () => void;
|
|
44
|
+
}
|
|
45
|
+
export declare function start(configMessage: Config, platformBindings: instance.PlatformBindings): Instance;
|
|
@@ -0,0 +1,301 @@
|
|
|
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.start = exports.QueueFullError = exports.MalformedJsonRpcError = exports.CrashError = exports.ConnectionError = 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
|
+
const instance = require("./raw-instance.js");
|
|
28
|
+
const client_js_1 = require("../client.js");
|
|
29
|
+
var raw_instance_js_1 = require("./raw-instance.js");
|
|
30
|
+
Object.defineProperty(exports, "ConnectionError", { enumerable: true, get: function () { return raw_instance_js_1.ConnectionError; } });
|
|
31
|
+
/**
|
|
32
|
+
* Thrown in case the underlying client encounters an unexpected crash.
|
|
33
|
+
*
|
|
34
|
+
* This is always an internal bug in smoldot and is never supposed to happen.
|
|
35
|
+
*/
|
|
36
|
+
class CrashError extends Error {
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.CrashError = CrashError;
|
|
42
|
+
/**
|
|
43
|
+
* Thrown in case a malformed JSON-RPC request is sent.
|
|
44
|
+
*/
|
|
45
|
+
class MalformedJsonRpcError extends Error {
|
|
46
|
+
constructor() {
|
|
47
|
+
super("JSON-RPC request is malformed");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.MalformedJsonRpcError = MalformedJsonRpcError;
|
|
51
|
+
/**
|
|
52
|
+
* Thrown in case the buffer of JSON-RPC requests is full and cannot accept any more request.
|
|
53
|
+
*/
|
|
54
|
+
class QueueFullError extends Error {
|
|
55
|
+
constructor() {
|
|
56
|
+
super("JSON-RPC requests queue is full");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.QueueFullError = QueueFullError;
|
|
60
|
+
function start(configMessage, platformBindings) {
|
|
61
|
+
// This variable represents the state of the instance, and serves two different purposes:
|
|
62
|
+
//
|
|
63
|
+
// - At initialization, it is a Promise containing the Wasm VM is still initializing.
|
|
64
|
+
// - After the Wasm VM has finished initialization, contains the `WebAssembly.Instance` object.
|
|
65
|
+
//
|
|
66
|
+
let state;
|
|
67
|
+
const crashError = {};
|
|
68
|
+
const currentTask = { name: null };
|
|
69
|
+
const printError = { printError: true };
|
|
70
|
+
// Contains the information of each chain that is currently alive.
|
|
71
|
+
let chains = new Map();
|
|
72
|
+
// Start initialization of the Wasm VM.
|
|
73
|
+
const config = {
|
|
74
|
+
onWasmPanic: (message) => {
|
|
75
|
+
// TODO: consider obtaining a backtrace here
|
|
76
|
+
crashError.error = new CrashError(message);
|
|
77
|
+
if (!printError.printError)
|
|
78
|
+
return;
|
|
79
|
+
console.error("Smoldot has panicked" +
|
|
80
|
+
(currentTask.name ? (" while executing task `" + currentTask.name + "`") : "") +
|
|
81
|
+
". This is a bug in smoldot. Please open an issue at " +
|
|
82
|
+
"https://github.com/smol-dot/smoldot/issues with the following message:\n" +
|
|
83
|
+
message);
|
|
84
|
+
for (const chain of Array.from(chains.values())) {
|
|
85
|
+
for (const promise of chain.jsonRpcResponsesPromises) {
|
|
86
|
+
promise.reject(crashError.error);
|
|
87
|
+
}
|
|
88
|
+
chain.jsonRpcResponsesPromises = [];
|
|
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) => {
|
|
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);
|
|
125
|
+
}
|
|
126
|
+
catch (_error) { }
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
instance.exports.init(configMessage.maxLogLevel, configMessage.enableCurrentTask ? 1 : 0, cpuRateLimit, periodicallyYield ? 1 : 0);
|
|
130
|
+
state = { initialized: true, instance, unregisterCallback };
|
|
131
|
+
return instance;
|
|
132
|
+
})
|
|
133
|
+
};
|
|
134
|
+
function queueOperation(operation) {
|
|
135
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
// What to do depends on the type of `state`.
|
|
137
|
+
// See the documentation of the `state` variable for information.
|
|
138
|
+
if (!state.initialized) {
|
|
139
|
+
// A message has been received while the Wasm VM is still initializing. Queue it for when
|
|
140
|
+
// initialization is over.
|
|
141
|
+
return state.promise.then((instance) => operation(instance));
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// Everything is already initialized. Process the message synchronously.
|
|
145
|
+
return operation(state.instance);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
request: (request, chainId) => {
|
|
151
|
+
// Because `request` is passed as parameter an identifier returned by `addChain`, it is
|
|
152
|
+
// always the case that the Wasm instance is already initialized. The only possibility for
|
|
153
|
+
// it to not be the case is if the user completely invented the `chainId`.
|
|
154
|
+
if (!state.initialized)
|
|
155
|
+
throw new Error("Internal error");
|
|
156
|
+
if (crashError.error)
|
|
157
|
+
throw crashError.error;
|
|
158
|
+
let retVal;
|
|
159
|
+
try {
|
|
160
|
+
const encoded = new TextEncoder().encode(request);
|
|
161
|
+
const ptr = state.instance.exports.alloc(encoded.length) >>> 0;
|
|
162
|
+
new Uint8Array(state.instance.exports.memory.buffer).set(encoded, ptr);
|
|
163
|
+
retVal = state.instance.exports.json_rpc_send(ptr, encoded.length, chainId) >>> 0;
|
|
164
|
+
}
|
|
165
|
+
catch (_error) {
|
|
166
|
+
console.assert(crashError.error);
|
|
167
|
+
throw crashError.error;
|
|
168
|
+
}
|
|
169
|
+
switch (retVal) {
|
|
170
|
+
case 0: break;
|
|
171
|
+
case 1: throw new MalformedJsonRpcError();
|
|
172
|
+
case 2: throw new QueueFullError();
|
|
173
|
+
default: throw new Error("Internal error: unknown json_rpc_send error code: " + retVal);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
nextJsonRpcResponse: (chainId) => __awaiter(this, void 0, void 0, function* () {
|
|
177
|
+
// Because `nextJsonRpcResponse` is passed as parameter an identifier returned by `addChain`,
|
|
178
|
+
// it is always the case that the Wasm instance is already initialized. The only possibility
|
|
179
|
+
// for it to not be the case is if the user completely invented the `chainId`.
|
|
180
|
+
if (!state.initialized)
|
|
181
|
+
throw new Error("Internal error");
|
|
182
|
+
while (true) {
|
|
183
|
+
if (crashError.error)
|
|
184
|
+
throw crashError.error;
|
|
185
|
+
// Try to pop a message from the queue.
|
|
186
|
+
try {
|
|
187
|
+
const mem = new Uint8Array(state.instance.exports.memory.buffer);
|
|
188
|
+
const responseInfo = state.instance.exports.json_rpc_responses_peek(chainId) >>> 0;
|
|
189
|
+
const ptr = buffer.readUInt32LE(mem, responseInfo) >>> 0;
|
|
190
|
+
const len = buffer.readUInt32LE(mem, responseInfo + 4) >>> 0;
|
|
191
|
+
// `len === 0` means "queue is empty" according to the API.
|
|
192
|
+
// In that situation, queue the resolve/reject.
|
|
193
|
+
if (len !== 0) {
|
|
194
|
+
const message = buffer.utf8BytesToString(mem, ptr, len);
|
|
195
|
+
state.instance.exports.json_rpc_responses_pop(chainId);
|
|
196
|
+
return message;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (_error) {
|
|
200
|
+
console.assert(crashError.error);
|
|
201
|
+
throw crashError.error;
|
|
202
|
+
}
|
|
203
|
+
// If no message is available, wait for one to be.
|
|
204
|
+
yield new Promise((resolve, reject) => {
|
|
205
|
+
chains.get(chainId).jsonRpcResponsesPromises.push({ resolve: () => resolve(undefined), reject });
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}),
|
|
209
|
+
addChain: (chainSpec, databaseContent, potentialRelayChains, disableJsonRpc) => {
|
|
210
|
+
return queueOperation((instance) => {
|
|
211
|
+
if (crashError.error)
|
|
212
|
+
throw crashError.error;
|
|
213
|
+
try {
|
|
214
|
+
// Write the chain specification into memory.
|
|
215
|
+
const chainSpecEncoded = new TextEncoder().encode(chainSpec);
|
|
216
|
+
const chainSpecPtr = instance.exports.alloc(chainSpecEncoded.length) >>> 0;
|
|
217
|
+
new Uint8Array(instance.exports.memory.buffer).set(chainSpecEncoded, chainSpecPtr);
|
|
218
|
+
// Write the database content into memory.
|
|
219
|
+
const databaseContentEncoded = new TextEncoder().encode(databaseContent);
|
|
220
|
+
const databaseContentPtr = instance.exports.alloc(databaseContentEncoded.length) >>> 0;
|
|
221
|
+
new Uint8Array(instance.exports.memory.buffer).set(databaseContentEncoded, databaseContentPtr);
|
|
222
|
+
// Write the potential relay chains into memory.
|
|
223
|
+
const potentialRelayChainsLen = potentialRelayChains.length;
|
|
224
|
+
const potentialRelayChainsPtr = instance.exports.alloc(potentialRelayChainsLen * 4) >>> 0;
|
|
225
|
+
for (let idx = 0; idx < potentialRelayChains.length; ++idx) {
|
|
226
|
+
buffer.writeUInt32LE(new Uint8Array(instance.exports.memory.buffer), potentialRelayChainsPtr + idx * 4, potentialRelayChains[idx]);
|
|
227
|
+
}
|
|
228
|
+
// `add_chain` unconditionally allocates a chain id. If an error occurs, however, this chain
|
|
229
|
+
// id will refer to an *erroneous* chain. `chain_is_ok` is used below to determine whether it
|
|
230
|
+
// has succeeeded or not.
|
|
231
|
+
// Note that `add_chain` properly de-allocates buffers even if it failed.
|
|
232
|
+
const chainId = instance.exports.add_chain(chainSpecPtr, chainSpecEncoded.length, databaseContentPtr, databaseContentEncoded.length, disableJsonRpc ? 0 : 1, potentialRelayChainsPtr, potentialRelayChainsLen);
|
|
233
|
+
if (instance.exports.chain_is_ok(chainId) != 0) {
|
|
234
|
+
console.assert(!chains.has(chainId));
|
|
235
|
+
chains.set(chainId, {
|
|
236
|
+
jsonRpcResponsesPromises: new Array()
|
|
237
|
+
});
|
|
238
|
+
return { success: true, chainId };
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
const errorMsgLen = instance.exports.chain_error_len(chainId) >>> 0;
|
|
242
|
+
const errorMsgPtr = instance.exports.chain_error_ptr(chainId) >>> 0;
|
|
243
|
+
const errorMsg = buffer.utf8BytesToString(new Uint8Array(instance.exports.memory.buffer), errorMsgPtr, errorMsgLen);
|
|
244
|
+
instance.exports.remove_chain(chainId);
|
|
245
|
+
return { success: false, error: errorMsg };
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (_error) {
|
|
249
|
+
console.assert(crashError.error);
|
|
250
|
+
throw crashError.error;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
},
|
|
254
|
+
removeChain: (chainId) => {
|
|
255
|
+
// Because `removeChain` is passed as parameter an identifier returned by `addChain`, it is
|
|
256
|
+
// always the case that the Wasm instance is already initialized. The only possibility for
|
|
257
|
+
// it to not be the case is if the user completely invented the `chainId`.
|
|
258
|
+
if (!state.initialized)
|
|
259
|
+
throw new Error("Internal error");
|
|
260
|
+
if (crashError.error)
|
|
261
|
+
throw crashError.error;
|
|
262
|
+
// Removing the chain synchronously avoids having to deal with race conditions such as a
|
|
263
|
+
// JSON-RPC response corresponding to a chain that is going to be deleted but hasn't been yet.
|
|
264
|
+
// These kind of race conditions are already delt with within smoldot.
|
|
265
|
+
console.assert(chains.has(chainId));
|
|
266
|
+
for (const { reject } of chains.get(chainId).jsonRpcResponsesPromises) {
|
|
267
|
+
reject(new client_js_1.AlreadyDestroyedError());
|
|
268
|
+
}
|
|
269
|
+
chains.delete(chainId);
|
|
270
|
+
try {
|
|
271
|
+
state.instance.exports.remove_chain(chainId);
|
|
272
|
+
}
|
|
273
|
+
catch (_error) {
|
|
274
|
+
console.assert(crashError.error);
|
|
275
|
+
throw crashError.error;
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
startShutdown: () => {
|
|
279
|
+
return queueOperation((instance) => {
|
|
280
|
+
// `startShutdown` is a bit special in its handling of crashes.
|
|
281
|
+
// Shutting down will lead to `onWasmPanic` being called at some point, possibly during
|
|
282
|
+
// the call to `start_shutdown` itself. As such, we move into "don't print errors anymore"
|
|
283
|
+
// mode even before calling `start_shutdown`.
|
|
284
|
+
//
|
|
285
|
+
// Furthermore, if a crash happened in the past, there is no point in throwing an
|
|
286
|
+
// exception when the user wants the shutdown to happen.
|
|
287
|
+
if (crashError.error)
|
|
288
|
+
return;
|
|
289
|
+
if (state.initialized)
|
|
290
|
+
state.unregisterCallback();
|
|
291
|
+
try {
|
|
292
|
+
printError.printError = false;
|
|
293
|
+
instance.exports.start_shutdown();
|
|
294
|
+
}
|
|
295
|
+
catch (_error) {
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
exports.start = start;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ConnectionConfig, Connection } from './bindings-smoldot-light.js';
|
|
2
|
+
import { SmoldotWasmInstance } from './bindings.js';
|
|
3
|
+
export { ConnectionConfig, ConnectionError, Connection } from './bindings-smoldot-light.js';
|
|
4
|
+
export interface Config {
|
|
5
|
+
/**
|
|
6
|
+
* Closure to call when the Wasm instance panics.
|
|
7
|
+
*
|
|
8
|
+
* This callback will always be invoked from within a binding called the Wasm instance.
|
|
9
|
+
*
|
|
10
|
+
* After this callback has been called, it is forbidden to invoke any function from the Wasm
|
|
11
|
+
* VM.
|
|
12
|
+
*
|
|
13
|
+
* If this callback is called while invoking a function from the Wasm VM, this function will
|
|
14
|
+
* throw a dummy exception.
|
|
15
|
+
*/
|
|
16
|
+
onWasmPanic: (message: string) => void;
|
|
17
|
+
logCallback: (level: number, target: string, message: string) => void;
|
|
18
|
+
jsonRpcResponsesNonEmptyCallback: (chainId: number) => void;
|
|
19
|
+
currentTaskCallback?: (taskName: string | null) => void;
|
|
20
|
+
cpuRateLimit: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Contains functions that the client will use when it needs to leverage the platform.
|
|
24
|
+
*/
|
|
25
|
+
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
|
+
/**
|
|
38
|
+
* Returns the number of milliseconds since an arbitrary epoch.
|
|
39
|
+
*/
|
|
40
|
+
performanceNow: () => number;
|
|
41
|
+
/**
|
|
42
|
+
* Fills the given buffer with randomly-generated bytes.
|
|
43
|
+
*/
|
|
44
|
+
getRandomValues: (buffer: Uint8Array) => void;
|
|
45
|
+
/**
|
|
46
|
+
* The "should periodically yield" setting indicates whether the execution should periodically
|
|
47
|
+
* yield back control by using `setTimeout(..., 0)`.
|
|
48
|
+
*
|
|
49
|
+
* On platforms such as browsers where `setTimeout(..., 0)` takes way more than 0 milliseconds,
|
|
50
|
+
* this setting should be set to `false`.
|
|
51
|
+
*
|
|
52
|
+
* This function registers a callback that is called when the setting should be updated with
|
|
53
|
+
* a new value. It returns the initial value of the setting and a function used to unregister
|
|
54
|
+
* the callback.
|
|
55
|
+
*/
|
|
56
|
+
registerShouldPeriodicallyYield: (callback: (newValue: boolean) => void) => [boolean, () => void];
|
|
57
|
+
/**
|
|
58
|
+
* Tries to open a new connection using the given configuration.
|
|
59
|
+
*
|
|
60
|
+
* @see Connection
|
|
61
|
+
* @throws {@link ConnectionError} If the multiaddress couldn't be parsed or contains an invalid protocol.
|
|
62
|
+
*/
|
|
63
|
+
connect(config: ConnectionConfig): Connection;
|
|
64
|
+
}
|
|
65
|
+
export declare function startInstance(config: Config, platformBindings: PlatformBindings): Promise<SmoldotWasmInstance>;
|
|
@@ -0,0 +1,72 @@
|
|
|
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.startInstance = exports.ConnectionError = 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 bindings_smoldot_light_js_1 = require("./bindings-smoldot-light.js");
|
|
27
|
+
const bindings_wasi_js_1 = require("./bindings-wasi.js");
|
|
28
|
+
const wasm_js_1 = require("./autogen/wasm.js");
|
|
29
|
+
var bindings_smoldot_light_js_2 = require("./bindings-smoldot-light.js");
|
|
30
|
+
Object.defineProperty(exports, "ConnectionError", { enumerable: true, get: function () { return bindings_smoldot_light_js_2.ConnectionError; } });
|
|
31
|
+
function startInstance(config, platformBindings) {
|
|
32
|
+
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
|
+
let killAll;
|
|
39
|
+
// Used to bind with the smoldot-light bindings. See the `bindings-smoldot-light.js` file.
|
|
40
|
+
const smoldotJsConfig = Object.assign({ performanceNow: platformBindings.performanceNow, connect: platformBindings.connect, onPanic: (message) => {
|
|
41
|
+
killAll();
|
|
42
|
+
config.onWasmPanic(message);
|
|
43
|
+
throw new Error();
|
|
44
|
+
} }, config);
|
|
45
|
+
// Used to bind with the Wasi bindings. See the `bindings-wasi.js` file.
|
|
46
|
+
const wasiConfig = {
|
|
47
|
+
envVars: [],
|
|
48
|
+
getRandomValues: platformBindings.getRandomValues,
|
|
49
|
+
onProcExit: (retCode) => {
|
|
50
|
+
killAll();
|
|
51
|
+
config.onWasmPanic(`proc_exit called: ${retCode}`);
|
|
52
|
+
throw new Error();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const { imports: smoldotBindings, killAll: smoldotBindingsKillAll } = (0, bindings_smoldot_light_js_1.default)(smoldotJsConfig);
|
|
56
|
+
killAll = smoldotBindingsKillAll;
|
|
57
|
+
// Start the Wasm virtual machine.
|
|
58
|
+
// The Rust code defines a list of imports that must be fulfilled by the environment. The second
|
|
59
|
+
// parameter provides their implementations.
|
|
60
|
+
const result = yield WebAssembly.instantiate(wasmBytecode, {
|
|
61
|
+
// The functions with the "smoldot" prefix are specific to smoldot.
|
|
62
|
+
"smoldot": smoldotBindings,
|
|
63
|
+
// As the Rust code is compiled for wasi, some more wasi-specific imports exist.
|
|
64
|
+
"wasi_snapshot_preview1": (0, bindings_wasi_js_1.default)(wasiConfig),
|
|
65
|
+
});
|
|
66
|
+
const instance = result.instance;
|
|
67
|
+
smoldotJsConfig.instance = instance;
|
|
68
|
+
wasiConfig.instance = instance;
|
|
69
|
+
return instance;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
exports.startInstance = startInstance;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decodes a multibase-encoded string.
|
|
3
|
+
*
|
|
4
|
+
* Throws an exception if the encoding isn't base64 or one of its variants.
|
|
5
|
+
*/
|
|
6
|
+
export declare function multibaseBase64Decode(input: string): Uint8Array;
|
|
7
|
+
/**
|
|
8
|
+
* Decodes a base64-encoded string into bytes using the original alphabet from RFC4648.
|
|
9
|
+
*
|
|
10
|
+
* See <https://datatracker.ietf.org/doc/html/rfc4648#section-4>.
|
|
11
|
+
*/
|
|
12
|
+
export declare function classicDecode(input: string): Uint8Array;
|
|
13
|
+
/**
|
|
14
|
+
* Decodes a base64-encoded string into bytes using the URL-safe alphabet.
|
|
15
|
+
*
|
|
16
|
+
* See <https://datatracker.ietf.org/doc/html/rfc4648#section-5>.
|
|
17
|
+
*/
|
|
18
|
+
export declare function urlSafeDecode(input: string): Uint8Array;
|
|
19
|
+
/**
|
|
20
|
+
* Decodes a base64-encoded string into bytes using the given alphabet.
|
|
21
|
+
*/
|
|
22
|
+
export declare function base64Decode(input: string, alphabet: Map<string, number>): Uint8Array;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Smoldot
|
|
2
|
+
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
|
4
|
+
// This program is free software: you can redistribute it and/or modify
|
|
5
|
+
// it under the terms of the GNU General Public License as published by
|
|
6
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
// (at your option) any later version.
|
|
8
|
+
// This program is distributed in the hope that it will be useful,
|
|
9
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
// GNU General Public License for more details.
|
|
12
|
+
// You should have received a copy of the GNU General Public License
|
|
13
|
+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
14
|
+
let rfc4648Alphabet = new Map();
|
|
15
|
+
const rfc4648AlphabetAsStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
16
|
+
for (let i = 0; i < rfc4648AlphabetAsStr.length; ++i) {
|
|
17
|
+
rfc4648Alphabet.set(rfc4648AlphabetAsStr[i], i);
|
|
18
|
+
}
|
|
19
|
+
let urlSafeAlphabet = new Map();
|
|
20
|
+
const urlSafeAlphabetAsStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
21
|
+
for (let i = 0; i < urlSafeAlphabetAsStr.length; ++i) {
|
|
22
|
+
urlSafeAlphabet.set(urlSafeAlphabetAsStr[i], i);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Decodes a multibase-encoded string.
|
|
26
|
+
*
|
|
27
|
+
* Throws an exception if the encoding isn't base64 or one of its variants.
|
|
28
|
+
*/
|
|
29
|
+
export function multibaseBase64Decode(input) {
|
|
30
|
+
if (input.length === 0)
|
|
31
|
+
throw new Error("Invalid multibase");
|
|
32
|
+
switch (input[0]) {
|
|
33
|
+
case 'm':
|
|
34
|
+
case 'M':
|
|
35
|
+
return classicDecode(input.slice(1));
|
|
36
|
+
case 'u':
|
|
37
|
+
case 'U':
|
|
38
|
+
return urlSafeDecode(input.slice(1));
|
|
39
|
+
default:
|
|
40
|
+
throw new Error('Unknown multibase prefix: ' + input[0]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Decodes a base64-encoded string into bytes using the original alphabet from RFC4648.
|
|
45
|
+
*
|
|
46
|
+
* See <https://datatracker.ietf.org/doc/html/rfc4648#section-4>.
|
|
47
|
+
*/
|
|
48
|
+
export function classicDecode(input) {
|
|
49
|
+
return base64Decode(input, rfc4648Alphabet);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Decodes a base64-encoded string into bytes using the URL-safe alphabet.
|
|
53
|
+
*
|
|
54
|
+
* See <https://datatracker.ietf.org/doc/html/rfc4648#section-5>.
|
|
55
|
+
*/
|
|
56
|
+
export function urlSafeDecode(input) {
|
|
57
|
+
return base64Decode(input, urlSafeAlphabet);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Decodes a base64-encoded string into bytes using the given alphabet.
|
|
61
|
+
*/
|
|
62
|
+
export function base64Decode(input, alphabet) {
|
|
63
|
+
// Remove the padding bytes at the end of the string. We don't check whether the padding is
|
|
64
|
+
// accurate.
|
|
65
|
+
while (input.length !== 0 && input[input.length - 1] === '=')
|
|
66
|
+
input = input.slice(0, -1);
|
|
67
|
+
// Contains the output data.
|
|
68
|
+
const out = new Uint8Array(Math.floor(input.length * 6 / 8));
|
|
69
|
+
// Position within `out` of the next byte to write.
|
|
70
|
+
let outPos = 0;
|
|
71
|
+
// The bits decoded from the input are added to the right of this value.
|
|
72
|
+
let currentByte = 0;
|
|
73
|
+
// The left-most `validBitsInCurrentByte` bits of `currentByte` must be written out.
|
|
74
|
+
let validBitsInCurrentByte = 0;
|
|
75
|
+
for (let i = 0; i < input.length; ++i) {
|
|
76
|
+
const inputChr = input[i];
|
|
77
|
+
const bitsToAppend = alphabet.get(inputChr);
|
|
78
|
+
if (bitsToAppend === undefined)
|
|
79
|
+
throw new Error('Invalid base64 character: ' + inputChr);
|
|
80
|
+
console.assert(bitsToAppend < (1 << 6));
|
|
81
|
+
currentByte = (currentByte << 6) | bitsToAppend;
|
|
82
|
+
validBitsInCurrentByte += 6;
|
|
83
|
+
if (validBitsInCurrentByte >= 8) {
|
|
84
|
+
let outByte = currentByte >> (validBitsInCurrentByte - 8);
|
|
85
|
+
out[outPos] = outByte;
|
|
86
|
+
outPos += 1;
|
|
87
|
+
validBitsInCurrentByte -= 8;
|
|
88
|
+
}
|
|
89
|
+
console.assert(validBitsInCurrentByte < 8);
|
|
90
|
+
currentByte &= 0xff;
|
|
91
|
+
}
|
|
92
|
+
if ((currentByte & ((1 << validBitsInCurrentByte) - 1)) !== 0)
|
|
93
|
+
throw new Error("Unexpected EOF");
|
|
94
|
+
if (validBitsInCurrentByte >= 6)
|
|
95
|
+
throw new Error("Unexpected EOF");
|
|
96
|
+
return out;
|
|
97
|
+
}
|