@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,85 @@
|
|
|
1
|
+
// src/node/parallel/commitment_worker.ts
|
|
2
|
+
import { parentPort } from "worker_threads";
|
|
3
|
+
import { Hex as Hex2 } from "@aptos-labs/ts-sdk";
|
|
4
|
+
|
|
5
|
+
// src/core/utils.ts
|
|
6
|
+
import {
|
|
7
|
+
AccountAddress,
|
|
8
|
+
Hex
|
|
9
|
+
} from "@aptos-labs/ts-sdk";
|
|
10
|
+
function zeroPadBytes(buffer, desiredLength) {
|
|
11
|
+
if (buffer.byteLength === desiredLength) {
|
|
12
|
+
return buffer;
|
|
13
|
+
}
|
|
14
|
+
if (buffer.byteLength > desiredLength) {
|
|
15
|
+
return buffer.subarray(0, desiredLength);
|
|
16
|
+
}
|
|
17
|
+
const paddedBuffer = new Uint8Array(desiredLength);
|
|
18
|
+
paddedBuffer.set(buffer);
|
|
19
|
+
return paddedBuffer;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/node/parallel/commitment_worker.ts
|
|
23
|
+
var ffiAvailable = null;
|
|
24
|
+
async function tryCreateFFIEncoder(opts) {
|
|
25
|
+
if (ffiAvailable === false) return null;
|
|
26
|
+
try {
|
|
27
|
+
const ffiPath = "@shelby-protocol/clay-codes-ffi";
|
|
28
|
+
const { createEncoder: createFFIEncoder } = await import(ffiPath);
|
|
29
|
+
const encoder = await createFFIEncoder(opts);
|
|
30
|
+
ffiAvailable = true;
|
|
31
|
+
return encoder;
|
|
32
|
+
} catch {
|
|
33
|
+
ffiAvailable = false;
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function createWASMEncoder(opts) {
|
|
38
|
+
const { createEncoder } = await import("@shelby-protocol/clay-codes");
|
|
39
|
+
return createEncoder(opts);
|
|
40
|
+
}
|
|
41
|
+
async function encodeChunksetForCommitment(chunksetData, encoding) {
|
|
42
|
+
const chunksetSizeBytes = encoding.erasure_k * encoding.chunkSizeBytes;
|
|
43
|
+
const paddedChunkset = zeroPadBytes(chunksetData, chunksetSizeBytes);
|
|
44
|
+
const encoder = await getEncoder(encoding);
|
|
45
|
+
encoder.erasureCode(paddedChunkset);
|
|
46
|
+
const commitment = encoder.getMerkleCommitment();
|
|
47
|
+
const chunkRoots = commitment.chunkRoots.map(
|
|
48
|
+
(root) => Hex2.fromHexInput(root).toString()
|
|
49
|
+
);
|
|
50
|
+
const chunksetRoot = Hex2.fromHexInput(commitment.chunksetRoot).toString();
|
|
51
|
+
return {
|
|
52
|
+
chunkset_root: chunksetRoot,
|
|
53
|
+
chunk_commitments: chunkRoots
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
var cachedEncoder;
|
|
57
|
+
var cachedEncodingKey;
|
|
58
|
+
async function getEncoder(encoding) {
|
|
59
|
+
const key = `${encoding.erasure_n},${encoding.erasure_k},${encoding.erasure_d},${encoding.chunkSizeBytes},${encoding.enumIndex}`;
|
|
60
|
+
if (!cachedEncoder || cachedEncodingKey !== key) {
|
|
61
|
+
const opts = {
|
|
62
|
+
n: encoding.erasure_n,
|
|
63
|
+
k: encoding.erasure_k,
|
|
64
|
+
d: encoding.erasure_d,
|
|
65
|
+
chunkSizeBytes: encoding.chunkSizeBytes
|
|
66
|
+
};
|
|
67
|
+
cachedEncoder = await tryCreateFFIEncoder(opts) ?? await createWASMEncoder(opts);
|
|
68
|
+
cachedEncodingKey = key;
|
|
69
|
+
}
|
|
70
|
+
return cachedEncoder;
|
|
71
|
+
}
|
|
72
|
+
parentPort?.on("message", async (input) => {
|
|
73
|
+
try {
|
|
74
|
+
const result = await encodeChunksetForCommitment(
|
|
75
|
+
input.chunksetData,
|
|
76
|
+
input.encoding
|
|
77
|
+
);
|
|
78
|
+
parentPort?.postMessage({ success: true, result });
|
|
79
|
+
} catch (error) {
|
|
80
|
+
parentPort?.postMessage({
|
|
81
|
+
success: false,
|
|
82
|
+
error: error instanceof Error ? error.message : String(error)
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Worker } from 'node:worker_threads';
|
|
2
|
+
import { ChunksetCommitment } from '../../core/commitments.js';
|
|
3
|
+
import { CommitmentEncoding } from './commitment_worker.js';
|
|
4
|
+
import { WorkerPool, WorkerPoolTask } from './worker_pool.js';
|
|
5
|
+
import '@aptos-labs/ts-sdk';
|
|
6
|
+
import 'zod';
|
|
7
|
+
import '../../clay-codes-DdXABBDx.js';
|
|
8
|
+
import '@shelby-protocol/clay-codes';
|
|
9
|
+
|
|
10
|
+
interface CommitmentTask extends WorkerPoolTask<ChunksetCommitment> {
|
|
11
|
+
chunksetIdx: number;
|
|
12
|
+
chunksetData: Uint8Array;
|
|
13
|
+
encoding: CommitmentEncoding;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Worker pool that encodes chunksets in parallel and returns their commitments.
|
|
17
|
+
*
|
|
18
|
+
* Unlike the RPC server's EncodeWorkerPool, this pool does not write encoded
|
|
19
|
+
* chunks to disk — it only computes the ChunksetCommitment. This makes it
|
|
20
|
+
* suitable for the CLI and any other caller that only needs commitments.
|
|
21
|
+
*/
|
|
22
|
+
declare class CommitmentWorkerPool extends WorkerPool<CommitmentTask, ChunksetCommitment> {
|
|
23
|
+
constructor(maxWorkers: number);
|
|
24
|
+
protected postTaskMessage(worker: Worker, task: CommitmentTask): void;
|
|
25
|
+
/**
|
|
26
|
+
* Dispatch a chunkset to a worker for encoding. Returns a promise that
|
|
27
|
+
* resolves to the chunkset's commitment once the worker finishes.
|
|
28
|
+
*
|
|
29
|
+
* The caller must ensure `chunksetData` is not used after this call
|
|
30
|
+
* (its underlying buffer is transferred to the worker thread).
|
|
31
|
+
*/
|
|
32
|
+
encodeChunkset(chunksetIdx: number, chunksetData: Uint8Array, encoding: CommitmentEncoding): Promise<ChunksetCommitment>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { CommitmentWorkerPool };
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
// src/node/parallel/worker_pool.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { Worker } from "worker_threads";
|
|
6
|
+
|
|
7
|
+
// src/node/parallel/coverage_flush.ts
|
|
8
|
+
import { takeCoverage } from "v8";
|
|
9
|
+
var FLUSH_REQUEST = "__shelby_cov_flush_request__";
|
|
10
|
+
var FLUSH_ACK = "__shelby_cov_flush_ack__";
|
|
11
|
+
var coverageEnabled = () => Boolean(process.env.NODE_V8_COVERAGE);
|
|
12
|
+
var isType = (msg, type) => typeof msg === "object" && msg !== null && msg.type === type;
|
|
13
|
+
function flushWorkerCoverage(worker, timeoutMs = 2e3) {
|
|
14
|
+
if (!coverageEnabled()) return Promise.resolve();
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
const onMessage = (msg) => {
|
|
17
|
+
if (isType(msg, FLUSH_ACK)) finish();
|
|
18
|
+
};
|
|
19
|
+
const finish = () => {
|
|
20
|
+
clearTimeout(timer);
|
|
21
|
+
worker.off("message", onMessage);
|
|
22
|
+
resolve();
|
|
23
|
+
};
|
|
24
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
25
|
+
worker.on("message", onMessage);
|
|
26
|
+
try {
|
|
27
|
+
worker.postMessage({ type: FLUSH_REQUEST });
|
|
28
|
+
} catch {
|
|
29
|
+
finish();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/node/parallel/worker_pool.ts
|
|
35
|
+
var WorkerPool = class {
|
|
36
|
+
workers = [];
|
|
37
|
+
queue = [];
|
|
38
|
+
maxWorkers;
|
|
39
|
+
workerPath;
|
|
40
|
+
isTerminating = false;
|
|
41
|
+
activeTasks = /* @__PURE__ */ new Set();
|
|
42
|
+
inFlightCount = 0;
|
|
43
|
+
slotWaiters = [];
|
|
44
|
+
constructor(maxWorkers, workerPath) {
|
|
45
|
+
if (!Number.isSafeInteger(maxWorkers) || maxWorkers < 1) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`maxWorkers must be a positive safe integer, got ${maxWorkers}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
this.maxWorkers = maxWorkers;
|
|
51
|
+
this.workerPath = workerPath;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the path to a worker script relative to the calling module.
|
|
55
|
+
*
|
|
56
|
+
* Pass `import.meta.url` from the subclass file along with the script name
|
|
57
|
+
* (without extension). The extension is inferred from the calling module's
|
|
58
|
+
* own extension so that both the TypeScript source (.ts) and the compiled
|
|
59
|
+
* output (.mjs / .js) are handled correctly.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* constructor(maxWorkers: number) {
|
|
63
|
+
* super(maxWorkers, WorkerPool.resolveWorkerPath(import.meta.url, "my_worker"));
|
|
64
|
+
* }
|
|
65
|
+
*/
|
|
66
|
+
static resolveWorkerPath(importMetaUrl, scriptName) {
|
|
67
|
+
const currentPath = fileURLToPath(importMetaUrl);
|
|
68
|
+
const dir = path.dirname(currentPath);
|
|
69
|
+
const ext = path.extname(currentPath);
|
|
70
|
+
const workerExt = ext === ".mjs" ? ".mjs" : ext === ".js" ? ".js" : ".ts";
|
|
71
|
+
if (workerExt === ".ts") {
|
|
72
|
+
const srcSegment = `${path.sep}src${path.sep}`;
|
|
73
|
+
const srcIdx = currentPath.indexOf(srcSegment);
|
|
74
|
+
if (srcIdx !== -1) {
|
|
75
|
+
const packageRoot = currentPath.slice(0, srcIdx);
|
|
76
|
+
const relFromSrc = path.relative(path.join(packageRoot, "src"), dir);
|
|
77
|
+
return path.join(
|
|
78
|
+
packageRoot,
|
|
79
|
+
"dist",
|
|
80
|
+
"workers",
|
|
81
|
+
relFromSrc,
|
|
82
|
+
`${scriptName}.js`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
let workerPath = path.join(dir, `${scriptName}${workerExt}`);
|
|
87
|
+
if (fs.existsSync(workerPath)) {
|
|
88
|
+
return workerPath;
|
|
89
|
+
}
|
|
90
|
+
workerPath = path.join(dir, "workers", `${scriptName}${workerExt}`);
|
|
91
|
+
if (fs.existsSync(workerPath)) {
|
|
92
|
+
return workerPath;
|
|
93
|
+
}
|
|
94
|
+
return path.join(dir, `${scriptName}${workerExt}`);
|
|
95
|
+
}
|
|
96
|
+
initialize() {
|
|
97
|
+
this.isTerminating = false;
|
|
98
|
+
if (!fs.existsSync(this.workerPath)) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Could not find worker script at ${this.workerPath}. Running as ${path.extname(this.workerPath)}, expected worker at same extension.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
for (let i = 0; i < this.maxWorkers; i++) {
|
|
104
|
+
this.workers.push({ worker: new Worker(this.workerPath), busy: false });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
isInitialized() {
|
|
108
|
+
return this.workers.length > 0;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Acquire a back-pressure slot before submitting work to the pool.
|
|
112
|
+
* Resolves immediately if fewer than `maxWorkers` chunksets are in flight;
|
|
113
|
+
* otherwise waits until a slot is released. Callers must call `releaseSlot()`
|
|
114
|
+
* when the submitted task settles (resolve or reject).
|
|
115
|
+
*
|
|
116
|
+
* Moving back-pressure into the pool ensures a single global bound on
|
|
117
|
+
* buffered memory regardless of how many concurrent callers share the pool.
|
|
118
|
+
*/
|
|
119
|
+
acquireSlot() {
|
|
120
|
+
if (this.inFlightCount < this.maxWorkers) {
|
|
121
|
+
this.inFlightCount++;
|
|
122
|
+
return Promise.resolve();
|
|
123
|
+
}
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
this.slotWaiters.push({
|
|
126
|
+
resolve: () => {
|
|
127
|
+
this.inFlightCount++;
|
|
128
|
+
resolve();
|
|
129
|
+
},
|
|
130
|
+
reject
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
releaseSlot() {
|
|
135
|
+
this.inFlightCount--;
|
|
136
|
+
const next = this.slotWaiters.shift();
|
|
137
|
+
if (next) {
|
|
138
|
+
next.resolve();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
enqueueTask(task) {
|
|
142
|
+
this.activeTasks.add(task);
|
|
143
|
+
if (this.isTerminating) {
|
|
144
|
+
this.activeTasks.delete(task);
|
|
145
|
+
task.reject(
|
|
146
|
+
new Error("Worker pool is being terminated - cannot process new tasks")
|
|
147
|
+
);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const availableWorker = this.workers.find((w) => !w.busy);
|
|
151
|
+
if (availableWorker) {
|
|
152
|
+
this.executeTask(availableWorker, task);
|
|
153
|
+
} else {
|
|
154
|
+
this.queue.push(task);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
processNextQueuedTask(preferredWorker) {
|
|
158
|
+
const nextTask = this.queue.shift();
|
|
159
|
+
if (!nextTask) return;
|
|
160
|
+
if (preferredWorker && !preferredWorker.busy && preferredWorker.worker) {
|
|
161
|
+
this.executeTask(preferredWorker, nextTask);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const availableWorker = this.workers.find((w) => !w.busy && w.worker);
|
|
165
|
+
if (availableWorker) {
|
|
166
|
+
this.executeTask(availableWorker, nextTask);
|
|
167
|
+
} else {
|
|
168
|
+
this.queue.unshift(nextTask);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
rejectAllQueuedTasks(error) {
|
|
172
|
+
const tasks = this.queue.splice(0);
|
|
173
|
+
for (const task of tasks) {
|
|
174
|
+
this.activeTasks.delete(task);
|
|
175
|
+
task.reject(error);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
rejectAllActiveTasks(error) {
|
|
179
|
+
const tasks = Array.from(this.activeTasks);
|
|
180
|
+
this.activeTasks.clear();
|
|
181
|
+
for (const task of tasks) {
|
|
182
|
+
task.reject(error);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
executeTask(workerWrapper, task) {
|
|
186
|
+
workerWrapper.busy = true;
|
|
187
|
+
const cleanup = () => {
|
|
188
|
+
workerWrapper.worker.off("message", messageHandler);
|
|
189
|
+
workerWrapper.worker.off("error", errorHandler);
|
|
190
|
+
workerWrapper.worker.off("exit", exitHandler);
|
|
191
|
+
workerWrapper.busy = false;
|
|
192
|
+
};
|
|
193
|
+
const messageHandler = (message) => {
|
|
194
|
+
cleanup();
|
|
195
|
+
this.activeTasks.delete(task);
|
|
196
|
+
if (message.success && message.result !== void 0) {
|
|
197
|
+
task.resolve(message.result);
|
|
198
|
+
} else {
|
|
199
|
+
task.reject(new Error(message.error ?? "Worker processing failed"));
|
|
200
|
+
}
|
|
201
|
+
this.processNextQueuedTask(workerWrapper);
|
|
202
|
+
};
|
|
203
|
+
const errorHandler = (error) => {
|
|
204
|
+
workerWrapper.worker.off("message", messageHandler);
|
|
205
|
+
workerWrapper.worker.off("error", errorHandler);
|
|
206
|
+
workerWrapper.busy = false;
|
|
207
|
+
this.activeTasks.delete(task);
|
|
208
|
+
task.reject(error);
|
|
209
|
+
};
|
|
210
|
+
const exitHandler = (exitCode) => {
|
|
211
|
+
workerWrapper.worker.off("message", messageHandler);
|
|
212
|
+
workerWrapper.worker.off("error", errorHandler);
|
|
213
|
+
workerWrapper.worker.off("exit", exitHandler);
|
|
214
|
+
workerWrapper.busy = false;
|
|
215
|
+
this.activeTasks.delete(task);
|
|
216
|
+
task.reject(
|
|
217
|
+
new Error(`Worker terminated unexpectedly with exit code ${exitCode}`)
|
|
218
|
+
);
|
|
219
|
+
if (this.isTerminating) return;
|
|
220
|
+
try {
|
|
221
|
+
const newWorker = new Worker(this.workerPath);
|
|
222
|
+
if (this.isTerminating) {
|
|
223
|
+
newWorker.terminate().catch(() => {
|
|
224
|
+
});
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
workerWrapper.worker = newWorker;
|
|
228
|
+
this.processNextQueuedTask(workerWrapper);
|
|
229
|
+
} catch (_error) {
|
|
230
|
+
const index = this.workers.indexOf(workerWrapper);
|
|
231
|
+
if (index > -1) this.workers.splice(index, 1);
|
|
232
|
+
if (this.workers.length === 0) {
|
|
233
|
+
this.rejectAllQueuedTasks(
|
|
234
|
+
new Error(
|
|
235
|
+
"All workers have died and could not be replaced - worker pool is non-functional"
|
|
236
|
+
)
|
|
237
|
+
);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
this.processNextQueuedTask();
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
workerWrapper.worker.on("message", messageHandler);
|
|
244
|
+
workerWrapper.worker.on("error", errorHandler);
|
|
245
|
+
workerWrapper.worker.on("exit", exitHandler);
|
|
246
|
+
this.postTaskMessage(workerWrapper.worker, task);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Terminate all workers and reject all pending tasks.
|
|
250
|
+
*
|
|
251
|
+
* This pool is NOT designed to be reused after termination.
|
|
252
|
+
* Create a new instance if a fresh pool is needed.
|
|
253
|
+
*/
|
|
254
|
+
async terminate() {
|
|
255
|
+
this.isTerminating = true;
|
|
256
|
+
const terminationError = new Error(
|
|
257
|
+
"Worker pool is being terminated - task cancelled"
|
|
258
|
+
);
|
|
259
|
+
this.rejectAllQueuedTasks(terminationError);
|
|
260
|
+
this.rejectAllActiveTasks(terminationError);
|
|
261
|
+
const waiters = this.slotWaiters.splice(0);
|
|
262
|
+
for (const waiter of waiters) {
|
|
263
|
+
waiter.reject(terminationError);
|
|
264
|
+
}
|
|
265
|
+
const terminatePromises = this.workers.map(async (workerWrapper) => {
|
|
266
|
+
const worker = workerWrapper.worker;
|
|
267
|
+
await flushWorkerCoverage(worker);
|
|
268
|
+
await new Promise((resolve) => {
|
|
269
|
+
let resolved = false;
|
|
270
|
+
const doResolve = () => {
|
|
271
|
+
if (!resolved) {
|
|
272
|
+
resolved = true;
|
|
273
|
+
resolve();
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
let timeoutId;
|
|
277
|
+
const exitHandler = () => {
|
|
278
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
279
|
+
doResolve();
|
|
280
|
+
};
|
|
281
|
+
worker.removeAllListeners("message");
|
|
282
|
+
worker.removeAllListeners("error");
|
|
283
|
+
worker.once("exit", exitHandler);
|
|
284
|
+
timeoutId = setTimeout(() => {
|
|
285
|
+
worker.removeListener("exit", exitHandler);
|
|
286
|
+
doResolve();
|
|
287
|
+
}, 2e3);
|
|
288
|
+
worker.terminate().catch(() => {
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
await Promise.all(terminatePromises);
|
|
293
|
+
this.workers = [];
|
|
294
|
+
this.queue = [];
|
|
295
|
+
this.inFlightCount = 0;
|
|
296
|
+
this.isTerminating = false;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// src/node/parallel/commitment_worker_pool.ts
|
|
301
|
+
var CommitmentWorkerPool = class extends WorkerPool {
|
|
302
|
+
constructor(maxWorkers) {
|
|
303
|
+
super(
|
|
304
|
+
maxWorkers,
|
|
305
|
+
WorkerPool.resolveWorkerPath(import.meta.url, "commitment_worker")
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
postTaskMessage(worker, task) {
|
|
309
|
+
worker.postMessage(
|
|
310
|
+
{
|
|
311
|
+
chunksetData: task.chunksetData,
|
|
312
|
+
encoding: task.encoding
|
|
313
|
+
},
|
|
314
|
+
[task.chunksetData.buffer]
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Dispatch a chunkset to a worker for encoding. Returns a promise that
|
|
319
|
+
* resolves to the chunkset's commitment once the worker finishes.
|
|
320
|
+
*
|
|
321
|
+
* The caller must ensure `chunksetData` is not used after this call
|
|
322
|
+
* (its underlying buffer is transferred to the worker thread).
|
|
323
|
+
*/
|
|
324
|
+
encodeChunkset(chunksetIdx, chunksetData, encoding) {
|
|
325
|
+
return new Promise((resolve, reject) => {
|
|
326
|
+
const task = {
|
|
327
|
+
chunksetIdx,
|
|
328
|
+
chunksetData,
|
|
329
|
+
encoding,
|
|
330
|
+
resolve,
|
|
331
|
+
reject
|
|
332
|
+
};
|
|
333
|
+
this.enqueueTask(task);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
export {
|
|
338
|
+
CommitmentWorkerPool
|
|
339
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MessagePort, Worker } from 'node:worker_threads';
|
|
2
|
+
|
|
3
|
+
declare function isCoverageFlushMessage(msg: unknown): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Worker side: handle flush requests by writing this thread's V8 coverage. Call
|
|
6
|
+
* once near the top of each worker entry. No-op unless NODE_V8_COVERAGE is set.
|
|
7
|
+
*/
|
|
8
|
+
declare function registerWorkerCoverageFlush(port: MessagePort | null): void;
|
|
9
|
+
/**
|
|
10
|
+
* Pool side: ask a worker to flush its V8 coverage and wait (briefly) for the ack,
|
|
11
|
+
* so the write completes before the worker is terminated. No-op unless
|
|
12
|
+
* NODE_V8_COVERAGE is set; always resolves (on ack or timeout) and never throws.
|
|
13
|
+
*/
|
|
14
|
+
declare function flushWorkerCoverage(worker: Worker, timeoutMs?: number): Promise<void>;
|
|
15
|
+
|
|
16
|
+
export { flushWorkerCoverage, isCoverageFlushMessage, registerWorkerCoverageFlush };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/node/parallel/coverage_flush.ts
|
|
2
|
+
import { takeCoverage } from "v8";
|
|
3
|
+
var FLUSH_REQUEST = "__shelby_cov_flush_request__";
|
|
4
|
+
var FLUSH_ACK = "__shelby_cov_flush_ack__";
|
|
5
|
+
var coverageEnabled = () => Boolean(process.env.NODE_V8_COVERAGE);
|
|
6
|
+
var isType = (msg, type) => typeof msg === "object" && msg !== null && msg.type === type;
|
|
7
|
+
function isCoverageFlushMessage(msg) {
|
|
8
|
+
return isType(msg, FLUSH_REQUEST) || isType(msg, FLUSH_ACK);
|
|
9
|
+
}
|
|
10
|
+
function registerWorkerCoverageFlush(port) {
|
|
11
|
+
if (!coverageEnabled() || !port) return;
|
|
12
|
+
port.on("message", (msg) => {
|
|
13
|
+
if (isType(msg, FLUSH_REQUEST)) {
|
|
14
|
+
takeCoverage();
|
|
15
|
+
port.postMessage({ type: FLUSH_ACK });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function flushWorkerCoverage(worker, timeoutMs = 2e3) {
|
|
20
|
+
if (!coverageEnabled()) return Promise.resolve();
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const onMessage = (msg) => {
|
|
23
|
+
if (isType(msg, FLUSH_ACK)) finish();
|
|
24
|
+
};
|
|
25
|
+
const finish = () => {
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
worker.off("message", onMessage);
|
|
28
|
+
resolve();
|
|
29
|
+
};
|
|
30
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
31
|
+
worker.on("message", onMessage);
|
|
32
|
+
try {
|
|
33
|
+
worker.postMessage({ type: FLUSH_REQUEST });
|
|
34
|
+
} catch {
|
|
35
|
+
finish();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
flushWorkerCoverage,
|
|
41
|
+
isCoverageFlushMessage,
|
|
42
|
+
registerWorkerCoverageFlush
|
|
43
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CommitmentWorkerPool } from './commitment_worker_pool.js';
|
|
2
|
+
import 'node:worker_threads';
|
|
3
|
+
import '../../core/commitments.js';
|
|
4
|
+
import '@aptos-labs/ts-sdk';
|
|
5
|
+
import 'zod';
|
|
6
|
+
import '../../clay-codes-DdXABBDx.js';
|
|
7
|
+
import '@shelby-protocol/clay-codes';
|
|
8
|
+
import './commitment_worker.js';
|
|
9
|
+
import './worker_pool.js';
|
|
10
|
+
|
|
11
|
+
interface CommitmentWorkerPoolOptions {
|
|
12
|
+
/** Worker thread count. Defaults to availableParallelism() - 1, minimum 1. */
|
|
13
|
+
workers?: number;
|
|
14
|
+
}
|
|
15
|
+
declare function defaultCommitmentWorkerCount(): number;
|
|
16
|
+
/**
|
|
17
|
+
* Create and initialize a caller-owned commitment worker pool.
|
|
18
|
+
*
|
|
19
|
+
* The returned pool is ready to pass to `generateCommitmentsParallel`.
|
|
20
|
+
* Callers own its lifecycle and must call `terminate()` during shutdown.
|
|
21
|
+
*/
|
|
22
|
+
declare function createDefaultCommitmentWorkerPool(options?: CommitmentWorkerPoolOptions): CommitmentWorkerPool;
|
|
23
|
+
|
|
24
|
+
export { type CommitmentWorkerPoolOptions, createDefaultCommitmentWorkerPool, defaultCommitmentWorkerCount };
|