@hazae41/bobine 0.0.2
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/LICENSE.md +19 -0
- package/README.md +224 -0
- package/out/libs/database/mod.d.ts +1 -0
- package/out/libs/database/mod.js +1 -0
- package/out/libs/metering/mod.d.ts +2 -0
- package/out/libs/metering/mod.js +46 -0
- package/out/libs/nullable/mod.d.ts +1 -0
- package/out/libs/nullable/mod.js +1 -0
- package/out/libs/packs/mod.d.ts +11 -0
- package/out/libs/packs/mod.js +98 -0
- package/out/libs/sql/mod.d.ts +3 -0
- package/out/libs/sql/mod.js +24 -0
- package/out/mod.d.ts +1 -0
- package/out/mod.js +1 -0
- package/out/mods/helper/bin.d.ts +1 -0
- package/out/mods/helper/bin.js +75 -0
- package/out/mods/mod.d.ts +1 -0
- package/out/mods/mod.js +1 -0
- package/out/mods/server/bin.d.ts +1 -0
- package/out/mods/server/bin.js +34 -0
- package/out/mods/server/mod.d.ts +8 -0
- package/out/mods/server/mod.js +299 -0
- package/out/mods/worker/bin.d.ts +1 -0
- package/out/mods/worker/bin.js +351 -0
- package/package.json +55 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# MIT
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Bobine
|
|
2
|
+
|
|
3
|
+
Your neighbor's chain of compute
|
|
4
|
+
|
|
5
|
+
## HTTP API
|
|
6
|
+
|
|
7
|
+
### POST /api/create
|
|
8
|
+
|
|
9
|
+
Creates a new module using some WebAssembly code and some unique salt
|
|
10
|
+
|
|
11
|
+
Accepts a form data with the following fields
|
|
12
|
+
|
|
13
|
+
- `code` as bytes = your WebAssembly code (your .wasm file)
|
|
14
|
+
|
|
15
|
+
- `salt` as bytes = some bytes (any length, can be 0) that will be hashed with your code to produce your module address
|
|
16
|
+
|
|
17
|
+
- `effort` as bytes = some unique 32 bytes whose sha256 `hash` validates `((2n ** 256n) / BigInt("0x" + hash.toHex())) > (code.length + salt.length)`
|
|
18
|
+
|
|
19
|
+
### POST /api/execute
|
|
20
|
+
|
|
21
|
+
Execute some function on a module
|
|
22
|
+
|
|
23
|
+
Accepts a form data with the following fields
|
|
24
|
+
|
|
25
|
+
- `module` as string = your module address (32-bytes hex)
|
|
26
|
+
|
|
27
|
+
- `method` as string = WebAssembly function to be executed
|
|
28
|
+
|
|
29
|
+
- `params` as bytes = WebAssembly function parameters as pack-encoded bytes (see pack encoding below)
|
|
30
|
+
|
|
31
|
+
- `effort` as bytes = some unique 32 bytes whose sha256 `hash` whose result in `((2n ** 256n) / BigInt("0x" + hash.toHex()))` will be the maximum number of sparks (gas) used
|
|
32
|
+
|
|
33
|
+
### POST /api/simulate
|
|
34
|
+
|
|
35
|
+
Simulate some function on a module (it won't write anything to storage and will execute in mode `2` so verifications such as signatures can be skipped)
|
|
36
|
+
|
|
37
|
+
Accepts a form data with the following fields
|
|
38
|
+
|
|
39
|
+
- `module` as string = your module address (32-bytes hex)
|
|
40
|
+
|
|
41
|
+
- `method` as string = WebAssembly function to be executed
|
|
42
|
+
|
|
43
|
+
- `params` as bytes = WebAssembly function parameters as pack-encoded bytes (see pack encoding below)
|
|
44
|
+
|
|
45
|
+
- `effort` as bytes = some unique 32 bytes whose sha256 `hash` whose result in `((2n ** 256n) / BigInt("0x" + hash.toHex()))` will be the maximum number of sparks (gas) used
|
|
46
|
+
|
|
47
|
+
## WebAssembly API
|
|
48
|
+
|
|
49
|
+
### <module>
|
|
50
|
+
|
|
51
|
+
You can use any module method by using the module address as hex
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
@external("5feeee846376f6436990aa2757bc67fbc4498bcc9993b647788e273ad6fde474", "add")
|
|
55
|
+
declare function add(x: u32, y: u32): u32
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### blobs
|
|
59
|
+
|
|
60
|
+
You can pass bytes between modules by storing them in the blob storage and loading them via reference
|
|
61
|
+
|
|
62
|
+
- `blobs.save(offset: u32, length: u32): blobref` = save `length` bytes at `offset` of your memory to the blob storage
|
|
63
|
+
|
|
64
|
+
- `blobs.load(blob: blobref, offset: u32): void` = load some blob into your memory at `offset`
|
|
65
|
+
|
|
66
|
+
- `blobs.equals(left: blobref, right: blobref): bool` = check if two blobs are equals without loading them into memory
|
|
67
|
+
|
|
68
|
+
- `blobs.concat(left: blobref, right: blobref): blobref` = concatenate two blobs without loading them into memory
|
|
69
|
+
|
|
70
|
+
- `blob.to_hex/from_hex/to_base64/from_base64(blob: blobref): blobref` = convert blobs to/from hex/base64 without loading them into memory
|
|
71
|
+
|
|
72
|
+
### bigints
|
|
73
|
+
|
|
74
|
+
You can work with infinite-precision bigints
|
|
75
|
+
|
|
76
|
+
- `bigints.add(left: bigintref, right: bigintref): bigintref` = add two bigints
|
|
77
|
+
|
|
78
|
+
- `bigints.sub(left: bigintref, right: bigintref): bigintref` = subtract two bigints
|
|
79
|
+
|
|
80
|
+
- `bigints.mul(left: bigintref, right: bigintref): bigintref` = multiply two bigints
|
|
81
|
+
|
|
82
|
+
- `bigints.div(left: bigintref, right: bigintref): bigintref` = divide two bigints
|
|
83
|
+
|
|
84
|
+
- `bigints.pow(left: bigintref, right: bigintref): bigintref` = left ** right
|
|
85
|
+
|
|
86
|
+
- `bigints.encode(bigint: bigintref): blobref` = convert bigint to bytes
|
|
87
|
+
|
|
88
|
+
- `bigints.decode(base16: blobref): bigintref` = convert bytes to bigint
|
|
89
|
+
|
|
90
|
+
- `bigints.to_base16(bigint: bigintref): blobref` = convert bigint to hex utf8 bytes
|
|
91
|
+
|
|
92
|
+
- `bigints.from_base16(base16: blobref): bigintref` = convert hex utf8 bytes to bigint
|
|
93
|
+
|
|
94
|
+
- `bigints.to_base10(bigint: bigintref): blobref` = convert bigint to base10 utf8 bytes
|
|
95
|
+
|
|
96
|
+
- `bigints.from_base10(base16: blobref): bigintref` = convert base10 utf8 bytes to bigint
|
|
97
|
+
|
|
98
|
+
### packs
|
|
99
|
+
|
|
100
|
+
You can pack various arguments (numbers, refs) into a pack which can be passed between modules and/or encoded/decoded into bytes
|
|
101
|
+
|
|
102
|
+
- `packs.create(...values: any[]): packref` = create a new pack from the provided values (number, blobref, packref, null)
|
|
103
|
+
|
|
104
|
+
- `packs.encode(pack: packref): blobref` = encodes values into bytes using the following pseudocode
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
function writePack(pack: packref) {
|
|
108
|
+
for (const value of values) {
|
|
109
|
+
if (value == null) {
|
|
110
|
+
writeUint8(1)
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (typeof value === "number") {
|
|
115
|
+
writeUint8(2)
|
|
116
|
+
writeFloat64(value, "little-endian")
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (typeof value === "bigint") {
|
|
121
|
+
writeUint8(3)
|
|
122
|
+
writeUint32(value.toHex().length, "little-endian")
|
|
123
|
+
writeBytes(value.toHex())
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (isBlobref(value)) {
|
|
128
|
+
writeUint8(4)
|
|
129
|
+
writeUint32(value.length, "little-endian")
|
|
130
|
+
writeBytes(value)
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (isPackref(value)) {
|
|
135
|
+
writeUint8(5)
|
|
136
|
+
writePack(value)
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
writeUint8(1) // anything else if encoded as null
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
writeUint8(0)
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
- `packs.decode(blob: blobref): packref` = decodes bytes into a pack of values using the same pseudocode but for reading
|
|
149
|
+
|
|
150
|
+
- `packs.concat(left: packref, right: packref)` = concatenate two packs into one (basically does `[...left, ...right]`)
|
|
151
|
+
|
|
152
|
+
- `packs.get<T>(pack: packref, index: u32): T` = get the value of a pack at `index` (throws if not found)
|
|
153
|
+
|
|
154
|
+
### env
|
|
155
|
+
|
|
156
|
+
Get infos about the executing environment
|
|
157
|
+
|
|
158
|
+
- `env.mode: u32` = `1` if execution, `2` is simulation
|
|
159
|
+
|
|
160
|
+
- `env.uuid(): blobref` = get the unique uuid of this environment (similar to a chain id)
|
|
161
|
+
|
|
162
|
+
### modules
|
|
163
|
+
|
|
164
|
+
Modules are identified by their address as a blob of bytes (pure sha256-output 32-length bytes without any encoding)
|
|
165
|
+
|
|
166
|
+
- `modules.load(module: blobref): blobref` = get the code of module as a blob
|
|
167
|
+
|
|
168
|
+
- `modules.call(module: blobref, method: blobref, params: packref): packref` = dynamically call a module method with the given params as pack and return value as a 1-length pack
|
|
169
|
+
|
|
170
|
+
- `modules.create(code: blobref, salt: blobref): blobref` = dynamically create a new module with the given code and salt, returns the module address
|
|
171
|
+
|
|
172
|
+
- `modules.self(): blobref` = get your module address as blob
|
|
173
|
+
|
|
174
|
+
### storage
|
|
175
|
+
|
|
176
|
+
You can use a private storage (it works like storage and events at the same time)
|
|
177
|
+
|
|
178
|
+
- `storage.set(key: blobref, value: blobref): void` = set some value to storage at key
|
|
179
|
+
|
|
180
|
+
- `storage.get(key: blobref): blobref` = get the latest value from storage at key
|
|
181
|
+
|
|
182
|
+
### sha256
|
|
183
|
+
|
|
184
|
+
Use the SHA-256 hashing algorithm
|
|
185
|
+
|
|
186
|
+
- `sha256.digest(payload: blobref): blobref` = hash the payload and returns the digest
|
|
187
|
+
|
|
188
|
+
### ed25519
|
|
189
|
+
|
|
190
|
+
Use the Ed25519 signing algorithm
|
|
191
|
+
|
|
192
|
+
- `ed25519.verify(pubkey: blobref, signature: blobref, payload: blobref): boolean` = verify a signature
|
|
193
|
+
|
|
194
|
+
- `ed25519.sign(payload: blobref): blobref` = (experimental) sign payload using the miner's private key
|
|
195
|
+
|
|
196
|
+
### symbols (experimental)
|
|
197
|
+
|
|
198
|
+
- `symbols.create(): symbolref` = create a unique reference that can be passed around
|
|
199
|
+
|
|
200
|
+
### refs (experimental)
|
|
201
|
+
|
|
202
|
+
- `refs.numerize(ref: symbolref/blobref/packref): u32` = translate any reference into a unique private pointer that can be stored into data structures
|
|
203
|
+
|
|
204
|
+
- `refs.denumerize(pointer: u32): symbolref/blobref/packref` = get the exact same reference back from your private pointer
|
|
205
|
+
|
|
206
|
+
This can be useful if you want to check a reference for authenticity
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
const sessions = new Set<u32>()
|
|
210
|
+
|
|
211
|
+
export function login(password: blobref): symbolref {
|
|
212
|
+
const session = symbols.create()
|
|
213
|
+
|
|
214
|
+
sessions.put(symbols.numerize(session))
|
|
215
|
+
|
|
216
|
+
return session
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function verify(session: symbolref) {
|
|
220
|
+
return sessions.has(symbols.numerize(session))
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
You should never accept a pointer instead of a real reference because they can be easily guessed by an attacking module
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as Wasm from "@hazae41/wasm";
|
|
2
|
+
export function meter(module, from, name) {
|
|
3
|
+
const wtype = module.body.sections.find(section => section.kind === Wasm.TypeSection.kind);
|
|
4
|
+
const wimport = module.body.sections.find(section => section.kind === Wasm.ImportSection.kind);
|
|
5
|
+
const wexport = module.body.sections.find(section => section.kind === Wasm.ExportSection.kind);
|
|
6
|
+
const wcode = module.body.sections.find(section => section.kind === Wasm.CodeSection.kind);
|
|
7
|
+
if (wtype == null)
|
|
8
|
+
throw new Error(`No type section`);
|
|
9
|
+
if (wimport == null)
|
|
10
|
+
throw new Error(`No import section`);
|
|
11
|
+
if (wexport == null)
|
|
12
|
+
throw new Error(`No export section`);
|
|
13
|
+
if (wcode == null)
|
|
14
|
+
throw new Error(`No code section`);
|
|
15
|
+
const wstart = module.body.sections.find(section => section.kind === Wasm.StartSection.kind);
|
|
16
|
+
wtype.descriptors.push({ prefix: Wasm.TypeSection.FuncType.kind, subtypes: [], body: new Wasm.TypeSection.FuncType([0x7f], []) });
|
|
17
|
+
wimport.descriptors.unshift({ from: new TextEncoder().encode(from), name: new TextEncoder().encode(name), body: new Wasm.ImportSection.FunctionImport(wtype.descriptors.length - 1) });
|
|
18
|
+
if (wstart != null)
|
|
19
|
+
wstart.funcidx++;
|
|
20
|
+
for (const body of wcode.bodies) {
|
|
21
|
+
const instructions = new Array();
|
|
22
|
+
const subinstructions = new Array();
|
|
23
|
+
for (const instruction of body.instructions) {
|
|
24
|
+
if ([0x10, 0x12, 0xd2].includes(instruction.opcode))
|
|
25
|
+
instruction.params[0].value++;
|
|
26
|
+
if ([0x03, 0x04, 0x05, 0x0B, 0x0c, 0x0D, 0x0E, 0x0F, 0xd5, 0xd6].includes(instruction.opcode)) {
|
|
27
|
+
subinstructions.push(instruction);
|
|
28
|
+
instructions.push(new Wasm.Instruction(0x41, [new Wasm.LEB128.I32(subinstructions.length)]));
|
|
29
|
+
instructions.push(new Wasm.Instruction(0x10, [new Wasm.LEB128.U32(0)]));
|
|
30
|
+
instructions.push(...subinstructions);
|
|
31
|
+
subinstructions.length = 0;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
subinstructions.push(instruction);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
instructions.push(...subinstructions);
|
|
38
|
+
body.instructions = instructions;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
for (const descriptor of wexport.descriptors) {
|
|
42
|
+
if (descriptor.kind !== 0x00)
|
|
43
|
+
continue;
|
|
44
|
+
descriptor.xidx++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Nullable<T> = T | null | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Cursor } from "@hazae41/cursor";
|
|
2
|
+
export declare class Pack {
|
|
3
|
+
readonly values: Array<Pack.Value>;
|
|
4
|
+
constructor(values: Array<Pack.Value>);
|
|
5
|
+
sizeOrThrow(): number;
|
|
6
|
+
writeOrThrow(cursor: Cursor): void;
|
|
7
|
+
}
|
|
8
|
+
export declare namespace Pack {
|
|
9
|
+
type Value = number | bigint | Uint8Array | Pack | null;
|
|
10
|
+
function readOrThrow(cursor: Cursor): Pack;
|
|
11
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export class Pack {
|
|
2
|
+
values;
|
|
3
|
+
constructor(values) {
|
|
4
|
+
this.values = values;
|
|
5
|
+
}
|
|
6
|
+
sizeOrThrow() {
|
|
7
|
+
let size = 0;
|
|
8
|
+
for (const value of this.values) {
|
|
9
|
+
if (typeof value === "number") {
|
|
10
|
+
size += 1 + 4;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (typeof value === "bigint") {
|
|
14
|
+
const text = value.toString(16);
|
|
15
|
+
const data = Uint8Array.fromHex(text.length % 2 === 1 ? "0" + text : text);
|
|
16
|
+
size += 1 + 4 + data.length;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (value instanceof Uint8Array) {
|
|
20
|
+
size += 1 + 4 + value.length;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (value instanceof Pack) {
|
|
24
|
+
size += 1 + value.sizeOrThrow();
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
size += 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
size += 1;
|
|
31
|
+
return size;
|
|
32
|
+
}
|
|
33
|
+
writeOrThrow(cursor) {
|
|
34
|
+
for (const value of this.values) {
|
|
35
|
+
if (typeof value === "number") {
|
|
36
|
+
cursor.writeUint8OrThrow(2);
|
|
37
|
+
cursor.writeFloat64OrThrow(value, true);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === "bigint") {
|
|
41
|
+
cursor.writeUint8OrThrow(3);
|
|
42
|
+
const text = value.toString(16);
|
|
43
|
+
const data = Uint8Array.fromHex(text.length % 2 === 1 ? "0" + text : text);
|
|
44
|
+
cursor.writeUint32OrThrow(data.length, true);
|
|
45
|
+
cursor.writeOrThrow(data);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (value instanceof Uint8Array) {
|
|
49
|
+
cursor.writeUint8OrThrow(4);
|
|
50
|
+
cursor.writeUint32OrThrow(value.length, true);
|
|
51
|
+
cursor.writeOrThrow(value);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (value instanceof Pack) {
|
|
55
|
+
cursor.writeUint8OrThrow(5);
|
|
56
|
+
value.writeOrThrow(cursor);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
cursor.writeUint8OrThrow(1);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
cursor.writeUint8OrThrow(0);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
(function (Pack) {
|
|
67
|
+
function readOrThrow(cursor) {
|
|
68
|
+
const values = [];
|
|
69
|
+
while (true) {
|
|
70
|
+
const type = cursor.readUint8OrThrow();
|
|
71
|
+
if (type === 0)
|
|
72
|
+
break;
|
|
73
|
+
if (type === 2) {
|
|
74
|
+
values.push(cursor.readFloat64OrThrow(true));
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (type === 3) {
|
|
78
|
+
const size = cursor.readUint32OrThrow(true);
|
|
79
|
+
const data = cursor.readOrThrow(size);
|
|
80
|
+
values.push(BigInt("0x" + data.toHex()));
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (type === 4) {
|
|
84
|
+
const size = cursor.readUint32OrThrow(true);
|
|
85
|
+
values.push(cursor.readOrThrow(size));
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (type === 5) {
|
|
89
|
+
values.push(Pack.readOrThrow(cursor));
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
values.push(null);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
return new Pack(values);
|
|
96
|
+
}
|
|
97
|
+
Pack.readOrThrow = readOrThrow;
|
|
98
|
+
})(Pack || (Pack = {}));
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Database } from "@tursodatabase/database";
|
|
2
|
+
export declare function runAsImmediateOrThrow<T>(database: Database, callback: (database: Database) => Promise<T>): Promise<T>;
|
|
3
|
+
export declare function runAsDeferredOrThrow<T>(database: Database, callback: (database: Database) => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export async function runAsImmediateOrThrow(database, callback) {
|
|
2
|
+
await database.exec("BEGIN IMMEDIATE TRANSACTION;");
|
|
3
|
+
try {
|
|
4
|
+
const result = await callback(database);
|
|
5
|
+
await database.exec("COMMIT;");
|
|
6
|
+
return result;
|
|
7
|
+
}
|
|
8
|
+
catch (e) {
|
|
9
|
+
await database.exec("ROLLBACK;");
|
|
10
|
+
throw e;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function runAsDeferredOrThrow(database, callback) {
|
|
14
|
+
await database.exec("BEGIN DEFERRED TRANSACTION;");
|
|
15
|
+
try {
|
|
16
|
+
const result = await callback(database);
|
|
17
|
+
await database.exec("COMMIT;");
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
await database.exec("ROLLBACK;");
|
|
22
|
+
throw e;
|
|
23
|
+
}
|
|
24
|
+
}
|
package/out/mod.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./mods/mod.ts";
|
package/out/mod.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./mods/mod.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { RpcMethodNotFoundError } from "@hazae41/jsonrpc";
|
|
2
|
+
import { connect } from "@tursodatabase/database";
|
|
3
|
+
import { runAsImmediateOrThrow } from "../../libs/sql/mod.js";
|
|
4
|
+
const params = new URL(import.meta.url).searchParams;
|
|
5
|
+
const database = await connect(params.get("database"));
|
|
6
|
+
self.addEventListener("message", async (event) => {
|
|
7
|
+
try {
|
|
8
|
+
const request = event.data;
|
|
9
|
+
if (request.method === "storage_set") {
|
|
10
|
+
return await runAsImmediateOrThrow(database, async (database) => {
|
|
11
|
+
const [module, method, args, events] = event.data.params;
|
|
12
|
+
const moment = await database.prepare(`INSERT INTO moments (epoch, module, method, params) VALUES (0, ?, ?, ?);`).run(module, method, args);
|
|
13
|
+
const writer = database.prepare(`INSERT INTO events (moment, module, key, value) VALUES (?, ?, ?, ?);`);
|
|
14
|
+
for (const [module, key, value] of events)
|
|
15
|
+
await writer.run(moment.lastInsertRowid, module, key, value);
|
|
16
|
+
request.result[0] = 1;
|
|
17
|
+
request.result[1] = moment.lastInsertRowid;
|
|
18
|
+
Atomics.notify(request.result, 0);
|
|
19
|
+
return;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
if (request.method === "storage_get") {
|
|
23
|
+
const [module, key] = request.params;
|
|
24
|
+
const row = await database.prepare(`SELECT value FROM events event WHERE event.module = ? AND event.key = ? ORDER BY event.nonce DESC LIMIT 1;`).get(module, key);
|
|
25
|
+
if (row == null) {
|
|
26
|
+
request.result[0] = 1;
|
|
27
|
+
request.result[1] = 2;
|
|
28
|
+
Atomics.notify(request.result, 0);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const valueAsBytes = new Uint8Array(row.value);
|
|
32
|
+
request.result[0] = 1;
|
|
33
|
+
request.result[1] = 1;
|
|
34
|
+
request.result[2] = valueAsBytes.length;
|
|
35
|
+
request.result.buffer.grow(request.result.buffer.byteLength + valueAsBytes.length);
|
|
36
|
+
new Uint8Array(request.result.buffer).set(valueAsBytes, 4 + 4 + 4);
|
|
37
|
+
Atomics.notify(request.result, 0);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (request.method === "sha256_digest") {
|
|
41
|
+
const [payloadAsBytes] = request.params;
|
|
42
|
+
const digestAsBytes = new Uint8Array(await crypto.subtle.digest("SHA-256", payloadAsBytes));
|
|
43
|
+
request.result[0] = 1;
|
|
44
|
+
new Uint8Array(request.result.buffer).set(digestAsBytes, 4);
|
|
45
|
+
Atomics.notify(request.result, 0);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (request.method === "ed25519_verify") {
|
|
49
|
+
const [pubkeyAsBytes, signatureAsBytes, payloadAsBytes] = request.params;
|
|
50
|
+
const pubkeyAsKey = await crypto.subtle.importKey("raw", pubkeyAsBytes, "Ed25519", true, ["verify"]);
|
|
51
|
+
const verified = await crypto.subtle.verify("Ed25519", pubkeyAsKey, signatureAsBytes, payloadAsBytes);
|
|
52
|
+
request.result[0] = 1;
|
|
53
|
+
request.result[1] = verified ? 1 : 0;
|
|
54
|
+
Atomics.notify(request.result, 0);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (request.method === "ed25519_sign") {
|
|
58
|
+
const [privkeyAsBytes, payloadAsBytes] = request.params;
|
|
59
|
+
const privkeyAsKey = await crypto.subtle.importKey("pkcs8", privkeyAsBytes, "Ed25519", false, ["sign"]);
|
|
60
|
+
const signatureAsBytes = new Uint8Array(await crypto.subtle.sign("Ed25519", privkeyAsKey, payloadAsBytes));
|
|
61
|
+
request.result[0] = 1;
|
|
62
|
+
new Uint8Array(request.result.buffer).set(signatureAsBytes, 4);
|
|
63
|
+
Atomics.notify(request.result, 0);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
throw new RpcMethodNotFoundError();
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
const request = event.data;
|
|
70
|
+
console.error(error);
|
|
71
|
+
request.result[0] = 2;
|
|
72
|
+
Atomics.notify(request.result, 0);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./server/mod.ts";
|
package/out/mods/mod.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./server/mod.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { serveWithEnv } from "./mod.js";
|
|
4
|
+
const { PORT = process.env.PORT || "8080", CERT = process.env.CERT, KEY = process.env.KEY, } = process.env;
|
|
5
|
+
const port = Number(PORT);
|
|
6
|
+
const cert = CERT != null ? readFileSync(CERT, "utf8") : undefined;
|
|
7
|
+
const key = KEY != null ? readFileSync(KEY, "utf8") : undefined;
|
|
8
|
+
const server = await serveWithEnv();
|
|
9
|
+
const route = async (request) => {
|
|
10
|
+
if (request.method === "OPTIONS")
|
|
11
|
+
return new Response(null, { status: 204 });
|
|
12
|
+
if (request.headers.get("Upgrade") === "websocket") {
|
|
13
|
+
const { socket, response } = Deno.upgradeWebSocket(request);
|
|
14
|
+
await server.onWebSocketRequest(request, socket);
|
|
15
|
+
return response;
|
|
16
|
+
}
|
|
17
|
+
return await server.onHttpRequest(request);
|
|
18
|
+
};
|
|
19
|
+
const onHttpRequest = async (request) => {
|
|
20
|
+
try {
|
|
21
|
+
const response = await route(request);
|
|
22
|
+
if (response.status === 101)
|
|
23
|
+
return response;
|
|
24
|
+
response.headers.set("Access-Control-Allow-Origin", "*");
|
|
25
|
+
response.headers.set("Access-Control-Allow-Methods", "*");
|
|
26
|
+
response.headers.set("Access-Control-Allow-Headers", "*");
|
|
27
|
+
return response;
|
|
28
|
+
}
|
|
29
|
+
catch (cause) {
|
|
30
|
+
console.error(cause);
|
|
31
|
+
return new Response("Error", { status: 500 });
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
Deno.serve({ hostname: "0.0.0.0", port, cert, key }, onHttpRequest);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function serveWithEnv(prefix?: string): Promise<{
|
|
2
|
+
onHttpRequest(request: Request): Promise<Response>;
|
|
3
|
+
onWebSocketRequest(request: Request, socket: WebSocket): Promise<void>;
|
|
4
|
+
}>;
|
|
5
|
+
export declare function serve(databaseAsPath: string, scriptsAsPath: string, ed25519PrivateKeyAsHex: string, ed25519PublicKeyAsHex: string): Promise<{
|
|
6
|
+
onHttpRequest(request: Request): Promise<Response>;
|
|
7
|
+
onWebSocketRequest(request: Request, socket: WebSocket): Promise<void>;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// deno-lint-ignore-file no-cond-assign no-unused-vars require-await
|
|
2
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
3
|
+
if (value !== null && value !== void 0) {
|
|
4
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
5
|
+
var dispose, inner;
|
|
6
|
+
if (async) {
|
|
7
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
8
|
+
dispose = value[Symbol.asyncDispose];
|
|
9
|
+
}
|
|
10
|
+
if (dispose === void 0) {
|
|
11
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
12
|
+
dispose = value[Symbol.dispose];
|
|
13
|
+
if (async) inner = dispose;
|
|
14
|
+
}
|
|
15
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
16
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
17
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
18
|
+
}
|
|
19
|
+
else if (async) {
|
|
20
|
+
env.stack.push({ async: true });
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
};
|
|
24
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
25
|
+
return function (env) {
|
|
26
|
+
function fail(e) {
|
|
27
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
28
|
+
env.hasError = true;
|
|
29
|
+
}
|
|
30
|
+
var r, s = 0;
|
|
31
|
+
function next() {
|
|
32
|
+
while (r = env.stack.pop()) {
|
|
33
|
+
try {
|
|
34
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
35
|
+
if (r.dispose) {
|
|
36
|
+
var result = r.dispose.call(r.value);
|
|
37
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
38
|
+
}
|
|
39
|
+
else s |= 1;
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
fail(e);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
46
|
+
if (env.hasError) throw env.error;
|
|
47
|
+
}
|
|
48
|
+
return next();
|
|
49
|
+
};
|
|
50
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
51
|
+
var e = new Error(message);
|
|
52
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
53
|
+
});
|
|
54
|
+
import { Writable } from "@hazae41/binary";
|
|
55
|
+
import { RpcRequest, RpcResponse } from "@hazae41/jsonrpc";
|
|
56
|
+
import { Mutex } from "@hazae41/mutex";
|
|
57
|
+
import { connect } from "@tursodatabase/database";
|
|
58
|
+
import { existsSync, mkdirSync, symlinkSync, writeFileSync } from "node:fs";
|
|
59
|
+
import { dirname } from "node:path";
|
|
60
|
+
import process from "node:process";
|
|
61
|
+
import { Pack } from "../../libs/packs/mod.js";
|
|
62
|
+
export async function serveWithEnv(prefix = "") {
|
|
63
|
+
const { DATABASE_PATH = process.env[prefix + "DATABASE_PATH"], SCRIPTS_PATH = process.env[prefix + "SCRIPTS_PATH"], ED25519_PRIVATE_KEY_HEX = process.env[prefix + "ED25519_PRIVATE_KEY_HEX"], ED25519_PUBLIC_KEY_HEX = process.env[prefix + "ED25519_PUBLIC_KEY_HEX"], } = {};
|
|
64
|
+
if (DATABASE_PATH == null)
|
|
65
|
+
throw new Error("DATABASE_PATH is not set");
|
|
66
|
+
if (SCRIPTS_PATH == null)
|
|
67
|
+
throw new Error("SCRIPTS_PATH is not set");
|
|
68
|
+
if (ED25519_PRIVATE_KEY_HEX == null)
|
|
69
|
+
throw new Error("ED25519_PRIVATE_KEY_HEX is not set");
|
|
70
|
+
if (ED25519_PUBLIC_KEY_HEX == null)
|
|
71
|
+
throw new Error("ED25519_PUBLIC_KEY_HEX is not set");
|
|
72
|
+
mkdirSync(dirname(DATABASE_PATH), { recursive: true });
|
|
73
|
+
return await serve(DATABASE_PATH, SCRIPTS_PATH, ED25519_PRIVATE_KEY_HEX, ED25519_PUBLIC_KEY_HEX);
|
|
74
|
+
}
|
|
75
|
+
export async function serve(databaseAsPath, scriptsAsPath, ed25519PrivateKeyAsHex, ed25519PublicKeyAsHex) {
|
|
76
|
+
const database = await connect(databaseAsPath);
|
|
77
|
+
await database.exec(`CREATE TABLE IF NOT EXISTS events (
|
|
78
|
+
nonce INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
79
|
+
|
|
80
|
+
moment INTEGER NOT NULL,
|
|
81
|
+
|
|
82
|
+
module TEXT NOT NULL,
|
|
83
|
+
|
|
84
|
+
key BLOB NOT NULL,
|
|
85
|
+
value BLOB NOT NULL
|
|
86
|
+
);`);
|
|
87
|
+
await database.exec(`CREATE TABLE IF NOT EXISTS moments (
|
|
88
|
+
nonce INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
89
|
+
|
|
90
|
+
epoch INTEGER NOT NULL,
|
|
91
|
+
|
|
92
|
+
module TEXT NOT NULL,
|
|
93
|
+
method TEXT NOT NULL,
|
|
94
|
+
params BLOB NOT NULL
|
|
95
|
+
);`);
|
|
96
|
+
await database.close();
|
|
97
|
+
mkdirSync(scriptsAsPath, { recursive: true });
|
|
98
|
+
const setOfEffortsAsHex = new Set();
|
|
99
|
+
const worker = new Mutex(new Worker(import.meta.resolve(`@/mods/worker/bin.ts?database=${databaseAsPath}&scripts=${scriptsAsPath}&ed25519PrivateKeyAsHex=${ed25519PrivateKeyAsHex}&ed25519PublicKeyAsHex=${ed25519PublicKeyAsHex}`), { name: "worker", type: "module" }));
|
|
100
|
+
const onHttpRequest = async (request) => {
|
|
101
|
+
let match;
|
|
102
|
+
if (match = new URLPattern("/api/create", request.url).exec(request.url)) {
|
|
103
|
+
if (request.method === "POST") {
|
|
104
|
+
const form = await request.formData();
|
|
105
|
+
const wasmAsEntry = form.get("code");
|
|
106
|
+
if (wasmAsEntry == null)
|
|
107
|
+
return Response.json(null, { status: 400 });
|
|
108
|
+
if (typeof wasmAsEntry === "string")
|
|
109
|
+
return Response.json(null, { status: 400 });
|
|
110
|
+
const wasmAsBytes = await wasmAsEntry.bytes();
|
|
111
|
+
const saltAsEntry = form.get("salt");
|
|
112
|
+
if (saltAsEntry == null)
|
|
113
|
+
return Response.json(null, { status: 400 });
|
|
114
|
+
if (typeof saltAsEntry === "string")
|
|
115
|
+
return Response.json(null, { status: 400 });
|
|
116
|
+
const saltAsBytes = await saltAsEntry.bytes();
|
|
117
|
+
const effortAsEntry = form.get("effort");
|
|
118
|
+
if (effortAsEntry == null)
|
|
119
|
+
return Response.json(null, { status: 400 });
|
|
120
|
+
if (typeof effortAsEntry === "string")
|
|
121
|
+
return Response.json(null, { status: 400 });
|
|
122
|
+
const effortAsBytes = await effortAsEntry.bytes();
|
|
123
|
+
if (effortAsBytes.length !== 32)
|
|
124
|
+
return Response.json(null, { status: 400 });
|
|
125
|
+
const effortAsHex = effortAsBytes.toHex();
|
|
126
|
+
if (setOfEffortsAsHex.has(effortAsHex))
|
|
127
|
+
return Response.json(null, { status: 402 });
|
|
128
|
+
setOfEffortsAsHex.add(effortAsHex);
|
|
129
|
+
const sparksAsBigInt = (2n ** 256n) / BigInt("0x" + new Uint8Array(await crypto.subtle.digest("SHA-256", effortAsBytes)).toHex());
|
|
130
|
+
if (sparksAsBigInt < (wasmAsBytes.length + saltAsBytes.length))
|
|
131
|
+
return Response.json(null, { status: 402 });
|
|
132
|
+
const packAsBytes = Writable.writeToBytesOrThrow(new Pack([wasmAsBytes, saltAsBytes]));
|
|
133
|
+
const digestOfWasmAsBytes = new Uint8Array(await crypto.subtle.digest("SHA-256", wasmAsBytes));
|
|
134
|
+
const digestOfPackAsBytes = new Uint8Array(await crypto.subtle.digest("SHA-256", packAsBytes));
|
|
135
|
+
const digestOfWasmAsHex = digestOfWasmAsBytes.toHex();
|
|
136
|
+
const digestOfPackAsHex = digestOfPackAsBytes.toHex();
|
|
137
|
+
if (!existsSync(`${scriptsAsPath}/${digestOfWasmAsHex}.wasm`))
|
|
138
|
+
writeFileSync(`${scriptsAsPath}/${digestOfWasmAsHex}.wasm`, wasmAsBytes);
|
|
139
|
+
if (!existsSync(`${scriptsAsPath}/${digestOfPackAsHex}.wasm`))
|
|
140
|
+
symlinkSync(`./${digestOfWasmAsHex}.wasm`, `${scriptsAsPath}/${digestOfPackAsHex}.wasm`);
|
|
141
|
+
return Response.json(digestOfPackAsHex);
|
|
142
|
+
}
|
|
143
|
+
return Response.json(null, { status: 405, headers: { "Allow": "POST" } });
|
|
144
|
+
}
|
|
145
|
+
if (match = new URLPattern("/api/execute", request.url).exec(request.url)) {
|
|
146
|
+
if (request.method === "POST") {
|
|
147
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
148
|
+
try {
|
|
149
|
+
const stack = __addDisposableResource(env_1, new DisposableStack(), false);
|
|
150
|
+
const form = await request.formData();
|
|
151
|
+
const moduleAsEntry = form.get("module");
|
|
152
|
+
if (moduleAsEntry == null)
|
|
153
|
+
return Response.json(null, { status: 400 });
|
|
154
|
+
if (typeof moduleAsEntry !== "string")
|
|
155
|
+
return Response.json(null, { status: 400 });
|
|
156
|
+
const methodAsEntry = form.get("method");
|
|
157
|
+
if (methodAsEntry == null)
|
|
158
|
+
return Response.json(null, { status: 400 });
|
|
159
|
+
if (typeof methodAsEntry !== "string")
|
|
160
|
+
return Response.json(null, { status: 400 });
|
|
161
|
+
const paramsAsEntry = form.get("params");
|
|
162
|
+
if (paramsAsEntry == null)
|
|
163
|
+
return Response.json(null, { status: 400 });
|
|
164
|
+
if (typeof paramsAsEntry === "string")
|
|
165
|
+
return Response.json(null, { status: 400 });
|
|
166
|
+
const paramsAsBytes = await paramsAsEntry.bytes();
|
|
167
|
+
const effortAsEntry = form.get("effort");
|
|
168
|
+
if (effortAsEntry == null)
|
|
169
|
+
return Response.json(null, { status: 400 });
|
|
170
|
+
if (typeof effortAsEntry === "string")
|
|
171
|
+
return Response.json(null, { status: 400 });
|
|
172
|
+
const effortAsBytes = await effortAsEntry.bytes();
|
|
173
|
+
if (effortAsBytes.length !== 32)
|
|
174
|
+
return Response.json(null, { status: 400 });
|
|
175
|
+
const effortAsHex = effortAsBytes.toHex();
|
|
176
|
+
if (setOfEffortsAsHex.has(effortAsHex))
|
|
177
|
+
return Response.json(null, { status: 402 });
|
|
178
|
+
setOfEffortsAsHex.add(effortAsHex);
|
|
179
|
+
const sparksAsBigInt = (2n ** 256n) / BigInt("0x" + new Uint8Array(await crypto.subtle.digest("SHA-256", effortAsBytes)).toHex());
|
|
180
|
+
const future = Promise.withResolvers();
|
|
181
|
+
const aborter = new AbortController();
|
|
182
|
+
stack.defer(() => aborter.abort());
|
|
183
|
+
stack.use(await worker.lockOrWait());
|
|
184
|
+
worker.get().addEventListener("message", (event) => {
|
|
185
|
+
RpcResponse.from(event.data).inspectSync(future.resolve).inspectErrSync(future.reject);
|
|
186
|
+
}, { signal: aborter.signal });
|
|
187
|
+
worker.get().addEventListener("error", (event) => {
|
|
188
|
+
future.reject(event.error);
|
|
189
|
+
}, { signal: aborter.signal });
|
|
190
|
+
worker.get().addEventListener("messageerror", (event) => {
|
|
191
|
+
future.reject(event.data);
|
|
192
|
+
}, { signal: aborter.signal });
|
|
193
|
+
AbortSignal.timeout(1000).addEventListener("abort", (reason) => {
|
|
194
|
+
future.reject(reason);
|
|
195
|
+
}, { signal: aborter.signal });
|
|
196
|
+
worker.get().postMessage(new RpcRequest(null, "execute", [moduleAsEntry, methodAsEntry, paramsAsBytes, sparksAsBigInt]));
|
|
197
|
+
return new Response(await future.promise);
|
|
198
|
+
}
|
|
199
|
+
catch (e_1) {
|
|
200
|
+
env_1.error = e_1;
|
|
201
|
+
env_1.hasError = true;
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
__disposeResources(env_1);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return Response.json(null, { status: 405, headers: { "Allow": "POST" } });
|
|
208
|
+
}
|
|
209
|
+
if (match = new URLPattern("/api/simulate", request.url).exec(request.url)) {
|
|
210
|
+
if (request.method === "POST") {
|
|
211
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
212
|
+
try {
|
|
213
|
+
const stack = __addDisposableResource(env_2, new DisposableStack(), false);
|
|
214
|
+
const form = await request.formData();
|
|
215
|
+
const moduleAsEntry = form.get("module");
|
|
216
|
+
if (moduleAsEntry == null)
|
|
217
|
+
return Response.json(null, { status: 400 });
|
|
218
|
+
if (typeof moduleAsEntry !== "string")
|
|
219
|
+
return Response.json(null, { status: 400 });
|
|
220
|
+
const methodAsEntry = form.get("method");
|
|
221
|
+
if (methodAsEntry == null)
|
|
222
|
+
return Response.json(null, { status: 400 });
|
|
223
|
+
if (typeof methodAsEntry !== "string")
|
|
224
|
+
return Response.json(null, { status: 400 });
|
|
225
|
+
const paramsAsEntry = form.get("params");
|
|
226
|
+
if (paramsAsEntry == null)
|
|
227
|
+
return Response.json(null, { status: 400 });
|
|
228
|
+
if (typeof paramsAsEntry === "string")
|
|
229
|
+
return Response.json(null, { status: 400 });
|
|
230
|
+
const paramsAsBytes = await paramsAsEntry.bytes();
|
|
231
|
+
const effortAsEntry = form.get("effort");
|
|
232
|
+
if (effortAsEntry == null)
|
|
233
|
+
return Response.json(null, { status: 400 });
|
|
234
|
+
if (typeof effortAsEntry === "string")
|
|
235
|
+
return Response.json(null, { status: 400 });
|
|
236
|
+
const effortAsBytes = await effortAsEntry.bytes();
|
|
237
|
+
if (effortAsBytes.length !== 32)
|
|
238
|
+
return Response.json(null, { status: 400 });
|
|
239
|
+
const effortAsHex = effortAsBytes.toHex();
|
|
240
|
+
if (setOfEffortsAsHex.has(effortAsHex))
|
|
241
|
+
return Response.json(null, { status: 402 });
|
|
242
|
+
setOfEffortsAsHex.add(effortAsHex);
|
|
243
|
+
const sparksAsBigInt = (2n ** 256n) / BigInt("0x" + new Uint8Array(await crypto.subtle.digest("SHA-256", effortAsBytes)).toHex());
|
|
244
|
+
const future = Promise.withResolvers();
|
|
245
|
+
const aborter = new AbortController();
|
|
246
|
+
stack.defer(() => aborter.abort());
|
|
247
|
+
stack.use(await worker.lockOrWait());
|
|
248
|
+
worker.get().addEventListener("message", (event) => {
|
|
249
|
+
RpcResponse.from(event.data).inspectSync(future.resolve).inspectErrSync(future.reject);
|
|
250
|
+
}, { signal: aborter.signal });
|
|
251
|
+
worker.get().addEventListener("error", (event) => {
|
|
252
|
+
future.reject(event.error);
|
|
253
|
+
}, { signal: aborter.signal });
|
|
254
|
+
worker.get().addEventListener("messageerror", (event) => {
|
|
255
|
+
future.reject(event.data);
|
|
256
|
+
}, { signal: aborter.signal });
|
|
257
|
+
AbortSignal.timeout(1000).addEventListener("abort", (reason) => {
|
|
258
|
+
future.reject(reason);
|
|
259
|
+
}, { signal: aborter.signal });
|
|
260
|
+
worker.get().postMessage(new RpcRequest(null, "simulate", [moduleAsEntry, methodAsEntry, paramsAsBytes, sparksAsBigInt]));
|
|
261
|
+
return new Response(await future.promise);
|
|
262
|
+
}
|
|
263
|
+
catch (e_2) {
|
|
264
|
+
env_2.error = e_2;
|
|
265
|
+
env_2.hasError = true;
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
__disposeResources(env_2);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return Response.json(null, { status: 405, headers: { "Allow": "POST" } });
|
|
272
|
+
}
|
|
273
|
+
return Response.json(null, { status: 404 });
|
|
274
|
+
};
|
|
275
|
+
const onWebSocketRequest = async (request, socket) => {
|
|
276
|
+
const routeOrThrow = (_message) => {
|
|
277
|
+
return;
|
|
278
|
+
};
|
|
279
|
+
const handleOrClose = async (request) => {
|
|
280
|
+
try {
|
|
281
|
+
if (!request)
|
|
282
|
+
return;
|
|
283
|
+
const response = await routeOrThrow(request);
|
|
284
|
+
if (response == null)
|
|
285
|
+
return;
|
|
286
|
+
socket.send(JSON.stringify(response));
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
socket.close();
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
socket.addEventListener("message", async (event) => {
|
|
293
|
+
if (typeof event.data !== "string")
|
|
294
|
+
return;
|
|
295
|
+
return await handleOrClose(event.data);
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
return { onHttpRequest, onWebSocketRequest };
|
|
299
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
// deno-lint-ignore-file no-explicit-any no-unused-vars ban-unused-ignore
|
|
2
|
+
import { Readable, Writable } from "@hazae41/binary";
|
|
3
|
+
import { RpcErr, RpcError, RpcMethodNotFoundError, RpcOk } from "@hazae41/jsonrpc";
|
|
4
|
+
import * as Wasm from "@hazae41/wasm";
|
|
5
|
+
import { Buffer } from "node:buffer";
|
|
6
|
+
import { existsSync, readFileSync, symlinkSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { meter } from "../../libs/metering/mod.js";
|
|
8
|
+
import { Pack } from "../../libs/packs/mod.js";
|
|
9
|
+
const params = new URL(import.meta.url).searchParams;
|
|
10
|
+
const scriptsAsPath = params.get("scripts");
|
|
11
|
+
const ed25519PrivkeyAsHex = params.get("ed25519PrivateKeyAsHex");
|
|
12
|
+
const ed25519PrivkeyAsBytes = Uint8Array.fromHex(ed25519PrivkeyAsHex);
|
|
13
|
+
const helper = new Worker(import.meta.resolve(`@/mods/helper/bin.ts?${params.toString()}`), { type: "module" });
|
|
14
|
+
function run(module, method, params, mode, maxsparks) {
|
|
15
|
+
let sparks = 0n;
|
|
16
|
+
const exports = {};
|
|
17
|
+
const caches = new Map();
|
|
18
|
+
const writes = new Array();
|
|
19
|
+
const pack_encode = (pack) => {
|
|
20
|
+
return Writable.writeToBytesOrThrow(pack);
|
|
21
|
+
};
|
|
22
|
+
const pack_decode = (bytes) => {
|
|
23
|
+
return Readable.readFromBytesOrThrow(Pack, bytes);
|
|
24
|
+
};
|
|
25
|
+
const sparks_consume = (amount) => {
|
|
26
|
+
sparks += amount;
|
|
27
|
+
if (maxsparks != null && sparks > maxsparks)
|
|
28
|
+
throw new Error("Out of sparks");
|
|
29
|
+
return;
|
|
30
|
+
};
|
|
31
|
+
const sha256_digest = (payload) => {
|
|
32
|
+
sparks_consume(BigInt(payload.length) * 256n);
|
|
33
|
+
const result = new Int32Array(new SharedArrayBuffer(4 + 32));
|
|
34
|
+
helper.postMessage({ method: "sha256_digest", params: [payload], result });
|
|
35
|
+
if (Atomics.wait(result, 0, 0) !== "ok")
|
|
36
|
+
throw new Error("Failed to wait");
|
|
37
|
+
if (result[0] === 2)
|
|
38
|
+
throw new Error("Internal error");
|
|
39
|
+
return new Uint8Array(result.buffer, 4, 32).slice();
|
|
40
|
+
};
|
|
41
|
+
const ed25519_verify = (pubkey, signature, payload) => {
|
|
42
|
+
sparks_consume(BigInt(payload.length) * 256n);
|
|
43
|
+
const result = new Int32Array(new SharedArrayBuffer(4 + 4));
|
|
44
|
+
helper.postMessage({ method: "ed25519_verify", params: [pubkey, signature, payload], result });
|
|
45
|
+
if (Atomics.wait(result, 0, 0) !== "ok")
|
|
46
|
+
throw new Error("Failed to wait");
|
|
47
|
+
if (result[0] === 2)
|
|
48
|
+
throw new Error("Internal error");
|
|
49
|
+
return result[1] === 1;
|
|
50
|
+
};
|
|
51
|
+
const ed25519_sign = (subpayload) => {
|
|
52
|
+
sparks_consume(BigInt(subpayload.length) * 256n);
|
|
53
|
+
const payload = pack_encode(new Pack([Uint8Array.fromHex(module), subpayload]));
|
|
54
|
+
const result = new Int32Array(new SharedArrayBuffer(4 + 64));
|
|
55
|
+
helper.postMessage({ method: "ed25519_sign", params: [ed25519PrivkeyAsBytes, payload], result });
|
|
56
|
+
if (Atomics.wait(result, 0, 0) !== "ok")
|
|
57
|
+
throw new Error("Failed to wait");
|
|
58
|
+
if (result[0] === 2)
|
|
59
|
+
throw new Error("Internal error");
|
|
60
|
+
return new Uint8Array(result.buffer, 4, 64).slice();
|
|
61
|
+
};
|
|
62
|
+
const load = (module) => {
|
|
63
|
+
const current = {};
|
|
64
|
+
caches.set(module, new Map());
|
|
65
|
+
const imports = {};
|
|
66
|
+
imports["env"] = {
|
|
67
|
+
mode: mode,
|
|
68
|
+
abort: () => {
|
|
69
|
+
throw new Error("Aborted");
|
|
70
|
+
},
|
|
71
|
+
uuid: () => {
|
|
72
|
+
return Uint8Array.fromHex("8a8f19d1de0e4fcd9ab15cd7ed5de6dd");
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
imports["sparks"] = {
|
|
76
|
+
remaining: () => {
|
|
77
|
+
return sparks;
|
|
78
|
+
},
|
|
79
|
+
consume: (amount) => {
|
|
80
|
+
sparks_consume(BigInt(amount >>> 0));
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
imports["console"] = {
|
|
84
|
+
log: (blob) => {
|
|
85
|
+
console.log(new TextDecoder().decode(blob));
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
imports["blobs"] = {
|
|
89
|
+
save: (offset, length) => {
|
|
90
|
+
const { memory } = current.instance.exports;
|
|
91
|
+
const view = new Uint8Array(memory.buffer, offset >>> 0, length >>> 0);
|
|
92
|
+
return view.slice();
|
|
93
|
+
},
|
|
94
|
+
size: (blob) => {
|
|
95
|
+
return blob.length;
|
|
96
|
+
},
|
|
97
|
+
load: (blob, offset) => {
|
|
98
|
+
const { memory } = current.instance.exports;
|
|
99
|
+
const view = new Uint8Array(memory.buffer, offset >>> 0, blob.length);
|
|
100
|
+
view.set(blob);
|
|
101
|
+
},
|
|
102
|
+
concat: (left, right) => {
|
|
103
|
+
const concat = new Uint8Array(left.length + right.length);
|
|
104
|
+
concat.set(left, 0);
|
|
105
|
+
concat.set(right, left.length);
|
|
106
|
+
return concat;
|
|
107
|
+
},
|
|
108
|
+
equals: (left, right) => {
|
|
109
|
+
return !Buffer.compare(left, right);
|
|
110
|
+
},
|
|
111
|
+
from_base16: (text) => {
|
|
112
|
+
return Uint8Array.fromHex(new TextDecoder().decode(text));
|
|
113
|
+
},
|
|
114
|
+
from_base64: (text) => {
|
|
115
|
+
return Uint8Array.fromBase64(new TextDecoder().decode(text));
|
|
116
|
+
},
|
|
117
|
+
to_base16: (bytes) => {
|
|
118
|
+
return new TextEncoder().encode(bytes.toHex());
|
|
119
|
+
},
|
|
120
|
+
to_base64: (bytes) => {
|
|
121
|
+
return new TextEncoder().encode(bytes.toBase64());
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
imports["bigints"] = {
|
|
125
|
+
add: (left, right) => {
|
|
126
|
+
return left + right;
|
|
127
|
+
},
|
|
128
|
+
sub: (left, right) => {
|
|
129
|
+
return left - right;
|
|
130
|
+
},
|
|
131
|
+
mul: (left, right) => {
|
|
132
|
+
return left * right;
|
|
133
|
+
},
|
|
134
|
+
div: (left, right) => {
|
|
135
|
+
return left / right;
|
|
136
|
+
},
|
|
137
|
+
pow: (left, right) => {
|
|
138
|
+
return left ** right;
|
|
139
|
+
},
|
|
140
|
+
encode: (bigint) => {
|
|
141
|
+
const text = bigint.toString(16);
|
|
142
|
+
const data = Uint8Array.fromHex(text.length % 2 === 1 ? "0" + text : text);
|
|
143
|
+
return data;
|
|
144
|
+
},
|
|
145
|
+
decode: (bytes) => {
|
|
146
|
+
return BigInt("0x" + bytes.toHex());
|
|
147
|
+
},
|
|
148
|
+
from_base16: (text) => {
|
|
149
|
+
return BigInt("0x" + new TextDecoder().decode(text));
|
|
150
|
+
},
|
|
151
|
+
to_base16: (bigint) => {
|
|
152
|
+
return new TextEncoder().encode(bigint.toString(16));
|
|
153
|
+
},
|
|
154
|
+
from_base10: (text) => {
|
|
155
|
+
return BigInt(new TextDecoder().decode(text));
|
|
156
|
+
},
|
|
157
|
+
to_base10: (bigint) => {
|
|
158
|
+
return new TextEncoder().encode(bigint.toString());
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
imports["symbols"] = {
|
|
162
|
+
create: () => {
|
|
163
|
+
return Symbol();
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const refs = new Array();
|
|
167
|
+
const ptrs = new Map();
|
|
168
|
+
imports["refs"] = {
|
|
169
|
+
numerize: (ref) => {
|
|
170
|
+
const stale = ptrs.get(ref);
|
|
171
|
+
if (stale != null)
|
|
172
|
+
return stale;
|
|
173
|
+
const fresh = refs.push(ref) - 1;
|
|
174
|
+
ptrs.set(ref, fresh);
|
|
175
|
+
return fresh;
|
|
176
|
+
},
|
|
177
|
+
denumerize: (ptr) => {
|
|
178
|
+
const ref = refs.at(ptr >>> 0);
|
|
179
|
+
if (ref == null)
|
|
180
|
+
throw new Error("Not found");
|
|
181
|
+
return ref;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
imports["modules"] = {
|
|
185
|
+
create: (wasmAsBytes, saltAsBytes) => {
|
|
186
|
+
const packAsBytes = pack_encode(new Pack([wasmAsBytes, saltAsBytes]));
|
|
187
|
+
const digestOfWasmAsBytes = sha256_digest(wasmAsBytes);
|
|
188
|
+
const digestOfPackAsBytes = sha256_digest(packAsBytes);
|
|
189
|
+
const digestOfWasmAsHex = digestOfWasmAsBytes.toHex();
|
|
190
|
+
const digestOfPackAsHex = digestOfPackAsBytes.toHex();
|
|
191
|
+
if (!existsSync(`${scriptsAsPath}/${digestOfWasmAsHex}.wasm`))
|
|
192
|
+
writeFileSync(`${scriptsAsPath}/${digestOfWasmAsHex}.wasm`, wasmAsBytes);
|
|
193
|
+
if (!existsSync(`${scriptsAsPath}/${digestOfPackAsHex}.wasm`))
|
|
194
|
+
symlinkSync(`./${digestOfWasmAsHex}.wasm`, `${scriptsAsPath}/${digestOfPackAsHex}.wasm`, "file");
|
|
195
|
+
return digestOfPackAsBytes;
|
|
196
|
+
},
|
|
197
|
+
call: (moduleAsBytes, methodAsBytes, paramsAsPack) => {
|
|
198
|
+
const moduleAsString = moduleAsBytes.toHex();
|
|
199
|
+
const methodAsString = new TextDecoder().decode(methodAsBytes);
|
|
200
|
+
if (exports[moduleAsString] == null)
|
|
201
|
+
load(moduleAsString);
|
|
202
|
+
if (typeof exports[moduleAsString][methodAsString] !== "function")
|
|
203
|
+
throw new Error("Not found");
|
|
204
|
+
return new Pack([exports[moduleAsString][methodAsString](...paramsAsPack.values)]);
|
|
205
|
+
},
|
|
206
|
+
load: (moduleAsBytes) => {
|
|
207
|
+
return readFileSync(`${scriptsAsPath}/${moduleAsBytes.toHex()}.wasm`);
|
|
208
|
+
},
|
|
209
|
+
self: () => {
|
|
210
|
+
return Uint8Array.fromHex(module);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
imports["sha256"] = {
|
|
214
|
+
digest: (payload) => {
|
|
215
|
+
return sha256_digest(payload);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
imports["ed25519"] = {
|
|
219
|
+
verify: (pubkey, signature, payload) => {
|
|
220
|
+
return ed25519_verify(pubkey, signature, payload);
|
|
221
|
+
},
|
|
222
|
+
sign: (payload) => {
|
|
223
|
+
return ed25519_sign(payload);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
imports["packs"] = {
|
|
227
|
+
create: (...values) => {
|
|
228
|
+
return new Pack(values);
|
|
229
|
+
},
|
|
230
|
+
concat: (left, right) => {
|
|
231
|
+
return new Pack([...left.values, ...right.values]);
|
|
232
|
+
},
|
|
233
|
+
length: (pack) => {
|
|
234
|
+
return pack.values.length;
|
|
235
|
+
},
|
|
236
|
+
get(pack, index) {
|
|
237
|
+
const value = pack.values[index >>> 0];
|
|
238
|
+
if (value === undefined)
|
|
239
|
+
throw new Error("Not found");
|
|
240
|
+
return value;
|
|
241
|
+
},
|
|
242
|
+
encode: (pack) => {
|
|
243
|
+
return pack_encode(pack);
|
|
244
|
+
},
|
|
245
|
+
decode: (blob) => {
|
|
246
|
+
return pack_decode(blob);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
imports["storage"] = {
|
|
250
|
+
set: (key, value) => {
|
|
251
|
+
const cache = caches.get(module);
|
|
252
|
+
cache.set(key, value);
|
|
253
|
+
writes.push([module, key, value]);
|
|
254
|
+
return;
|
|
255
|
+
},
|
|
256
|
+
get: (key) => {
|
|
257
|
+
const cache = caches.get(module);
|
|
258
|
+
const stale = cache.get(key);
|
|
259
|
+
if (stale != null)
|
|
260
|
+
return stale;
|
|
261
|
+
const result = new Int32Array(new SharedArrayBuffer(4 + 4 + 4, { maxByteLength: ((4 + 4 + 4) + (1024 * 1024)) }));
|
|
262
|
+
helper.postMessage({ method: "storage_get", params: [module, key], result });
|
|
263
|
+
if (Atomics.wait(result, 0, 0) !== "ok")
|
|
264
|
+
throw new Error("Failed to wait");
|
|
265
|
+
if (result[0] === 2)
|
|
266
|
+
throw new Error("Internal error");
|
|
267
|
+
if (result[1] === 2)
|
|
268
|
+
return null;
|
|
269
|
+
const fresh = new Uint8Array(result.buffer, 4 + 4 + 4, result[2]).slice();
|
|
270
|
+
cache.set(key, fresh);
|
|
271
|
+
return fresh;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
let meteredWasmAsBytes;
|
|
275
|
+
if (!existsSync(`${scriptsAsPath}/${module}.metered.wasm`)) {
|
|
276
|
+
const wasmAsBytes = readFileSync(`${scriptsAsPath}/${module}.wasm`);
|
|
277
|
+
const wasmAsParsed = Readable.readFromBytesOrThrow(Wasm.Module, wasmAsBytes);
|
|
278
|
+
meter(wasmAsParsed, "sparks", "consume");
|
|
279
|
+
meteredWasmAsBytes = Writable.writeToBytesOrThrow(wasmAsParsed);
|
|
280
|
+
writeFileSync(`${scriptsAsPath}/${module}.metered.wasm`, meteredWasmAsBytes);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
meteredWasmAsBytes = readFileSync(`${scriptsAsPath}/${module}.metered.wasm`);
|
|
284
|
+
}
|
|
285
|
+
const meteredWasmAsModule = new WebAssembly.Module(meteredWasmAsBytes);
|
|
286
|
+
for (const descriptor of WebAssembly.Module.imports(meteredWasmAsModule)) {
|
|
287
|
+
if (imports[descriptor.module] != null) {
|
|
288
|
+
// NOOP
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (exports[descriptor.module] != null) {
|
|
292
|
+
imports[descriptor.module] = exports[descriptor.module];
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const { instance } = load(descriptor.module);
|
|
296
|
+
imports[descriptor.module] = instance.exports;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const meteredWasmAsInstance = new WebAssembly.Instance(meteredWasmAsModule, imports);
|
|
300
|
+
current.instance = meteredWasmAsInstance;
|
|
301
|
+
current.module = meteredWasmAsModule;
|
|
302
|
+
exports[module] = meteredWasmAsInstance.exports;
|
|
303
|
+
return current;
|
|
304
|
+
};
|
|
305
|
+
const { instance } = load(module);
|
|
306
|
+
if (typeof instance.exports[method] !== "function")
|
|
307
|
+
throw new Error("Not found");
|
|
308
|
+
const result = pack_encode(new Pack([instance.exports[method](...pack_decode(params).values)]));
|
|
309
|
+
return { result, writes, sparks };
|
|
310
|
+
}
|
|
311
|
+
self.addEventListener("message", (event) => {
|
|
312
|
+
try {
|
|
313
|
+
const request = event.data;
|
|
314
|
+
if (request.method === "execute") {
|
|
315
|
+
const [module, method, params, maxsparks] = request.params;
|
|
316
|
+
const start = performance.now();
|
|
317
|
+
const { result, writes, sparks } = run(module, method, params, 1, maxsparks);
|
|
318
|
+
const until = performance.now();
|
|
319
|
+
console.log(`Evaluated ${(until - start).toFixed(2)}ms with ${sparks} sparks`);
|
|
320
|
+
if (writes.length) {
|
|
321
|
+
const result = new Int32Array(new SharedArrayBuffer(4 + 4));
|
|
322
|
+
helper.postMessage({ method: "storage_set", params: [module, method, params, writes], result });
|
|
323
|
+
if (Atomics.wait(result, 0, 0) !== "ok")
|
|
324
|
+
throw new Error("Failed to wait");
|
|
325
|
+
if (result[0] === 2)
|
|
326
|
+
throw new Error("Internal error");
|
|
327
|
+
console.log(`Wrote ${writes.length} events to storage`);
|
|
328
|
+
}
|
|
329
|
+
self.postMessage(new RpcOk(request.id, result));
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (request.method === "simulate") {
|
|
333
|
+
const [module, method, params, maxsparks] = request.params;
|
|
334
|
+
const start = performance.now();
|
|
335
|
+
const { result, sparks } = run(module, method, params, 2, maxsparks);
|
|
336
|
+
const until = performance.now();
|
|
337
|
+
console.log(`Evaluated ${(until - start).toFixed(2)}ms with ${sparks} sparks`);
|
|
338
|
+
self.postMessage(new RpcOk(request.id, result));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (request.method === "verify") {
|
|
342
|
+
// TODO
|
|
343
|
+
}
|
|
344
|
+
throw new RpcMethodNotFoundError();
|
|
345
|
+
}
|
|
346
|
+
catch (cause) {
|
|
347
|
+
const request = event.data;
|
|
348
|
+
console.error(cause);
|
|
349
|
+
self.postMessage(new RpcErr(request.id, RpcError.rewrap(cause)));
|
|
350
|
+
}
|
|
351
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"name": "@hazae41/bobine",
|
|
4
|
+
"version": "0.0.2",
|
|
5
|
+
"description": "Your neighbor's chain of compute",
|
|
6
|
+
"repository": "github:hazae41/bobine",
|
|
7
|
+
"author": "hazae41",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"examine": "deno lint ./src && deno check ./src && deno test -R ./src",
|
|
11
|
+
"version": "deno -RW ./x.vsync.ts && git add deno.json",
|
|
12
|
+
"prepare": "deno -RW ./x.dsync.ts && deno install",
|
|
13
|
+
"prepack": "rm -rf ./out && tsc && tscousin",
|
|
14
|
+
"develop": "deno --env-file=./.env.local -A ./src/mods/server/bin.ts"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"./out"
|
|
18
|
+
],
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./out/mod.d.ts",
|
|
22
|
+
"import": "./out/mod.js"
|
|
23
|
+
},
|
|
24
|
+
"./worker": {
|
|
25
|
+
"types": "./out/mods/worker/bin.d.ts",
|
|
26
|
+
"import": "./out/mods/worker/bin.js"
|
|
27
|
+
},
|
|
28
|
+
"./helper": {
|
|
29
|
+
"types": "./out/mods/helper/bin.d.ts",
|
|
30
|
+
"import": "./out/mods/helper/bin.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@hazae41/binary": "^2.0.2",
|
|
35
|
+
"@hazae41/cursor": "^2.1.3",
|
|
36
|
+
"@hazae41/jsonrpc": "^1.1.11",
|
|
37
|
+
"@hazae41/mutex": "^3.0.1",
|
|
38
|
+
"@hazae41/wasm": "^1.0.0",
|
|
39
|
+
"@tursodatabase/database": "^0.3.2"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@hazae41/phobos": "^2.0.16",
|
|
43
|
+
"@hazae41/tscousin": "^1.0.28",
|
|
44
|
+
"@types/node": "^24.10.1",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"jsonrpc",
|
|
49
|
+
"webassembly",
|
|
50
|
+
"typescript",
|
|
51
|
+
"esmodules",
|
|
52
|
+
"tested",
|
|
53
|
+
"unit-tested"
|
|
54
|
+
]
|
|
55
|
+
}
|