@shelby-protocol/sdk 0.3.0 → 0.4.0
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/dist/browser/index.d.ts +19 -10
- package/dist/browser/index.mjs +4200 -146
- package/dist/core/aptos-explorer.mjs +46 -5
- package/dist/core/blobs.mjs +12 -4
- package/dist/core/chunk.mjs +58 -9
- package/dist/core/clients/ShelbyBlobClient.d.ts +155 -83
- package/dist/core/clients/ShelbyBlobClient.mjs +1286 -17
- package/dist/core/clients/ShelbyClient.d.ts +39 -6
- package/dist/core/clients/ShelbyClient.mjs +3233 -23
- package/dist/core/clients/ShelbyClientConfig.d.ts +9 -1
- package/dist/core/clients/ShelbyClientConfig.mjs +0 -1
- package/dist/core/clients/ShelbyMetadataClient.d.ts +20 -6
- package/dist/core/clients/ShelbyMetadataClient.mjs +388 -6
- package/dist/core/clients/ShelbyMicropaymentChannelClient.d.ts +7 -7
- package/dist/core/clients/ShelbyMicropaymentChannelClient.mjs +894 -13
- package/dist/core/clients/ShelbyPlacementGroupClient.mjs +310 -8
- package/dist/core/clients/ShelbyRPCClient.d.ts +117 -25
- package/dist/core/clients/ShelbyRPCClient.mjs +1245 -12
- package/dist/core/clients/index.d.ts +4 -2
- package/dist/core/clients/index.mjs +3802 -39
- package/dist/core/clients/utils.mjs +65 -6
- package/dist/core/commitments.d.ts +12 -1
- package/dist/core/commitments.mjs +277 -11
- package/dist/core/constants.d.ts +3 -3
- package/dist/core/constants.mjs +39 -16
- package/dist/core/erasure/clay-codes.mjs +129 -4
- package/dist/core/erasure/constants.d.ts +16 -1
- package/dist/core/erasure/constants.mjs +48 -11
- package/dist/core/erasure/default.mjs +191 -9
- package/dist/core/erasure/index.d.ts +1 -1
- package/dist/core/erasure/index.mjs +281 -24
- package/dist/core/erasure/reed-solomon.mjs +68 -4
- package/dist/core/erasure/utils.mjs +0 -2
- package/dist/core/errors.d.ts +20 -1
- package/dist/core/errors.mjs +58 -8
- package/dist/core/index.d.ts +9 -9
- package/dist/core/index.mjs +4194 -146
- package/dist/core/layout.mjs +37 -5
- package/dist/core/networks.mjs +10 -5
- package/dist/core/operations/generated/sdk.d.ts +807 -42
- package/dist/core/operations/generated/sdk.mjs +267 -18
- package/dist/core/operations/index.d.ts +1 -1
- package/dist/core/operations/index.mjs +368 -24
- package/dist/core/promises.mjs +4 -4
- package/dist/core/rpc-responses.d.ts +1 -57
- package/dist/core/rpc-responses.mjs +14 -15
- package/dist/core/shelby-explorer.mjs +31 -6
- package/dist/core/sp/chunk_proof.d.ts +23 -0
- package/dist/core/sp/chunk_proof.mjs +113 -0
- package/dist/core/sp/index.d.ts +3 -0
- package/dist/core/sp/index.mjs +402 -0
- package/dist/core/sp/sp_write_client.d.ts +53 -0
- package/dist/core/sp/sp_write_client.mjs +302 -0
- package/dist/core/strings.mjs +0 -2
- package/dist/core/types/blobs.d.ts +24 -5
- package/dist/core/types/blobs.mjs +24 -1
- package/dist/core/types/index.d.ts +2 -2
- package/dist/core/types/index.mjs +275 -10
- package/dist/core/types/payments.d.ts +6 -6
- package/dist/core/types/payments.mjs +230 -5
- package/dist/core/types/placement_groups.mjs +0 -1
- package/dist/core/types/storage_providers.d.ts +32 -6
- package/dist/core/types/storage_providers.mjs +22 -1
- package/dist/core/utils.mjs +94 -8
- package/dist/gen/rpc_server_pb.d.ts +295 -0
- package/dist/gen/rpc_server_pb.mjs +28 -0
- package/dist/node/clients/ShelbyNodeClient.d.ts +2 -0
- package/dist/node/clients/ShelbyNodeClient.mjs +3237 -24
- package/dist/node/clients/index.d.ts +2 -0
- package/dist/node/clients/index.mjs +3236 -26
- package/dist/node/index.d.ts +9 -9
- package/dist/node/index.mjs +4217 -153
- package/dist/node/parallel/commitment_worker.d.ts +9 -0
- package/dist/node/parallel/commitment_worker.mjs +85 -0
- package/dist/node/parallel/commitment_worker_pool.d.ts +35 -0
- package/dist/node/parallel/commitment_worker_pool.mjs +339 -0
- package/dist/node/parallel/coverage_flush.d.ts +16 -0
- package/dist/node/parallel/coverage_flush.mjs +43 -0
- package/dist/node/parallel/default_commitment_worker_pool.d.ts +24 -0
- package/dist/node/parallel/default_commitment_worker_pool.mjs +355 -0
- package/dist/node/parallel/index.d.ts +12 -0
- package/dist/node/parallel/index.mjs +631 -0
- package/dist/node/parallel/parallel_commitments.d.ts +32 -0
- package/dist/node/parallel/parallel_commitments.mjs +614 -0
- package/dist/node/parallel/worker_pool.d.ts +74 -0
- package/dist/node/parallel/worker_pool.mjs +301 -0
- package/dist/node/testUtil.mjs +8 -4
- package/package.json +16 -5
- package/dist/chunk-3NRBHSMQ.mjs +0 -12
- package/dist/chunk-5BVSWXQJ.mjs +0 -801
- package/dist/chunk-6FJNAUU5.mjs +0 -278
- package/dist/chunk-6XSZRDLJ.mjs +0 -375
- package/dist/chunk-7P6ASYW6.mjs +0 -9
- package/dist/chunk-A4IG6GSE.mjs +0 -21
- package/dist/chunk-AD2G3QYD.mjs +0 -0
- package/dist/chunk-AUQDI5BS.mjs +0 -43
- package/dist/chunk-AY3HBWXA.mjs +0 -51
- package/dist/chunk-BDSW5PHM.mjs +0 -37
- package/dist/chunk-C6RQ3AEU.mjs +0 -51
- package/dist/chunk-CQ6QPIZK.mjs +0 -37
- package/dist/chunk-D6GQHO6G.mjs +0 -15
- package/dist/chunk-EM67QTMR.mjs +0 -0
- package/dist/chunk-I6NG5GNL.mjs +0 -8
- package/dist/chunk-JTXYKO3U.mjs +0 -38
- package/dist/chunk-KG2TMLEV.mjs +0 -207
- package/dist/chunk-M3CP5TFP.mjs +0 -39
- package/dist/chunk-M7W43K2Q.mjs +0 -505
- package/dist/chunk-MB7C7VQF.mjs +0 -0
- package/dist/chunk-MNCQBEMS.mjs +0 -55
- package/dist/chunk-MQUVYMNQ.mjs +0 -0
- package/dist/chunk-NEO7Z3ZQ.mjs +0 -49
- package/dist/chunk-NHWWORCH.mjs +0 -72
- package/dist/chunk-OEK24ON7.mjs +0 -135
- package/dist/chunk-QQ57OGQ2.mjs +0 -0
- package/dist/chunk-RNNJXTX2.mjs +0 -154
- package/dist/chunk-S6Q4OG7H.mjs +0 -83
- package/dist/chunk-TET3DJEO.mjs +0 -199
- package/dist/chunk-UEZNZBJO.mjs +0 -104
- package/dist/chunk-V46D5NBW.mjs +0 -11
- package/dist/chunk-V4B3GAMQ.mjs +0 -42
- package/dist/chunk-WFWRBNTW.mjs +0 -412
- package/dist/chunk-WTICJPDB.mjs +0 -0
- package/dist/chunk-XNEIWM4O.mjs +0 -0
- package/dist/chunk-Z4FZ7W6L.mjs +0 -39
- package/dist/chunk-Z7RFCADT.mjs +0 -0
- package/dist/chunk-ZHXCVRZX.mjs +0 -0
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
// src/node/parallel/parallel_commitments.ts
|
|
2
|
+
import { Hex as Hex3 } from "@aptos-labs/ts-sdk";
|
|
3
|
+
|
|
4
|
+
// src/core/commitments.ts
|
|
5
|
+
import { Hex as Hex2 } from "@aptos-labs/ts-sdk";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
// src/core/erasure/constants.ts
|
|
9
|
+
var ERASURE_CODE_PARAMS = {
|
|
10
|
+
["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */]: {
|
|
11
|
+
// total chunks (data + parity)
|
|
12
|
+
erasure_n: 16,
|
|
13
|
+
// data chunks
|
|
14
|
+
erasure_k: 10,
|
|
15
|
+
// helper nodes
|
|
16
|
+
erasure_d: 13,
|
|
17
|
+
// enum index
|
|
18
|
+
enumIndex: 0
|
|
19
|
+
},
|
|
20
|
+
["ClayCode_4Total_2Data_3Helper" /* ClayCode_4Total_2Data_3Helper */]: {
|
|
21
|
+
// total chunks (data + parity)
|
|
22
|
+
erasure_n: 4,
|
|
23
|
+
// data chunks
|
|
24
|
+
erasure_k: 2,
|
|
25
|
+
// helper nodes
|
|
26
|
+
erasure_d: 3,
|
|
27
|
+
// enum index
|
|
28
|
+
enumIndex: 1
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var DEFAULT_ERASURE_N = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_n;
|
|
32
|
+
var DEFAULT_ERASURE_K = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_k;
|
|
33
|
+
var DEFAULT_ERASURE_D = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_d;
|
|
34
|
+
var DEFAULT_ERASURE_M = DEFAULT_ERASURE_N - DEFAULT_ERASURE_K;
|
|
35
|
+
|
|
36
|
+
// src/core/chunk.ts
|
|
37
|
+
var CHUNK_SIZE_PARAMS = {
|
|
38
|
+
["ChunkSet10MiB_Chunk1MiB" /* ChunkSet10MiB_Chunk1MiB */]: {
|
|
39
|
+
// 1MiB
|
|
40
|
+
chunkSizeBytes: 1 * 1024 * 1024,
|
|
41
|
+
// 10MiB
|
|
42
|
+
chunksetSizeBytes: 10 * 1024 * 1024
|
|
43
|
+
},
|
|
44
|
+
["ChunkSet2MiB_Chunk1MiB" /* ChunkSet2MiB_Chunk1MiB */]: {
|
|
45
|
+
// 1MiB
|
|
46
|
+
chunkSizeBytes: 1 * 1024 * 1024,
|
|
47
|
+
// 2MiB
|
|
48
|
+
chunksetSizeBytes: 2 * 1024 * 1024
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var DEFAULT_CHUNK_SIZE_BYTES = CHUNK_SIZE_PARAMS["ChunkSet10MiB_Chunk1MiB" /* ChunkSet10MiB_Chunk1MiB */].chunkSizeBytes;
|
|
52
|
+
var DEFAULT_CHUNKSET_SIZE_BYTES = CHUNK_SIZE_PARAMS["ChunkSet10MiB_Chunk1MiB" /* ChunkSet10MiB_Chunk1MiB */].chunksetSizeBytes;
|
|
53
|
+
var ERASURE_CODE_AND_CHUNK_MAPPING = {
|
|
54
|
+
["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */]: {
|
|
55
|
+
...CHUNK_SIZE_PARAMS.ChunkSet10MiB_Chunk1MiB
|
|
56
|
+
},
|
|
57
|
+
["ClayCode_4Total_2Data_3Helper" /* ClayCode_4Total_2Data_3Helper */]: {
|
|
58
|
+
...CHUNK_SIZE_PARAMS.ChunkSet2MiB_Chunk1MiB
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/core/utils.ts
|
|
63
|
+
import {
|
|
64
|
+
AccountAddress,
|
|
65
|
+
Hex
|
|
66
|
+
} from "@aptos-labs/ts-sdk";
|
|
67
|
+
async function* readInChunks(input, chunkSize) {
|
|
68
|
+
let idx = 0;
|
|
69
|
+
if (isReadableStream(input)) {
|
|
70
|
+
const reader = input.getReader();
|
|
71
|
+
let buffer = new Uint8Array(chunkSize);
|
|
72
|
+
let bufferWriteOffset = 0;
|
|
73
|
+
try {
|
|
74
|
+
while (true) {
|
|
75
|
+
const { value, done } = await reader.read();
|
|
76
|
+
if (done) break;
|
|
77
|
+
if (value === void 0) continue;
|
|
78
|
+
let srcOffset = 0;
|
|
79
|
+
while (srcOffset < value.length) {
|
|
80
|
+
const remainingCapacity = chunkSize - bufferWriteOffset;
|
|
81
|
+
const bytesToCopy = Math.min(
|
|
82
|
+
remainingCapacity,
|
|
83
|
+
value.length - srcOffset
|
|
84
|
+
);
|
|
85
|
+
buffer.set(
|
|
86
|
+
value.subarray(srcOffset, srcOffset + bytesToCopy),
|
|
87
|
+
bufferWriteOffset
|
|
88
|
+
);
|
|
89
|
+
bufferWriteOffset += bytesToCopy;
|
|
90
|
+
srcOffset += bytesToCopy;
|
|
91
|
+
if (bufferWriteOffset >= chunkSize) {
|
|
92
|
+
yield [idx++, buffer];
|
|
93
|
+
buffer = new Uint8Array(chunkSize);
|
|
94
|
+
bufferWriteOffset = 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} finally {
|
|
99
|
+
reader.releaseLock();
|
|
100
|
+
}
|
|
101
|
+
if (bufferWriteOffset > 0) {
|
|
102
|
+
yield [idx++, buffer.subarray(0, bufferWriteOffset)];
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const view = toUint8Array(input);
|
|
107
|
+
for (let offset = 0; offset < view.byteLength; offset += chunkSize) {
|
|
108
|
+
yield [
|
|
109
|
+
idx++,
|
|
110
|
+
view.subarray(offset, Math.min(offset + chunkSize, view.byteLength))
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function concatHashes(parts) {
|
|
115
|
+
const chunks = parts.map((part) => Hex.fromHexInput(part).toUint8Array());
|
|
116
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
|
|
117
|
+
const combined = new Uint8Array(totalLength);
|
|
118
|
+
let offset = 0;
|
|
119
|
+
for (const chunk of chunks) {
|
|
120
|
+
combined.set(chunk, offset);
|
|
121
|
+
offset += chunk.byteLength;
|
|
122
|
+
}
|
|
123
|
+
return Hex.fromHexInput(
|
|
124
|
+
new Uint8Array(await crypto.subtle.digest("SHA-256", combined))
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
function isReadableStream(value) {
|
|
128
|
+
return typeof value === "object" && value !== null && "getReader" in value && typeof value.getReader === "function";
|
|
129
|
+
}
|
|
130
|
+
function toUint8Array(view) {
|
|
131
|
+
return view instanceof Uint8Array ? view : new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/core/commitments.ts
|
|
135
|
+
var COMMITMENT_SCHEMA_VERSION = "1.3";
|
|
136
|
+
var ChunksetCommitmentSchema = z.object({
|
|
137
|
+
// Chunkset root (vector commitment of child chunks)
|
|
138
|
+
chunkset_root: z.string(),
|
|
139
|
+
// the size is known statically from the current configuration
|
|
140
|
+
chunk_commitments: z.array(z.string())
|
|
141
|
+
}).refine(
|
|
142
|
+
(data) => {
|
|
143
|
+
return data.chunk_commitments.length === DEFAULT_ERASURE_K + DEFAULT_ERASURE_M;
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
message: `Chunkset must have exactly ${DEFAULT_ERASURE_K + DEFAULT_ERASURE_M} chunks (ERASURE_K + ERASURE_M = ${DEFAULT_ERASURE_K} + ${DEFAULT_ERASURE_M})`,
|
|
147
|
+
path: ["chunk_commitments"]
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
function expectedTotalChunksets(rawSize, chunksetSize = DEFAULT_CHUNKSET_SIZE_BYTES) {
|
|
151
|
+
if (chunksetSize <= 0) {
|
|
152
|
+
throw new Error("chunksetSize must be positive");
|
|
153
|
+
}
|
|
154
|
+
if (rawSize === 0) return 1;
|
|
155
|
+
return Math.ceil(rawSize / chunksetSize);
|
|
156
|
+
}
|
|
157
|
+
var BlobCommitmentsSchema = z.object({
|
|
158
|
+
schema_version: z.string(),
|
|
159
|
+
raw_data_size: z.number(),
|
|
160
|
+
// FIXME I am not sure about this being here, or if it should be somewhere else
|
|
161
|
+
blob_merkle_root: z.string(),
|
|
162
|
+
chunkset_commitments: z.array(ChunksetCommitmentSchema)
|
|
163
|
+
}).refine(
|
|
164
|
+
(data) => {
|
|
165
|
+
return expectedTotalChunksets(data.raw_data_size) === data.chunkset_commitments.length;
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
message: "Total chunkset count mismatches with raw data size",
|
|
169
|
+
// FIXME put more details in here
|
|
170
|
+
path: ["chunkset_commitments"]
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
async function generateMerkleRoot(leafHashes) {
|
|
174
|
+
if (!leafHashes.length) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
"An empty array cannot be used to construct a Merkle tree."
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const zeroArray = new Uint8Array(leafHashes[0].toUint8Array().length);
|
|
180
|
+
const zeroBytes = Hex2.fromHexInput(zeroArray);
|
|
181
|
+
let currentLeaves = leafHashes;
|
|
182
|
+
while (currentLeaves.length > 1) {
|
|
183
|
+
if (currentLeaves.length % 2 !== 0) {
|
|
184
|
+
currentLeaves.push(zeroBytes);
|
|
185
|
+
}
|
|
186
|
+
const nextLeaves = [];
|
|
187
|
+
for (let i = 0; i < currentLeaves.length; i += 2) {
|
|
188
|
+
nextLeaves.push(
|
|
189
|
+
await concatHashes([
|
|
190
|
+
currentLeaves[i].toUint8Array(),
|
|
191
|
+
currentLeaves[i + 1].toUint8Array()
|
|
192
|
+
])
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
currentLeaves = nextLeaves;
|
|
196
|
+
}
|
|
197
|
+
return currentLeaves[0];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/node/parallel/default_commitment_worker_pool.ts
|
|
201
|
+
import { availableParallelism } from "os";
|
|
202
|
+
|
|
203
|
+
// src/node/parallel/worker_pool.ts
|
|
204
|
+
import fs from "fs";
|
|
205
|
+
import path from "path";
|
|
206
|
+
import { fileURLToPath } from "url";
|
|
207
|
+
import { Worker } from "worker_threads";
|
|
208
|
+
|
|
209
|
+
// src/node/parallel/coverage_flush.ts
|
|
210
|
+
import { takeCoverage } from "v8";
|
|
211
|
+
var FLUSH_REQUEST = "__shelby_cov_flush_request__";
|
|
212
|
+
var FLUSH_ACK = "__shelby_cov_flush_ack__";
|
|
213
|
+
var coverageEnabled = () => Boolean(process.env.NODE_V8_COVERAGE);
|
|
214
|
+
var isType = (msg, type) => typeof msg === "object" && msg !== null && msg.type === type;
|
|
215
|
+
function flushWorkerCoverage(worker, timeoutMs = 2e3) {
|
|
216
|
+
if (!coverageEnabled()) return Promise.resolve();
|
|
217
|
+
return new Promise((resolve) => {
|
|
218
|
+
const onMessage = (msg) => {
|
|
219
|
+
if (isType(msg, FLUSH_ACK)) finish();
|
|
220
|
+
};
|
|
221
|
+
const finish = () => {
|
|
222
|
+
clearTimeout(timer);
|
|
223
|
+
worker.off("message", onMessage);
|
|
224
|
+
resolve();
|
|
225
|
+
};
|
|
226
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
227
|
+
worker.on("message", onMessage);
|
|
228
|
+
try {
|
|
229
|
+
worker.postMessage({ type: FLUSH_REQUEST });
|
|
230
|
+
} catch {
|
|
231
|
+
finish();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/node/parallel/worker_pool.ts
|
|
237
|
+
var WorkerPool = class {
|
|
238
|
+
workers = [];
|
|
239
|
+
queue = [];
|
|
240
|
+
maxWorkers;
|
|
241
|
+
workerPath;
|
|
242
|
+
isTerminating = false;
|
|
243
|
+
activeTasks = /* @__PURE__ */ new Set();
|
|
244
|
+
inFlightCount = 0;
|
|
245
|
+
slotWaiters = [];
|
|
246
|
+
constructor(maxWorkers, workerPath) {
|
|
247
|
+
if (!Number.isSafeInteger(maxWorkers) || maxWorkers < 1) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`maxWorkers must be a positive safe integer, got ${maxWorkers}`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
this.maxWorkers = maxWorkers;
|
|
253
|
+
this.workerPath = workerPath;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Resolve the path to a worker script relative to the calling module.
|
|
257
|
+
*
|
|
258
|
+
* Pass `import.meta.url` from the subclass file along with the script name
|
|
259
|
+
* (without extension). The extension is inferred from the calling module's
|
|
260
|
+
* own extension so that both the TypeScript source (.ts) and the compiled
|
|
261
|
+
* output (.mjs / .js) are handled correctly.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* constructor(maxWorkers: number) {
|
|
265
|
+
* super(maxWorkers, WorkerPool.resolveWorkerPath(import.meta.url, "my_worker"));
|
|
266
|
+
* }
|
|
267
|
+
*/
|
|
268
|
+
static resolveWorkerPath(importMetaUrl, scriptName) {
|
|
269
|
+
const currentPath = fileURLToPath(importMetaUrl);
|
|
270
|
+
const dir = path.dirname(currentPath);
|
|
271
|
+
const ext = path.extname(currentPath);
|
|
272
|
+
const workerExt = ext === ".mjs" ? ".mjs" : ext === ".js" ? ".js" : ".ts";
|
|
273
|
+
if (workerExt === ".ts") {
|
|
274
|
+
const srcSegment = `${path.sep}src${path.sep}`;
|
|
275
|
+
const srcIdx = currentPath.indexOf(srcSegment);
|
|
276
|
+
if (srcIdx !== -1) {
|
|
277
|
+
const packageRoot = currentPath.slice(0, srcIdx);
|
|
278
|
+
const relFromSrc = path.relative(path.join(packageRoot, "src"), dir);
|
|
279
|
+
return path.join(
|
|
280
|
+
packageRoot,
|
|
281
|
+
"dist",
|
|
282
|
+
"workers",
|
|
283
|
+
relFromSrc,
|
|
284
|
+
`${scriptName}.js`
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
let workerPath = path.join(dir, `${scriptName}${workerExt}`);
|
|
289
|
+
if (fs.existsSync(workerPath)) {
|
|
290
|
+
return workerPath;
|
|
291
|
+
}
|
|
292
|
+
workerPath = path.join(dir, "workers", `${scriptName}${workerExt}`);
|
|
293
|
+
if (fs.existsSync(workerPath)) {
|
|
294
|
+
return workerPath;
|
|
295
|
+
}
|
|
296
|
+
return path.join(dir, `${scriptName}${workerExt}`);
|
|
297
|
+
}
|
|
298
|
+
initialize() {
|
|
299
|
+
this.isTerminating = false;
|
|
300
|
+
if (!fs.existsSync(this.workerPath)) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`Could not find worker script at ${this.workerPath}. Running as ${path.extname(this.workerPath)}, expected worker at same extension.`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
for (let i = 0; i < this.maxWorkers; i++) {
|
|
306
|
+
this.workers.push({ worker: new Worker(this.workerPath), busy: false });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
isInitialized() {
|
|
310
|
+
return this.workers.length > 0;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Acquire a back-pressure slot before submitting work to the pool.
|
|
314
|
+
* Resolves immediately if fewer than `maxWorkers` chunksets are in flight;
|
|
315
|
+
* otherwise waits until a slot is released. Callers must call `releaseSlot()`
|
|
316
|
+
* when the submitted task settles (resolve or reject).
|
|
317
|
+
*
|
|
318
|
+
* Moving back-pressure into the pool ensures a single global bound on
|
|
319
|
+
* buffered memory regardless of how many concurrent callers share the pool.
|
|
320
|
+
*/
|
|
321
|
+
acquireSlot() {
|
|
322
|
+
if (this.inFlightCount < this.maxWorkers) {
|
|
323
|
+
this.inFlightCount++;
|
|
324
|
+
return Promise.resolve();
|
|
325
|
+
}
|
|
326
|
+
return new Promise((resolve, reject) => {
|
|
327
|
+
this.slotWaiters.push({
|
|
328
|
+
resolve: () => {
|
|
329
|
+
this.inFlightCount++;
|
|
330
|
+
resolve();
|
|
331
|
+
},
|
|
332
|
+
reject
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
releaseSlot() {
|
|
337
|
+
this.inFlightCount--;
|
|
338
|
+
const next = this.slotWaiters.shift();
|
|
339
|
+
if (next) {
|
|
340
|
+
next.resolve();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
enqueueTask(task) {
|
|
344
|
+
this.activeTasks.add(task);
|
|
345
|
+
if (this.isTerminating) {
|
|
346
|
+
this.activeTasks.delete(task);
|
|
347
|
+
task.reject(
|
|
348
|
+
new Error("Worker pool is being terminated - cannot process new tasks")
|
|
349
|
+
);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const availableWorker = this.workers.find((w) => !w.busy);
|
|
353
|
+
if (availableWorker) {
|
|
354
|
+
this.executeTask(availableWorker, task);
|
|
355
|
+
} else {
|
|
356
|
+
this.queue.push(task);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
processNextQueuedTask(preferredWorker) {
|
|
360
|
+
const nextTask = this.queue.shift();
|
|
361
|
+
if (!nextTask) return;
|
|
362
|
+
if (preferredWorker && !preferredWorker.busy && preferredWorker.worker) {
|
|
363
|
+
this.executeTask(preferredWorker, nextTask);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const availableWorker = this.workers.find((w) => !w.busy && w.worker);
|
|
367
|
+
if (availableWorker) {
|
|
368
|
+
this.executeTask(availableWorker, nextTask);
|
|
369
|
+
} else {
|
|
370
|
+
this.queue.unshift(nextTask);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
rejectAllQueuedTasks(error) {
|
|
374
|
+
const tasks = this.queue.splice(0);
|
|
375
|
+
for (const task of tasks) {
|
|
376
|
+
this.activeTasks.delete(task);
|
|
377
|
+
task.reject(error);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
rejectAllActiveTasks(error) {
|
|
381
|
+
const tasks = Array.from(this.activeTasks);
|
|
382
|
+
this.activeTasks.clear();
|
|
383
|
+
for (const task of tasks) {
|
|
384
|
+
task.reject(error);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
executeTask(workerWrapper, task) {
|
|
388
|
+
workerWrapper.busy = true;
|
|
389
|
+
const cleanup = () => {
|
|
390
|
+
workerWrapper.worker.off("message", messageHandler);
|
|
391
|
+
workerWrapper.worker.off("error", errorHandler);
|
|
392
|
+
workerWrapper.worker.off("exit", exitHandler);
|
|
393
|
+
workerWrapper.busy = false;
|
|
394
|
+
};
|
|
395
|
+
const messageHandler = (message) => {
|
|
396
|
+
cleanup();
|
|
397
|
+
this.activeTasks.delete(task);
|
|
398
|
+
if (message.success && message.result !== void 0) {
|
|
399
|
+
task.resolve(message.result);
|
|
400
|
+
} else {
|
|
401
|
+
task.reject(new Error(message.error ?? "Worker processing failed"));
|
|
402
|
+
}
|
|
403
|
+
this.processNextQueuedTask(workerWrapper);
|
|
404
|
+
};
|
|
405
|
+
const errorHandler = (error) => {
|
|
406
|
+
workerWrapper.worker.off("message", messageHandler);
|
|
407
|
+
workerWrapper.worker.off("error", errorHandler);
|
|
408
|
+
workerWrapper.busy = false;
|
|
409
|
+
this.activeTasks.delete(task);
|
|
410
|
+
task.reject(error);
|
|
411
|
+
};
|
|
412
|
+
const exitHandler = (exitCode) => {
|
|
413
|
+
workerWrapper.worker.off("message", messageHandler);
|
|
414
|
+
workerWrapper.worker.off("error", errorHandler);
|
|
415
|
+
workerWrapper.worker.off("exit", exitHandler);
|
|
416
|
+
workerWrapper.busy = false;
|
|
417
|
+
this.activeTasks.delete(task);
|
|
418
|
+
task.reject(
|
|
419
|
+
new Error(`Worker terminated unexpectedly with exit code ${exitCode}`)
|
|
420
|
+
);
|
|
421
|
+
if (this.isTerminating) return;
|
|
422
|
+
try {
|
|
423
|
+
const newWorker = new Worker(this.workerPath);
|
|
424
|
+
if (this.isTerminating) {
|
|
425
|
+
newWorker.terminate().catch(() => {
|
|
426
|
+
});
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
workerWrapper.worker = newWorker;
|
|
430
|
+
this.processNextQueuedTask(workerWrapper);
|
|
431
|
+
} catch (_error) {
|
|
432
|
+
const index = this.workers.indexOf(workerWrapper);
|
|
433
|
+
if (index > -1) this.workers.splice(index, 1);
|
|
434
|
+
if (this.workers.length === 0) {
|
|
435
|
+
this.rejectAllQueuedTasks(
|
|
436
|
+
new Error(
|
|
437
|
+
"All workers have died and could not be replaced - worker pool is non-functional"
|
|
438
|
+
)
|
|
439
|
+
);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
this.processNextQueuedTask();
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
workerWrapper.worker.on("message", messageHandler);
|
|
446
|
+
workerWrapper.worker.on("error", errorHandler);
|
|
447
|
+
workerWrapper.worker.on("exit", exitHandler);
|
|
448
|
+
this.postTaskMessage(workerWrapper.worker, task);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Terminate all workers and reject all pending tasks.
|
|
452
|
+
*
|
|
453
|
+
* This pool is NOT designed to be reused after termination.
|
|
454
|
+
* Create a new instance if a fresh pool is needed.
|
|
455
|
+
*/
|
|
456
|
+
async terminate() {
|
|
457
|
+
this.isTerminating = true;
|
|
458
|
+
const terminationError = new Error(
|
|
459
|
+
"Worker pool is being terminated - task cancelled"
|
|
460
|
+
);
|
|
461
|
+
this.rejectAllQueuedTasks(terminationError);
|
|
462
|
+
this.rejectAllActiveTasks(terminationError);
|
|
463
|
+
const waiters = this.slotWaiters.splice(0);
|
|
464
|
+
for (const waiter of waiters) {
|
|
465
|
+
waiter.reject(terminationError);
|
|
466
|
+
}
|
|
467
|
+
const terminatePromises = this.workers.map(async (workerWrapper) => {
|
|
468
|
+
const worker = workerWrapper.worker;
|
|
469
|
+
await flushWorkerCoverage(worker);
|
|
470
|
+
await new Promise((resolve) => {
|
|
471
|
+
let resolved = false;
|
|
472
|
+
const doResolve = () => {
|
|
473
|
+
if (!resolved) {
|
|
474
|
+
resolved = true;
|
|
475
|
+
resolve();
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
let timeoutId;
|
|
479
|
+
const exitHandler = () => {
|
|
480
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
481
|
+
doResolve();
|
|
482
|
+
};
|
|
483
|
+
worker.removeAllListeners("message");
|
|
484
|
+
worker.removeAllListeners("error");
|
|
485
|
+
worker.once("exit", exitHandler);
|
|
486
|
+
timeoutId = setTimeout(() => {
|
|
487
|
+
worker.removeListener("exit", exitHandler);
|
|
488
|
+
doResolve();
|
|
489
|
+
}, 2e3);
|
|
490
|
+
worker.terminate().catch(() => {
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
await Promise.all(terminatePromises);
|
|
495
|
+
this.workers = [];
|
|
496
|
+
this.queue = [];
|
|
497
|
+
this.inFlightCount = 0;
|
|
498
|
+
this.isTerminating = false;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// src/node/parallel/commitment_worker_pool.ts
|
|
503
|
+
var CommitmentWorkerPool = class extends WorkerPool {
|
|
504
|
+
constructor(maxWorkers) {
|
|
505
|
+
super(
|
|
506
|
+
maxWorkers,
|
|
507
|
+
WorkerPool.resolveWorkerPath(import.meta.url, "commitment_worker")
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
postTaskMessage(worker, task) {
|
|
511
|
+
worker.postMessage(
|
|
512
|
+
{
|
|
513
|
+
chunksetData: task.chunksetData,
|
|
514
|
+
encoding: task.encoding
|
|
515
|
+
},
|
|
516
|
+
[task.chunksetData.buffer]
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Dispatch a chunkset to a worker for encoding. Returns a promise that
|
|
521
|
+
* resolves to the chunkset's commitment once the worker finishes.
|
|
522
|
+
*
|
|
523
|
+
* The caller must ensure `chunksetData` is not used after this call
|
|
524
|
+
* (its underlying buffer is transferred to the worker thread).
|
|
525
|
+
*/
|
|
526
|
+
encodeChunkset(chunksetIdx, chunksetData, encoding) {
|
|
527
|
+
return new Promise((resolve, reject) => {
|
|
528
|
+
const task = {
|
|
529
|
+
chunksetIdx,
|
|
530
|
+
chunksetData,
|
|
531
|
+
encoding,
|
|
532
|
+
resolve,
|
|
533
|
+
reject
|
|
534
|
+
};
|
|
535
|
+
this.enqueueTask(task);
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
// src/node/parallel/default_commitment_worker_pool.ts
|
|
541
|
+
function defaultCommitmentWorkerCount() {
|
|
542
|
+
return Math.max(1, availableParallelism() - 1);
|
|
543
|
+
}
|
|
544
|
+
function createDefaultCommitmentWorkerPool(options = {}) {
|
|
545
|
+
const pool = new CommitmentWorkerPool(
|
|
546
|
+
options.workers ?? defaultCommitmentWorkerCount()
|
|
547
|
+
);
|
|
548
|
+
pool.initialize();
|
|
549
|
+
return pool;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// src/node/parallel/parallel_commitments.ts
|
|
553
|
+
function toCommitmentEncoding(provider) {
|
|
554
|
+
const cfg = provider.config;
|
|
555
|
+
if (typeof cfg.erasure_d !== "number" || typeof cfg.enumIndex !== "number") {
|
|
556
|
+
throw new Error(
|
|
557
|
+
"generateCommitmentsParallel requires a Clay erasure coding provider (erasure_d and enumIndex must be set on provider.config)"
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
erasure_n: cfg.erasure_n,
|
|
562
|
+
erasure_k: cfg.erasure_k,
|
|
563
|
+
erasure_d: cfg.erasure_d,
|
|
564
|
+
chunkSizeBytes: cfg.chunkSizeBytes,
|
|
565
|
+
enumIndex: cfg.enumIndex
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
async function generateCommitmentsParallel(provider, data, options) {
|
|
569
|
+
const encoding = toCommitmentEncoding(provider);
|
|
570
|
+
const chunksetSize = encoding.erasure_k * encoding.chunkSizeBytes;
|
|
571
|
+
const ownPool = options?.pool === void 0;
|
|
572
|
+
const pool = options?.pool ?? createDefaultCommitmentWorkerPool({ workers: options?.concurrency });
|
|
573
|
+
if (!pool.isInitialized()) {
|
|
574
|
+
throw new Error(
|
|
575
|
+
"The provided CommitmentWorkerPool has not been initialized. Call pool.initialize() before passing it to generateCommitmentsParallel."
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
try {
|
|
579
|
+
const pending = [];
|
|
580
|
+
let rawDataSize = 0;
|
|
581
|
+
for await (const [idx, chunkData] of readInChunks(data, chunksetSize)) {
|
|
582
|
+
rawDataSize += chunkData.length;
|
|
583
|
+
await pool.acquireSlot();
|
|
584
|
+
const copy = new Uint8Array(chunkData);
|
|
585
|
+
const p = pool.encodeChunkset(idx, copy, encoding);
|
|
586
|
+
pending[idx] = p;
|
|
587
|
+
p.then(pool.releaseSlot.bind(pool), pool.releaseSlot.bind(pool));
|
|
588
|
+
}
|
|
589
|
+
if (rawDataSize === 0) {
|
|
590
|
+
pending[0] = pool.encodeChunkset(
|
|
591
|
+
0,
|
|
592
|
+
new Uint8Array(chunksetSize),
|
|
593
|
+
encoding
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
const chunksetCommitments = await Promise.all(pending);
|
|
597
|
+
const chunksetHashes = chunksetCommitments.map(
|
|
598
|
+
(c) => Hex3.fromHexInput(c.chunkset_root)
|
|
599
|
+
);
|
|
600
|
+
return {
|
|
601
|
+
schema_version: COMMITMENT_SCHEMA_VERSION,
|
|
602
|
+
raw_data_size: rawDataSize,
|
|
603
|
+
blob_merkle_root: (await generateMerkleRoot(chunksetHashes)).toString(),
|
|
604
|
+
chunkset_commitments: chunksetCommitments
|
|
605
|
+
};
|
|
606
|
+
} finally {
|
|
607
|
+
if (ownPool) {
|
|
608
|
+
await pool.terminate();
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
export {
|
|
613
|
+
generateCommitmentsParallel
|
|
614
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Worker } from 'node:worker_threads';
|
|
2
|
+
|
|
3
|
+
interface WorkerPoolTask<TResult> {
|
|
4
|
+
resolve: (result: TResult) => void;
|
|
5
|
+
reject: (error: Error) => void;
|
|
6
|
+
}
|
|
7
|
+
interface WorkerWrapper {
|
|
8
|
+
worker: Worker;
|
|
9
|
+
busy: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Generic worker pool. Subclasses define the task type and how to post messages
|
|
13
|
+
* to the worker. The pool manages the worker lifecycle, task queue, and
|
|
14
|
+
* worker replacement on crash.
|
|
15
|
+
*
|
|
16
|
+
* Worker responses must follow the shape { success: boolean, result?: TResult, error?: string }.
|
|
17
|
+
*
|
|
18
|
+
* The worker script path is passed to the constructor and resolved relative to the
|
|
19
|
+
* subclass file using the static `resolveWorkerPath` helper.
|
|
20
|
+
*/
|
|
21
|
+
declare abstract class WorkerPool<TTask extends WorkerPoolTask<TResult>, TResult> {
|
|
22
|
+
protected workers: WorkerWrapper[];
|
|
23
|
+
private queue;
|
|
24
|
+
readonly maxWorkers: number;
|
|
25
|
+
private readonly workerPath;
|
|
26
|
+
private isTerminating;
|
|
27
|
+
private activeTasks;
|
|
28
|
+
private inFlightCount;
|
|
29
|
+
private slotWaiters;
|
|
30
|
+
constructor(maxWorkers: number, workerPath: string);
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the path to a worker script relative to the calling module.
|
|
33
|
+
*
|
|
34
|
+
* Pass `import.meta.url` from the subclass file along with the script name
|
|
35
|
+
* (without extension). The extension is inferred from the calling module's
|
|
36
|
+
* own extension so that both the TypeScript source (.ts) and the compiled
|
|
37
|
+
* output (.mjs / .js) are handled correctly.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* constructor(maxWorkers: number) {
|
|
41
|
+
* super(maxWorkers, WorkerPool.resolveWorkerPath(import.meta.url, "my_worker"));
|
|
42
|
+
* }
|
|
43
|
+
*/
|
|
44
|
+
static resolveWorkerPath(importMetaUrl: string, scriptName: string): string;
|
|
45
|
+
/** Post the task's payload to the worker. */
|
|
46
|
+
protected abstract postTaskMessage(worker: Worker, task: TTask): void;
|
|
47
|
+
initialize(): void;
|
|
48
|
+
isInitialized(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Acquire a back-pressure slot before submitting work to the pool.
|
|
51
|
+
* Resolves immediately if fewer than `maxWorkers` chunksets are in flight;
|
|
52
|
+
* otherwise waits until a slot is released. Callers must call `releaseSlot()`
|
|
53
|
+
* when the submitted task settles (resolve or reject).
|
|
54
|
+
*
|
|
55
|
+
* Moving back-pressure into the pool ensures a single global bound on
|
|
56
|
+
* buffered memory regardless of how many concurrent callers share the pool.
|
|
57
|
+
*/
|
|
58
|
+
acquireSlot(): Promise<void>;
|
|
59
|
+
releaseSlot(): void;
|
|
60
|
+
protected enqueueTask(task: TTask): void;
|
|
61
|
+
private processNextQueuedTask;
|
|
62
|
+
private rejectAllQueuedTasks;
|
|
63
|
+
private rejectAllActiveTasks;
|
|
64
|
+
private executeTask;
|
|
65
|
+
/**
|
|
66
|
+
* Terminate all workers and reject all pending tasks.
|
|
67
|
+
*
|
|
68
|
+
* This pool is NOT designed to be reused after termination.
|
|
69
|
+
* Create a new instance if a fresh pool is needed.
|
|
70
|
+
*/
|
|
71
|
+
terminate(): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { WorkerPool, type WorkerPoolTask };
|