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,10 @@
|
|
|
1
|
+
export declare function utf8BytesToString(buffer: Uint8Array, offset: number, length: number): string;
|
|
2
|
+
export declare function readUInt32LE(buffer: Uint8Array, offset: number): number;
|
|
3
|
+
/**
|
|
4
|
+
* Sets the value of a given byte in the buffer.
|
|
5
|
+
*
|
|
6
|
+
* This function is equivalent to `buffer[offset] = value`, except that an exception is thrown
|
|
7
|
+
* if `offset` is out of range.
|
|
8
|
+
*/
|
|
9
|
+
export declare function writeUInt8(buffer: Uint8Array, offset: number, value: number): void;
|
|
10
|
+
export declare function writeUInt32LE(buffer: Uint8Array, offset: number, value: number): void;
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
export function utf8BytesToString(buffer, offset, length) {
|
|
15
|
+
checkRange(buffer, offset, length);
|
|
16
|
+
// The `TextDecoder` API is supported by all major browsers and by NodeJS.
|
|
17
|
+
// <https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder>
|
|
18
|
+
return new TextDecoder().decode(buffer.slice(offset, offset + length));
|
|
19
|
+
}
|
|
20
|
+
export function readUInt32LE(buffer, offset) {
|
|
21
|
+
checkRange(buffer, offset, 4);
|
|
22
|
+
return (buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16)) + (buffer[offset + 3] * 0x1000000);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sets the value of a given byte in the buffer.
|
|
26
|
+
*
|
|
27
|
+
* This function is equivalent to `buffer[offset] = value`, except that an exception is thrown
|
|
28
|
+
* if `offset` is out of range.
|
|
29
|
+
*/
|
|
30
|
+
export function writeUInt8(buffer, offset, value) {
|
|
31
|
+
checkRange(buffer, offset, 1);
|
|
32
|
+
buffer[offset] = value & 0xff;
|
|
33
|
+
}
|
|
34
|
+
export function writeUInt32LE(buffer, offset, value) {
|
|
35
|
+
checkRange(buffer, offset, 4);
|
|
36
|
+
buffer[offset + 3] = (value >>> 24) & 0xff;
|
|
37
|
+
buffer[offset + 2] = (value >>> 16) & 0xff;
|
|
38
|
+
buffer[offset + 1] = (value >>> 8) & 0xff;
|
|
39
|
+
buffer[offset] = value & 0xff;
|
|
40
|
+
}
|
|
41
|
+
function checkRange(buffer, offset, length) {
|
|
42
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
43
|
+
throw new RangeError();
|
|
44
|
+
if (offset + length > buffer.length)
|
|
45
|
+
throw new RangeError();
|
|
46
|
+
}
|
|
@@ -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,293 @@
|
|
|
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
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
5
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
6
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
7
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
8
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
9
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
10
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
// This program is free software: you can redistribute it and/or modify
|
|
14
|
+
// it under the terms of the GNU General Public License as published by
|
|
15
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
16
|
+
// (at your option) any later version.
|
|
17
|
+
// This program is distributed in the hope that it will be useful,
|
|
18
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
19
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
20
|
+
// GNU General Public License for more details.
|
|
21
|
+
// You should have received a copy of the GNU General Public License
|
|
22
|
+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
|
+
import * as buffer from './buffer.js';
|
|
24
|
+
import * as instance from './raw-instance.js';
|
|
25
|
+
import { AlreadyDestroyedError } from '../client.js';
|
|
26
|
+
export { ConnectionError } from './raw-instance.js';
|
|
27
|
+
/**
|
|
28
|
+
* Thrown in case the underlying client encounters an unexpected crash.
|
|
29
|
+
*
|
|
30
|
+
* This is always an internal bug in smoldot and is never supposed to happen.
|
|
31
|
+
*/
|
|
32
|
+
export class CrashError extends Error {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super(message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Thrown in case a malformed JSON-RPC request is sent.
|
|
39
|
+
*/
|
|
40
|
+
export class MalformedJsonRpcError extends Error {
|
|
41
|
+
constructor() {
|
|
42
|
+
super("JSON-RPC request is malformed");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Thrown in case the buffer of JSON-RPC requests is full and cannot accept any more request.
|
|
47
|
+
*/
|
|
48
|
+
export class QueueFullError extends Error {
|
|
49
|
+
constructor() {
|
|
50
|
+
super("JSON-RPC requests queue is full");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function start(configMessage, platformBindings) {
|
|
54
|
+
// This variable represents the state of the instance, and serves two different purposes:
|
|
55
|
+
//
|
|
56
|
+
// - At initialization, it is a Promise containing the Wasm VM is still initializing.
|
|
57
|
+
// - After the Wasm VM has finished initialization, contains the `WebAssembly.Instance` object.
|
|
58
|
+
//
|
|
59
|
+
let state;
|
|
60
|
+
const crashError = {};
|
|
61
|
+
const currentTask = { name: null };
|
|
62
|
+
const printError = { printError: true };
|
|
63
|
+
// Contains the information of each chain that is currently alive.
|
|
64
|
+
let chains = new Map();
|
|
65
|
+
// Start initialization of the Wasm VM.
|
|
66
|
+
const config = {
|
|
67
|
+
onWasmPanic: (message) => {
|
|
68
|
+
// TODO: consider obtaining a backtrace here
|
|
69
|
+
crashError.error = new CrashError(message);
|
|
70
|
+
if (!printError.printError)
|
|
71
|
+
return;
|
|
72
|
+
console.error("Smoldot has panicked" +
|
|
73
|
+
(currentTask.name ? (" while executing task `" + currentTask.name + "`") : "") +
|
|
74
|
+
". This is a bug in smoldot. Please open an issue at " +
|
|
75
|
+
"https://github.com/smol-dot/smoldot/issues with the following message:\n" +
|
|
76
|
+
message);
|
|
77
|
+
for (const chain of Array.from(chains.values())) {
|
|
78
|
+
for (const promise of chain.jsonRpcResponsesPromises) {
|
|
79
|
+
promise.reject(crashError.error);
|
|
80
|
+
}
|
|
81
|
+
chain.jsonRpcResponsesPromises = [];
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
logCallback: (level, target, message) => {
|
|
85
|
+
configMessage.logCallback(level, target, message);
|
|
86
|
+
},
|
|
87
|
+
jsonRpcResponsesNonEmptyCallback: (chainId) => {
|
|
88
|
+
// Notify every single promise found in `jsonRpcResponsesPromises`.
|
|
89
|
+
const promises = chains.get(chainId).jsonRpcResponsesPromises;
|
|
90
|
+
while (promises.length !== 0) {
|
|
91
|
+
promises.shift().resolve();
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
currentTaskCallback: (taskName) => {
|
|
95
|
+
currentTask.name = taskName;
|
|
96
|
+
},
|
|
97
|
+
cpuRateLimit: configMessage.cpuRateLimit,
|
|
98
|
+
};
|
|
99
|
+
state = {
|
|
100
|
+
initialized: false, promise: instance.startInstance(config, platformBindings).then((instance) => {
|
|
101
|
+
// `config.cpuRateLimit` is a floating point that should be between 0 and 1, while the value
|
|
102
|
+
// to pass as parameter must be between `0` and `2^32-1`.
|
|
103
|
+
// The few lines of code below should handle all possible values of `number`, including
|
|
104
|
+
// infinites and NaN.
|
|
105
|
+
let cpuRateLimit = Math.round(config.cpuRateLimit * 4294967295); // `2^32 - 1`
|
|
106
|
+
if (cpuRateLimit < 0)
|
|
107
|
+
cpuRateLimit = 0;
|
|
108
|
+
if (cpuRateLimit > 4294967295)
|
|
109
|
+
cpuRateLimit = 4294967295;
|
|
110
|
+
if (!Number.isFinite(cpuRateLimit))
|
|
111
|
+
cpuRateLimit = 4294967295; // User might have passed NaN
|
|
112
|
+
// Smoldot requires an initial call to the `init` function in order to do its internal
|
|
113
|
+
// configuration.
|
|
114
|
+
const [periodicallyYield, unregisterCallback] = platformBindings.registerShouldPeriodicallyYield((newValue) => {
|
|
115
|
+
if (state.initialized && !crashError.error) {
|
|
116
|
+
try {
|
|
117
|
+
state.instance.exports.set_periodically_yield(newValue ? 1 : 0);
|
|
118
|
+
}
|
|
119
|
+
catch (_error) { }
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
instance.exports.init(configMessage.maxLogLevel, configMessage.enableCurrentTask ? 1 : 0, cpuRateLimit, periodicallyYield ? 1 : 0);
|
|
123
|
+
state = { initialized: true, instance, unregisterCallback };
|
|
124
|
+
return instance;
|
|
125
|
+
})
|
|
126
|
+
};
|
|
127
|
+
function queueOperation(operation) {
|
|
128
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
129
|
+
// What to do depends on the type of `state`.
|
|
130
|
+
// See the documentation of the `state` variable for information.
|
|
131
|
+
if (!state.initialized) {
|
|
132
|
+
// A message has been received while the Wasm VM is still initializing. Queue it for when
|
|
133
|
+
// initialization is over.
|
|
134
|
+
return state.promise.then((instance) => operation(instance));
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// Everything is already initialized. Process the message synchronously.
|
|
138
|
+
return operation(state.instance);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
request: (request, chainId) => {
|
|
144
|
+
// Because `request` is passed as parameter an identifier returned by `addChain`, it is
|
|
145
|
+
// always the case that the Wasm instance is already initialized. The only possibility for
|
|
146
|
+
// it to not be the case is if the user completely invented the `chainId`.
|
|
147
|
+
if (!state.initialized)
|
|
148
|
+
throw new Error("Internal error");
|
|
149
|
+
if (crashError.error)
|
|
150
|
+
throw crashError.error;
|
|
151
|
+
let retVal;
|
|
152
|
+
try {
|
|
153
|
+
const encoded = new TextEncoder().encode(request);
|
|
154
|
+
const ptr = state.instance.exports.alloc(encoded.length) >>> 0;
|
|
155
|
+
new Uint8Array(state.instance.exports.memory.buffer).set(encoded, ptr);
|
|
156
|
+
retVal = state.instance.exports.json_rpc_send(ptr, encoded.length, chainId) >>> 0;
|
|
157
|
+
}
|
|
158
|
+
catch (_error) {
|
|
159
|
+
console.assert(crashError.error);
|
|
160
|
+
throw crashError.error;
|
|
161
|
+
}
|
|
162
|
+
switch (retVal) {
|
|
163
|
+
case 0: break;
|
|
164
|
+
case 1: throw new MalformedJsonRpcError();
|
|
165
|
+
case 2: throw new QueueFullError();
|
|
166
|
+
default: throw new Error("Internal error: unknown json_rpc_send error code: " + retVal);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
nextJsonRpcResponse: (chainId) => __awaiter(this, void 0, void 0, function* () {
|
|
170
|
+
// Because `nextJsonRpcResponse` is passed as parameter an identifier returned by `addChain`,
|
|
171
|
+
// it is always the case that the Wasm instance is already initialized. The only possibility
|
|
172
|
+
// for it to not be the case is if the user completely invented the `chainId`.
|
|
173
|
+
if (!state.initialized)
|
|
174
|
+
throw new Error("Internal error");
|
|
175
|
+
while (true) {
|
|
176
|
+
if (crashError.error)
|
|
177
|
+
throw crashError.error;
|
|
178
|
+
// Try to pop a message from the queue.
|
|
179
|
+
try {
|
|
180
|
+
const mem = new Uint8Array(state.instance.exports.memory.buffer);
|
|
181
|
+
const responseInfo = state.instance.exports.json_rpc_responses_peek(chainId) >>> 0;
|
|
182
|
+
const ptr = buffer.readUInt32LE(mem, responseInfo) >>> 0;
|
|
183
|
+
const len = buffer.readUInt32LE(mem, responseInfo + 4) >>> 0;
|
|
184
|
+
// `len === 0` means "queue is empty" according to the API.
|
|
185
|
+
// In that situation, queue the resolve/reject.
|
|
186
|
+
if (len !== 0) {
|
|
187
|
+
const message = buffer.utf8BytesToString(mem, ptr, len);
|
|
188
|
+
state.instance.exports.json_rpc_responses_pop(chainId);
|
|
189
|
+
return message;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch (_error) {
|
|
193
|
+
console.assert(crashError.error);
|
|
194
|
+
throw crashError.error;
|
|
195
|
+
}
|
|
196
|
+
// If no message is available, wait for one to be.
|
|
197
|
+
yield new Promise((resolve, reject) => {
|
|
198
|
+
chains.get(chainId).jsonRpcResponsesPromises.push({ resolve: () => resolve(undefined), reject });
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}),
|
|
202
|
+
addChain: (chainSpec, databaseContent, potentialRelayChains, disableJsonRpc) => {
|
|
203
|
+
return queueOperation((instance) => {
|
|
204
|
+
if (crashError.error)
|
|
205
|
+
throw crashError.error;
|
|
206
|
+
try {
|
|
207
|
+
// Write the chain specification into memory.
|
|
208
|
+
const chainSpecEncoded = new TextEncoder().encode(chainSpec);
|
|
209
|
+
const chainSpecPtr = instance.exports.alloc(chainSpecEncoded.length) >>> 0;
|
|
210
|
+
new Uint8Array(instance.exports.memory.buffer).set(chainSpecEncoded, chainSpecPtr);
|
|
211
|
+
// Write the database content into memory.
|
|
212
|
+
const databaseContentEncoded = new TextEncoder().encode(databaseContent);
|
|
213
|
+
const databaseContentPtr = instance.exports.alloc(databaseContentEncoded.length) >>> 0;
|
|
214
|
+
new Uint8Array(instance.exports.memory.buffer).set(databaseContentEncoded, databaseContentPtr);
|
|
215
|
+
// Write the potential relay chains into memory.
|
|
216
|
+
const potentialRelayChainsLen = potentialRelayChains.length;
|
|
217
|
+
const potentialRelayChainsPtr = instance.exports.alloc(potentialRelayChainsLen * 4) >>> 0;
|
|
218
|
+
for (let idx = 0; idx < potentialRelayChains.length; ++idx) {
|
|
219
|
+
buffer.writeUInt32LE(new Uint8Array(instance.exports.memory.buffer), potentialRelayChainsPtr + idx * 4, potentialRelayChains[idx]);
|
|
220
|
+
}
|
|
221
|
+
// `add_chain` unconditionally allocates a chain id. If an error occurs, however, this chain
|
|
222
|
+
// id will refer to an *erroneous* chain. `chain_is_ok` is used below to determine whether it
|
|
223
|
+
// has succeeeded or not.
|
|
224
|
+
// Note that `add_chain` properly de-allocates buffers even if it failed.
|
|
225
|
+
const chainId = instance.exports.add_chain(chainSpecPtr, chainSpecEncoded.length, databaseContentPtr, databaseContentEncoded.length, disableJsonRpc ? 0 : 1, potentialRelayChainsPtr, potentialRelayChainsLen);
|
|
226
|
+
if (instance.exports.chain_is_ok(chainId) != 0) {
|
|
227
|
+
console.assert(!chains.has(chainId));
|
|
228
|
+
chains.set(chainId, {
|
|
229
|
+
jsonRpcResponsesPromises: new Array()
|
|
230
|
+
});
|
|
231
|
+
return { success: true, chainId };
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
const errorMsgLen = instance.exports.chain_error_len(chainId) >>> 0;
|
|
235
|
+
const errorMsgPtr = instance.exports.chain_error_ptr(chainId) >>> 0;
|
|
236
|
+
const errorMsg = buffer.utf8BytesToString(new Uint8Array(instance.exports.memory.buffer), errorMsgPtr, errorMsgLen);
|
|
237
|
+
instance.exports.remove_chain(chainId);
|
|
238
|
+
return { success: false, error: errorMsg };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch (_error) {
|
|
242
|
+
console.assert(crashError.error);
|
|
243
|
+
throw crashError.error;
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
removeChain: (chainId) => {
|
|
248
|
+
// Because `removeChain` is passed as parameter an identifier returned by `addChain`, it is
|
|
249
|
+
// always the case that the Wasm instance is already initialized. The only possibility for
|
|
250
|
+
// it to not be the case is if the user completely invented the `chainId`.
|
|
251
|
+
if (!state.initialized)
|
|
252
|
+
throw new Error("Internal error");
|
|
253
|
+
if (crashError.error)
|
|
254
|
+
throw crashError.error;
|
|
255
|
+
// Removing the chain synchronously avoids having to deal with race conditions such as a
|
|
256
|
+
// JSON-RPC response corresponding to a chain that is going to be deleted but hasn't been yet.
|
|
257
|
+
// These kind of race conditions are already delt with within smoldot.
|
|
258
|
+
console.assert(chains.has(chainId));
|
|
259
|
+
for (const { reject } of chains.get(chainId).jsonRpcResponsesPromises) {
|
|
260
|
+
reject(new AlreadyDestroyedError());
|
|
261
|
+
}
|
|
262
|
+
chains.delete(chainId);
|
|
263
|
+
try {
|
|
264
|
+
state.instance.exports.remove_chain(chainId);
|
|
265
|
+
}
|
|
266
|
+
catch (_error) {
|
|
267
|
+
console.assert(crashError.error);
|
|
268
|
+
throw crashError.error;
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
startShutdown: () => {
|
|
272
|
+
return queueOperation((instance) => {
|
|
273
|
+
// `startShutdown` is a bit special in its handling of crashes.
|
|
274
|
+
// Shutting down will lead to `onWasmPanic` being called at some point, possibly during
|
|
275
|
+
// the call to `start_shutdown` itself. As such, we move into "don't print errors anymore"
|
|
276
|
+
// mode even before calling `start_shutdown`.
|
|
277
|
+
//
|
|
278
|
+
// Furthermore, if a crash happened in the past, there is no point in throwing an
|
|
279
|
+
// exception when the user wants the shutdown to happen.
|
|
280
|
+
if (crashError.error)
|
|
281
|
+
return;
|
|
282
|
+
if (state.initialized)
|
|
283
|
+
state.unregisterCallback();
|
|
284
|
+
try {
|
|
285
|
+
printError.printError = false;
|
|
286
|
+
instance.exports.start_shutdown();
|
|
287
|
+
}
|
|
288
|
+
catch (_error) {
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
@@ -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,67 @@
|
|
|
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
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
5
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
6
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
7
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
8
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
9
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
10
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
// This program is free software: you can redistribute it and/or modify
|
|
14
|
+
// it under the terms of the GNU General Public License as published by
|
|
15
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
16
|
+
// (at your option) any later version.
|
|
17
|
+
// This program is distributed in the hope that it will be useful,
|
|
18
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
19
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
20
|
+
// GNU General Public License for more details.
|
|
21
|
+
// You should have received a copy of the GNU General Public License
|
|
22
|
+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
|
+
import { default as smoldotLightBindingsBuilder } from './bindings-smoldot-light.js';
|
|
24
|
+
import { default as wasiBindingsBuilder } from './bindings-wasi.js';
|
|
25
|
+
import { default as wasmBase64 } from './autogen/wasm.js';
|
|
26
|
+
export { ConnectionError } from './bindings-smoldot-light.js';
|
|
27
|
+
export function startInstance(config, platformBindings) {
|
|
28
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
29
|
+
// The actual Wasm bytecode is base64-decoded then deflate-decoded from a constant found in a
|
|
30
|
+
// different file.
|
|
31
|
+
// This is suboptimal compared to using `instantiateStreaming`, but it is the most
|
|
32
|
+
// cross-platform cross-bundler approach.
|
|
33
|
+
const wasmBytecode = yield platformBindings.trustedBase64DecodeAndZlibInflate(wasmBase64);
|
|
34
|
+
let killAll;
|
|
35
|
+
// Used to bind with the smoldot-light bindings. See the `bindings-smoldot-light.js` file.
|
|
36
|
+
const smoldotJsConfig = Object.assign({ performanceNow: platformBindings.performanceNow, connect: platformBindings.connect, onPanic: (message) => {
|
|
37
|
+
killAll();
|
|
38
|
+
config.onWasmPanic(message);
|
|
39
|
+
throw new Error();
|
|
40
|
+
} }, config);
|
|
41
|
+
// Used to bind with the Wasi bindings. See the `bindings-wasi.js` file.
|
|
42
|
+
const wasiConfig = {
|
|
43
|
+
envVars: [],
|
|
44
|
+
getRandomValues: platformBindings.getRandomValues,
|
|
45
|
+
onProcExit: (retCode) => {
|
|
46
|
+
killAll();
|
|
47
|
+
config.onWasmPanic(`proc_exit called: ${retCode}`);
|
|
48
|
+
throw new Error();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const { imports: smoldotBindings, killAll: smoldotBindingsKillAll } = smoldotLightBindingsBuilder(smoldotJsConfig);
|
|
52
|
+
killAll = smoldotBindingsKillAll;
|
|
53
|
+
// Start the Wasm virtual machine.
|
|
54
|
+
// The Rust code defines a list of imports that must be fulfilled by the environment. The second
|
|
55
|
+
// parameter provides their implementations.
|
|
56
|
+
const result = yield WebAssembly.instantiate(wasmBytecode, {
|
|
57
|
+
// The functions with the "smoldot" prefix are specific to smoldot.
|
|
58
|
+
"smoldot": smoldotBindings,
|
|
59
|
+
// As the Rust code is compiled for wasi, some more wasi-specific imports exist.
|
|
60
|
+
"wasi_snapshot_preview1": wasiBindingsBuilder(wasiConfig),
|
|
61
|
+
});
|
|
62
|
+
const instance = result.instance;
|
|
63
|
+
smoldotJsConfig.instance = instance;
|
|
64
|
+
wasiConfig.instance = instance;
|
|
65
|
+
return instance;
|
|
66
|
+
});
|
|
67
|
+
}
|
package/package.json
CHANGED
|
@@ -1,37 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smoldot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.11",
|
|
4
4
|
"description": "Light client that connects to Polkadot and Substrate-based blockchains",
|
|
5
5
|
"author": "Parity Technologies <admin@parity.io>",
|
|
6
6
|
"license": "GPL-3.0-or-later WITH Classpath-exception-2.0",
|
|
7
|
-
"homepage": "https://github.com/
|
|
7
|
+
"homepage": "https://github.com/smol-dot/smoldot",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/
|
|
10
|
+
"url": "git+https://github.com/smol-dot/smoldot.git"
|
|
11
11
|
},
|
|
12
12
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/
|
|
13
|
+
"url": "https://github.com/smol-dot/smoldot/issues"
|
|
14
14
|
},
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"main": "dist/cjs/index-browser.js",
|
|
19
|
+
"types": "dist/mjs/index-browser.d.ts",
|
|
20
|
+
"module": "dist/mjs/index-browser.js",
|
|
21
|
+
"exports": {
|
|
22
|
+
"node": {
|
|
23
|
+
"import": "./dist/mjs/index-nodejs.js",
|
|
24
|
+
"require": "./dist/cjs/index-nodejs.js"
|
|
25
|
+
},
|
|
26
|
+
"default": {
|
|
27
|
+
"import": "./dist/mjs/index-browser.js",
|
|
28
|
+
"require": "./dist/cjs/index-browser.js"
|
|
29
|
+
}
|
|
23
30
|
},
|
|
24
|
-
"
|
|
25
|
-
"
|
|
31
|
+
"scripts": {
|
|
32
|
+
"buildModules": "tsc -p tsconfig-mjs.json && tsc -p tsconfig-cjs.json && bash fix-package-type.sh",
|
|
33
|
+
"prepublishOnly": "node prepare.mjs --release && rimraf ./dist && npm run buildModules",
|
|
34
|
+
"build": "node prepare.mjs --release && rimraf ./dist && npm run buildModules",
|
|
35
|
+
"start": "node prepare.mjs --debug && rimraf ./dist && npm run buildModules && node demo/demo.mjs",
|
|
36
|
+
"test": "node prepare.mjs --debug && rimraf ./dist && npm run buildModules && deno run ./dist/mjs/index-deno.js && ava --timeout=2m --concurrency 2 --no-worker-threads",
|
|
37
|
+
"doc": "node prepare.mjs --debug && typedoc --basePath ./src --out ./dist/doc --treatWarningsAsErrors ./src/index-browser.ts"
|
|
26
38
|
},
|
|
27
39
|
"dependencies": {
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"randombytes": "^2.1.0",
|
|
31
|
-
"websocket": "^1.0.32"
|
|
40
|
+
"pako": "^2.0.4",
|
|
41
|
+
"ws": "^8.8.1"
|
|
32
42
|
},
|
|
33
43
|
"devDependencies": {
|
|
44
|
+
"@types/node": "^18.0.0",
|
|
45
|
+
"@types/pako": "^2.0.0",
|
|
46
|
+
"@types/ws": "^8.5.3",
|
|
47
|
+
"ava": "^5.0.1",
|
|
34
48
|
"dtslint": "^4.0.6",
|
|
35
|
-
"
|
|
49
|
+
"rimraf": "^3.0.2",
|
|
50
|
+
"typedoc": "^0.23.15",
|
|
51
|
+
"typescript": "^4.5.4"
|
|
36
52
|
}
|
|
37
53
|
}
|