@cofhe/sdk 1.1.0-alpha.3 → 1.1.0-alpha.4
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/core/client.ts +6 -0
- package/core/config.ts +11 -0
- package/core/decrypt/decryptForTxBuilder.ts +33 -9
- package/core/decrypt/decryptForViewBuilder.ts +31 -9
- package/core/decrypt/decryptLimiter.ts +39 -0
- package/core/decrypt/submitRetry.ts +30 -0
- package/core/decrypt/tnDecryptV2.ts +27 -7
- package/core/decrypt/tnSealOutputV2.ts +27 -7
- package/core/error.ts +2 -0
- package/core/test/config.test.ts +15 -0
- package/core/test/decryptBuilders.test.ts +56 -0
- package/core/test/decryptLimiter.test.ts +72 -0
- package/core/test/submitRetry.test.ts +61 -1
- package/dist/acps.js +2 -2
- package/dist/{chunk-WAA2U7CT.js → chunk-37SG44CR.js} +1 -1
- package/dist/{chunk-OC5TGQGG.js → chunk-NJTYZ3CW.js} +1 -0
- package/dist/{chunk-3SD2UCRE.js → chunk-PLHQYVND.js} +1 -1
- package/dist/{chunk-KHM6LYLU.js → chunk-ZYCIUX5C.js} +158 -39
- package/dist/{clientTypes-LvslG7o0.d.ts → clientTypes-DFXuL9kz.d.ts} +31 -0
- package/dist/{clientTypes-BCAMwrqk.d.cts → clientTypes-K6XNB9H2.d.cts} +31 -0
- package/dist/core.cjs +156 -36
- package/dist/core.d.cts +5 -3
- package/dist/core.d.ts +5 -3
- package/dist/core.js +4 -4
- package/dist/node.cjs +155 -36
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +4 -4
- package/dist/web.cjs +155 -36
- package/dist/web.d.cts +1 -1
- package/dist/web.d.ts +1 -1
- package/dist/web.js +4 -4
- package/dist/zkProve.worker.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { createDecryptLimiter } from '../decrypt/decryptLimiter.js';
|
|
3
|
+
import { CofheErrorCode } from '../error.js';
|
|
4
|
+
|
|
5
|
+
function deferred<T>() {
|
|
6
|
+
let resolve!: (value: T) => void;
|
|
7
|
+
const promise = new Promise<T>((r) => (resolve = r));
|
|
8
|
+
return { promise, resolve };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('createDecryptLimiter', () => {
|
|
12
|
+
it('runs at most `max` tasks at a time, in call order', async () => {
|
|
13
|
+
const limit = createDecryptLimiter(2);
|
|
14
|
+
const gates = [deferred<number>(), deferred<number>(), deferred<number>()];
|
|
15
|
+
const started = vi.fn();
|
|
16
|
+
const run = (i: number) =>
|
|
17
|
+
limit(() => {
|
|
18
|
+
started(i);
|
|
19
|
+
return gates[i].promise;
|
|
20
|
+
}, 'decrypt');
|
|
21
|
+
|
|
22
|
+
const all = Promise.all([run(0), run(1), run(2)]);
|
|
23
|
+
await Promise.resolve();
|
|
24
|
+
expect(started.mock.calls.map(([i]) => i)).toEqual([0, 1]);
|
|
25
|
+
|
|
26
|
+
gates[0].resolve(10);
|
|
27
|
+
await Promise.resolve();
|
|
28
|
+
await Promise.resolve();
|
|
29
|
+
expect(started.mock.calls.map(([i]) => i)).toEqual([0, 1, 2]);
|
|
30
|
+
|
|
31
|
+
gates[1].resolve(11);
|
|
32
|
+
gates[2].resolve(12);
|
|
33
|
+
await expect(all).resolves.toEqual([10, 11, 12]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('a task aborted while waiting never starts and frees its place in the queue', async () => {
|
|
37
|
+
const limit = createDecryptLimiter(1);
|
|
38
|
+
const gate = deferred<number>();
|
|
39
|
+
const controller = new AbortController();
|
|
40
|
+
const started = vi.fn();
|
|
41
|
+
|
|
42
|
+
const first = limit(() => gate.promise, 'decrypt');
|
|
43
|
+
const second = limit(async () => started('second'), 'decrypt', controller.signal);
|
|
44
|
+
const third = limit(async () => started('third'), 'decrypt');
|
|
45
|
+
|
|
46
|
+
controller.abort();
|
|
47
|
+
await expect(second).rejects.toMatchObject({ code: CofheErrorCode.Aborted });
|
|
48
|
+
expect(started).not.toHaveBeenCalled();
|
|
49
|
+
|
|
50
|
+
gate.resolve(1);
|
|
51
|
+
await expect(first).resolves.toBe(1);
|
|
52
|
+
await third;
|
|
53
|
+
expect(started).toHaveBeenCalledWith('third');
|
|
54
|
+
expect(started).not.toHaveBeenCalledWith('second');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('a signal that already fired rejects before the task is queued', async () => {
|
|
58
|
+
const limit = createDecryptLimiter(1);
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
controller.abort();
|
|
61
|
+
const task = vi.fn(async () => 1);
|
|
62
|
+
|
|
63
|
+
await expect(limit(task, 'sealOutput', controller.signal)).rejects.toMatchObject({ code: CofheErrorCode.Aborted });
|
|
64
|
+
expect(task).not.toHaveBeenCalled();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('a failed task releases its slot', async () => {
|
|
68
|
+
const limit = createDecryptLimiter(1);
|
|
69
|
+
await expect(limit(async () => Promise.reject(new Error('boom')), 'decrypt')).rejects.toThrow('boom');
|
|
70
|
+
await expect(limit(async () => 2, 'decrypt')).resolves.toBe(2);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
classifySubmitResponse,
|
|
4
|
+
sleepUnlessAborted,
|
|
5
|
+
throwIfAborted,
|
|
6
|
+
throwIfSubmitRetryTimedOut,
|
|
7
|
+
} from '../decrypt/submitRetry.js';
|
|
3
8
|
import { CofheError, CofheErrorCode } from '../error.js';
|
|
4
9
|
|
|
5
10
|
const makeResponse = (opts: {
|
|
@@ -180,3 +185,58 @@ describe('throwIfSubmitRetryTimedOut', () => {
|
|
|
180
185
|
}
|
|
181
186
|
});
|
|
182
187
|
});
|
|
188
|
+
|
|
189
|
+
describe('abort helpers', () => {
|
|
190
|
+
it('throwIfAborted is a no-op without a signal or before it fires, and throws Aborted after', () => {
|
|
191
|
+
const controller = new AbortController();
|
|
192
|
+
expect(() => throwIfAborted(undefined, 'decrypt')).not.toThrow();
|
|
193
|
+
expect(() => throwIfAborted(controller.signal, 'decrypt')).not.toThrow();
|
|
194
|
+
|
|
195
|
+
controller.abort(new Error('navigated away'));
|
|
196
|
+
|
|
197
|
+
let thrown: unknown;
|
|
198
|
+
try {
|
|
199
|
+
throwIfAborted(controller.signal, 'decrypt');
|
|
200
|
+
} catch (e) {
|
|
201
|
+
thrown = e;
|
|
202
|
+
}
|
|
203
|
+
expect(thrown).toBeInstanceOf(CofheError);
|
|
204
|
+
expect((thrown as CofheError).code).toBe(CofheErrorCode.Aborted);
|
|
205
|
+
expect((thrown as CofheError).message).toMatch(/^decrypt aborted by the caller/);
|
|
206
|
+
expect((thrown as CofheError).cause?.message).toBe('navigated away');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('sleepUnlessAborted resolves after the wait, and rejects the moment the signal fires', async () => {
|
|
210
|
+
vi.useFakeTimers();
|
|
211
|
+
try {
|
|
212
|
+
const resolved = vi.fn();
|
|
213
|
+
sleepUnlessAborted(1_000, undefined, 'decrypt').then(resolved);
|
|
214
|
+
await vi.advanceTimersByTimeAsync(1_000);
|
|
215
|
+
expect(resolved).toHaveBeenCalledOnce();
|
|
216
|
+
|
|
217
|
+
const controller = new AbortController();
|
|
218
|
+
const waiting = sleepUnlessAborted(60_000, controller.signal, 'sealOutput');
|
|
219
|
+
const rejected = vi.fn();
|
|
220
|
+
waiting.catch(rejected);
|
|
221
|
+
await vi.advanceTimersByTimeAsync(10);
|
|
222
|
+
expect(rejected).not.toHaveBeenCalled();
|
|
223
|
+
|
|
224
|
+
controller.abort();
|
|
225
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
226
|
+
expect(rejected).toHaveBeenCalledOnce();
|
|
227
|
+
expect(rejected.mock.calls[0][0]).toMatchObject({ code: CofheErrorCode.Aborted });
|
|
228
|
+
expect(rejected.mock.calls[0][0].message).toMatch(/^sealOutput aborted by the caller/);
|
|
229
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
230
|
+
} finally {
|
|
231
|
+
vi.useRealTimers();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('sleepUnlessAborted rejects at once for a signal that already fired', async () => {
|
|
236
|
+
const controller = new AbortController();
|
|
237
|
+
controller.abort();
|
|
238
|
+
await expect(sleepUnlessAborted(60_000, controller.signal, 'decrypt')).rejects.toMatchObject({
|
|
239
|
+
code: CofheErrorCode.Aborted,
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
package/dist/acps.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { ACPScope, ACPUtils, ACP_REVOKER_ABI, ACP_SHARE_REGISTRY_ABI, ACP_SHARE_TUPLE_ABI, ACP_STORE_DEFAULTS, ERC7984_ABI, GenerateSealingKey, ImportACPOptionsValidator, ImportACPValidator, LAYOUT_VERSION as SHARE_METADATA_VERSION, SelfACPOptionsValidator, SelfACPValidator, SharingACPOptionsValidator, SharingACPValidator, SignatureTypes, SignatureUtils, UNLABELLED, ValidationUtils, _acpStore, acpStore, addressNotZeroSchema, addressSchema, bytesNotEmptySchema, bytesSchema, clearAclServedAddresses, clearStaleStore, computeShareId, contractsSchema, decodeShareAppKey, decodeShareMetadata, decodeShareMetadataFull, describeShareLabels, describeShareMetadata, encodeShareMetadata, eventLogLabel, getACP, getACPs, getAclAddress, getAclServedAddresses, getActiveACP, getActiveACPHash, getSignatureTypesAndMessage, handlesSchema, readPostedShares, readShare, readSharesFor, removeACP, removeActiveACPHash, resetStore, seal, setACP, setActiveACPHash, shareAppKey, shareIdOfChainShare, toChainShare, unsealWithPrivateKey, validateImportACP, validateImportACPOptions, validateSelfACP, validateSelfACPOptions, validateSharingACP, validateSharingACPOptions, verifyShareLabel, verifyShareLabels, viewFunctionLabel } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
export { ACPScope, ACPUtils, ACP_REVOKER_ABI, ACP_SHARE_REGISTRY_ABI, ACP_SHARE_TUPLE_ABI, ACP_STORE_DEFAULTS, ERC7984_ABI, GenerateSealingKey, ImportACPOptionsValidator, ImportACPValidator, LAYOUT_VERSION as SHARE_METADATA_VERSION, SelfACPOptionsValidator, SelfACPValidator, SharingACPOptionsValidator, SharingACPValidator, SignatureTypes, SignatureUtils, UNLABELLED, ValidationUtils, _acpStore, acpStore, addressNotZeroSchema, addressSchema, bytesNotEmptySchema, bytesSchema, clearAclServedAddresses, clearStaleStore, computeShareId, contractsSchema, decodeShareAppKey, decodeShareMetadata, decodeShareMetadataFull, describeShareLabels, describeShareMetadata, encodeShareMetadata, eventLogLabel, getACP, getACPs, getAclAddress, getAclServedAddresses, getActiveACP, getActiveACPHash, getSignatureTypesAndMessage, handlesSchema, readPostedShares, readShare, readSharesFor, removeACP, removeActiveACPHash, resetStore, seal, setACP, setActiveACPHash, shareAppKey, shareIdOfChainShare, toChainShare, unsealWithPrivateKey, validateImportACP, validateImportACPOptions, validateSelfACP, validateSelfACPOptions, validateSharingACP, validateSharingACPOptions, verifyShareLabel, verifyShareLabels, viewFunctionLabel } from './chunk-37SG44CR.js';
|
|
2
|
+
import './chunk-NJTYZ3CW.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CofheError, TASK_MANAGER_ID, COFHE_ADDRESS_BOOK_ADDRESS } from './chunk-
|
|
1
|
+
import { CofheError, TASK_MANAGER_ID, COFHE_ADDRESS_BOOK_ADDRESS } from './chunk-NJTYZ3CW.js';
|
|
2
2
|
import { isAddress, getAddress, zeroAddress, isHex, parseAbi, keccak256, padHex, numberToHex, concatHex, toFunctionSelector, size, sliceHex, toHex, encodeAbiParameters, hexToBigInt, stringToHex, hexToNumber, toEventSignature, toFunctionSignature, decodeErrorResult, toEventSelector, decodeAbiParameters } from 'viem';
|
|
3
3
|
import nacl from 'tweetnacl';
|
|
4
4
|
import { z } from 'zod';
|
|
@@ -64,6 +64,7 @@ var CofheErrorCode = /* @__PURE__ */ ((CofheErrorCode2) => {
|
|
|
64
64
|
CofheErrorCode2["ACPVerifierTimeout"] = "ACP_VERIFIER_TIMEOUT";
|
|
65
65
|
CofheErrorCode2["ACPRequired"] = "ACP_REQUIRED";
|
|
66
66
|
CofheErrorCode2["SealFailed"] = "SEAL_FAILED";
|
|
67
|
+
CofheErrorCode2["Aborted"] = "ABORTED";
|
|
67
68
|
return CofheErrorCode2;
|
|
68
69
|
})(CofheErrorCode || {});
|
|
69
70
|
var CofheError = class _CofheError extends Error {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { hardhat as hardhat$1 } from './chunk-UKQAJK4N.js';
|
|
2
|
-
import { acpStore, clearAclServedAddresses, getAclServedAddresses, computeShareId, ACP_SHARE_REGISTRY_ABI, ACPUtils, getTaskManagerAddress, verifyShareLabels, decodeShareMetadata, shareAppKey, readShare, readSharesFor, toChainShare, ACP_REVOKER_ABI, decodeShareAppKey, encodeShareMetadata } from './chunk-
|
|
3
|
-
import { sleep, getPublicClientChainID, getWalletClientAccount, validateBigIntInRange, toBigIntOrThrow, hexToBytes, toHexString, FheUintUTypes } from './chunk-
|
|
4
|
-
import { TFHE_RS_KEY_VERSION, CofheError, TFHE_RS_ZK_MAX_BITS, TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT, MOCKS_THRESHOLD_NETWORK_ADDRESS, MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY, MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY, MOCKS_ZK_VERIFIER_ADDRESS } from './chunk-
|
|
2
|
+
import { acpStore, clearAclServedAddresses, getAclServedAddresses, computeShareId, ACP_SHARE_REGISTRY_ABI, ACPUtils, getTaskManagerAddress, verifyShareLabels, decodeShareMetadata, shareAppKey, readShare, readSharesFor, toChainShare, ACP_REVOKER_ABI, decodeShareAppKey, encodeShareMetadata } from './chunk-37SG44CR.js';
|
|
3
|
+
import { sleep, getPublicClientChainID, getWalletClientAccount, validateBigIntInRange, toBigIntOrThrow, hexToBytes, toHexString, FheUintUTypes } from './chunk-PLHQYVND.js';
|
|
4
|
+
import { TFHE_RS_KEY_VERSION, CofheError, TFHE_RS_ZK_MAX_BITS, TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT, MOCKS_THRESHOLD_NETWORK_ADDRESS, MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY, MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY, MOCKS_ZK_VERIFIER_ADDRESS } from './chunk-NJTYZ3CW.js';
|
|
5
5
|
import { createStore } from 'zustand/vanilla';
|
|
6
6
|
import { parseAbi, isAddress, isAddressEqual, zeroAddress, recoverAddress, encodePacked, keccak256, createWalletClient, http, getAddress, parseSignature, serializeSignature } from 'viem';
|
|
7
7
|
import { hardhat } from 'viem/chains';
|
|
@@ -481,6 +481,8 @@ var CofheConfigSchema = z.object({
|
|
|
481
481
|
supportedChains: z.array(z.custom()),
|
|
482
482
|
/** Default acp expiration in seconds, default is 30 days */
|
|
483
483
|
defaultACPExpiration: z.number().optional().default(60 * 60 * 24 * 30),
|
|
484
|
+
/** Decrypts sent to the threshold network at a time per client; `Infinity` disables the cap (default 8) */
|
|
485
|
+
decryptConcurrency: z.union([z.number().min(1), z.literal(Infinity)]).optional().default(8),
|
|
484
486
|
/** Storage method for fhe keys (defaults to indexedDB on web, filesystem on node) */
|
|
485
487
|
fheKeyStorage: z.object({
|
|
486
488
|
getItem: z.custom((val) => typeof val === "function", {
|
|
@@ -2033,6 +2035,32 @@ function throwIfSubmitRetryTimedOut(params) {
|
|
|
2033
2035
|
});
|
|
2034
2036
|
}
|
|
2035
2037
|
}
|
|
2038
|
+
function decryptAbortedError(operationLabel, signal) {
|
|
2039
|
+
return new CofheError({
|
|
2040
|
+
code: "ABORTED" /* Aborted */,
|
|
2041
|
+
message: `${operationLabel} aborted by the caller`,
|
|
2042
|
+
cause: signal.reason instanceof Error ? signal.reason : void 0
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
function throwIfAborted(signal, operationLabel) {
|
|
2046
|
+
if (signal?.aborted)
|
|
2047
|
+
throw decryptAbortedError(operationLabel, signal);
|
|
2048
|
+
}
|
|
2049
|
+
function sleepUnlessAborted(ms, signal, operationLabel) {
|
|
2050
|
+
return new Promise((resolve, reject) => {
|
|
2051
|
+
if (signal?.aborted)
|
|
2052
|
+
return reject(decryptAbortedError(operationLabel, signal));
|
|
2053
|
+
const onAbort = () => {
|
|
2054
|
+
clearTimeout(timer);
|
|
2055
|
+
reject(decryptAbortedError(operationLabel, signal));
|
|
2056
|
+
};
|
|
2057
|
+
const timer = setTimeout(() => {
|
|
2058
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2059
|
+
resolve();
|
|
2060
|
+
}, ms);
|
|
2061
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2036
2064
|
function normalizeTnSignature(signature) {
|
|
2037
2065
|
if (typeof signature !== "string") {
|
|
2038
2066
|
throw new CofheError({
|
|
@@ -2150,7 +2178,7 @@ function parseCompletedSealOutputResponse(params) {
|
|
|
2150
2178
|
}
|
|
2151
2179
|
return convertSealedData(sealed);
|
|
2152
2180
|
}
|
|
2153
|
-
async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, acp, overallStartTime, retry404TimeoutMs, timeoutMs, onPoll) {
|
|
2181
|
+
async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, acp, overallStartTime, retry404TimeoutMs, timeoutMs, onPoll, signal) {
|
|
2154
2182
|
const body = {
|
|
2155
2183
|
ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
|
|
2156
2184
|
host_chain_id: chainId,
|
|
@@ -2159,6 +2187,7 @@ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, acp
|
|
|
2159
2187
|
let attemptIndex = 0;
|
|
2160
2188
|
let last404ApiErrorMessage;
|
|
2161
2189
|
for (; ; ) {
|
|
2190
|
+
throwIfAborted(signal, "sealOutput");
|
|
2162
2191
|
let response;
|
|
2163
2192
|
try {
|
|
2164
2193
|
response = await cofheFetch(
|
|
@@ -2169,10 +2198,12 @@ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, acp
|
|
|
2169
2198
|
headers: {
|
|
2170
2199
|
"Content-Type": "application/json"
|
|
2171
2200
|
},
|
|
2172
|
-
body: JSON.stringify(body)
|
|
2201
|
+
body: JSON.stringify(body),
|
|
2202
|
+
signal
|
|
2173
2203
|
}
|
|
2174
2204
|
);
|
|
2175
2205
|
} catch (e) {
|
|
2206
|
+
throwIfAborted(signal, "sealOutput");
|
|
2176
2207
|
throw new CofheError({
|
|
2177
2208
|
code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
|
|
2178
2209
|
message: `sealOutput request failed`,
|
|
@@ -2267,13 +2298,14 @@ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, acp
|
|
|
2267
2298
|
intervalMs: SUBMIT_RETRY_INTERVAL_MS,
|
|
2268
2299
|
timeoutMs
|
|
2269
2300
|
});
|
|
2270
|
-
await
|
|
2301
|
+
await sleepUnlessAborted(SUBMIT_RETRY_INTERVAL_MS, signal, "sealOutput");
|
|
2271
2302
|
attemptIndex += 1;
|
|
2272
2303
|
}
|
|
2273
2304
|
}
|
|
2274
|
-
async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStartTime, timeoutMs, onPoll) {
|
|
2305
|
+
async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStartTime, timeoutMs, onPoll, signal) {
|
|
2275
2306
|
let attemptIndex = 0;
|
|
2276
2307
|
while (true) {
|
|
2308
|
+
throwIfAborted(signal, "sealOutput");
|
|
2277
2309
|
const elapsedMs = Date.now() - overallStartTime;
|
|
2278
2310
|
const intervalMs = computeMinuteRampPollIntervalMs(elapsedMs, {
|
|
2279
2311
|
minIntervalMs: POLL_INTERVAL_MS,
|
|
@@ -2308,7 +2340,8 @@ async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStart
|
|
|
2308
2340
|
method: "GET",
|
|
2309
2341
|
headers: {
|
|
2310
2342
|
"Content-Type": "application/json"
|
|
2311
|
-
}
|
|
2343
|
+
},
|
|
2344
|
+
signal
|
|
2312
2345
|
}
|
|
2313
2346
|
);
|
|
2314
2347
|
} catch (e) {
|
|
@@ -2369,7 +2402,7 @@ async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStart
|
|
|
2369
2402
|
requestId
|
|
2370
2403
|
});
|
|
2371
2404
|
}
|
|
2372
|
-
await
|
|
2405
|
+
await sleepUnlessAborted(intervalMs, signal, "sealOutput");
|
|
2373
2406
|
attemptIndex += 1;
|
|
2374
2407
|
}
|
|
2375
2408
|
throw new CofheError({
|
|
@@ -2382,7 +2415,7 @@ async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStart
|
|
|
2382
2415
|
});
|
|
2383
2416
|
}
|
|
2384
2417
|
async function tnSealOutputV2(params) {
|
|
2385
|
-
const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll } = params;
|
|
2418
|
+
const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll, signal } = params;
|
|
2386
2419
|
const timeoutMs = params.timeoutMs ?? DEFAULT_DECRYPT_TIMEOUT_MS;
|
|
2387
2420
|
const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
|
|
2388
2421
|
timeoutMs: retry404TimeoutMs,
|
|
@@ -2398,12 +2431,20 @@ async function tnSealOutputV2(params) {
|
|
|
2398
2431
|
overallStartTime,
|
|
2399
2432
|
normalized404RetryTimeoutMs,
|
|
2400
2433
|
timeoutMs,
|
|
2401
|
-
onPoll
|
|
2434
|
+
onPoll,
|
|
2435
|
+
signal
|
|
2402
2436
|
);
|
|
2403
2437
|
if (submitResult.kind === "completed") {
|
|
2404
2438
|
return submitResult.sealed;
|
|
2405
2439
|
}
|
|
2406
|
-
return await pollSealOutputStatus(
|
|
2440
|
+
return await pollSealOutputStatus(
|
|
2441
|
+
thresholdNetworkUrl,
|
|
2442
|
+
submitResult.requestId,
|
|
2443
|
+
overallStartTime,
|
|
2444
|
+
timeoutMs,
|
|
2445
|
+
onPoll,
|
|
2446
|
+
signal
|
|
2447
|
+
);
|
|
2407
2448
|
}
|
|
2408
2449
|
|
|
2409
2450
|
// core/decrypt/decryptForViewBuilder.ts
|
|
@@ -2416,6 +2457,8 @@ var DecryptForViewBuilder = class extends BaseBuilder {
|
|
|
2416
2457
|
pollCallback;
|
|
2417
2458
|
retry404TimeoutMs = DEFAULT_404_RETRY_TIMEOUT_MS2;
|
|
2418
2459
|
timeoutMs;
|
|
2460
|
+
signal;
|
|
2461
|
+
limit;
|
|
2419
2462
|
constructor(params) {
|
|
2420
2463
|
super({
|
|
2421
2464
|
config: params.config,
|
|
@@ -2429,6 +2472,7 @@ var DecryptForViewBuilder = class extends BaseBuilder {
|
|
|
2429
2472
|
this.utype = params.utype;
|
|
2430
2473
|
this.acpHash = params.acpHash;
|
|
2431
2474
|
this.acp = params.acp;
|
|
2475
|
+
this.limit = params.limit ?? ((task) => task());
|
|
2432
2476
|
}
|
|
2433
2477
|
/**
|
|
2434
2478
|
* @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct acp.
|
|
@@ -2499,6 +2543,15 @@ var DecryptForViewBuilder = class extends BaseBuilder {
|
|
|
2499
2543
|
this.timeoutMs = timeoutMs;
|
|
2500
2544
|
return this;
|
|
2501
2545
|
}
|
|
2546
|
+
/**
|
|
2547
|
+
* Stops the decrypt when `signal` fires: the pending request is aborted, no further attempt is
|
|
2548
|
+
* sent, and `execute()` rejects with `Aborted`. A request already processing on the threshold
|
|
2549
|
+
* network is not cancelled.
|
|
2550
|
+
*/
|
|
2551
|
+
setSignal(signal) {
|
|
2552
|
+
this.signal = signal;
|
|
2553
|
+
return this;
|
|
2554
|
+
}
|
|
2502
2555
|
withACP(acpOrACPHash) {
|
|
2503
2556
|
if (typeof acpOrACPHash === "string") {
|
|
2504
2557
|
this.acpHash = acpOrACPHash;
|
|
@@ -2622,15 +2675,20 @@ var DecryptForViewBuilder = class extends BaseBuilder {
|
|
|
2622
2675
|
this.assertPublicClient();
|
|
2623
2676
|
const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
|
|
2624
2677
|
const wireAcp = ACPUtils.getPublic(acp, true);
|
|
2625
|
-
const sealed = await
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2678
|
+
const sealed = await this.limit(
|
|
2679
|
+
() => tnSealOutputV2({
|
|
2680
|
+
ctHash: this.ctHash,
|
|
2681
|
+
chainId: this.chainId,
|
|
2682
|
+
acp: wireAcp,
|
|
2683
|
+
thresholdNetworkUrl,
|
|
2684
|
+
retry404TimeoutMs: this.retry404TimeoutMs,
|
|
2685
|
+
timeoutMs: this.timeoutMs,
|
|
2686
|
+
onPoll: this.pollCallback,
|
|
2687
|
+
signal: this.signal
|
|
2688
|
+
}),
|
|
2689
|
+
"sealOutput",
|
|
2690
|
+
this.signal
|
|
2691
|
+
);
|
|
2634
2692
|
return ACPUtils.unseal(acp, sealed);
|
|
2635
2693
|
}
|
|
2636
2694
|
/**
|
|
@@ -2847,7 +2905,7 @@ function assertDecryptStatusResponseV2(value) {
|
|
|
2847
2905
|
}
|
|
2848
2906
|
return value;
|
|
2849
2907
|
}
|
|
2850
|
-
async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, acp, overallStartTime, retry404TimeoutMs, timeoutMs, onPoll) {
|
|
2908
|
+
async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, acp, overallStartTime, retry404TimeoutMs, timeoutMs, onPoll, signal) {
|
|
2851
2909
|
const body = {
|
|
2852
2910
|
ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
|
|
2853
2911
|
host_chain_id: chainId
|
|
@@ -2858,6 +2916,7 @@ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, acp,
|
|
|
2858
2916
|
let attemptIndex = 0;
|
|
2859
2917
|
let last404ApiErrorMessage;
|
|
2860
2918
|
for (; ; ) {
|
|
2919
|
+
throwIfAborted(signal, "decrypt");
|
|
2861
2920
|
let response;
|
|
2862
2921
|
try {
|
|
2863
2922
|
response = await cofheFetch(
|
|
@@ -2868,10 +2927,12 @@ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, acp,
|
|
|
2868
2927
|
headers: {
|
|
2869
2928
|
"Content-Type": "application/json"
|
|
2870
2929
|
},
|
|
2871
|
-
body: JSON.stringify(body)
|
|
2930
|
+
body: JSON.stringify(body),
|
|
2931
|
+
signal
|
|
2872
2932
|
}
|
|
2873
2933
|
);
|
|
2874
2934
|
} catch (e) {
|
|
2935
|
+
throwIfAborted(signal, "decrypt");
|
|
2875
2936
|
throw new CofheError({
|
|
2876
2937
|
code: "DECRYPT_FAILED" /* DecryptFailed */,
|
|
2877
2938
|
message: `decrypt request failed`,
|
|
@@ -2968,13 +3029,14 @@ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, acp,
|
|
|
2968
3029
|
intervalMs: SUBMIT_RETRY_INTERVAL_MS2,
|
|
2969
3030
|
timeoutMs
|
|
2970
3031
|
});
|
|
2971
|
-
await
|
|
3032
|
+
await sleepUnlessAborted(SUBMIT_RETRY_INTERVAL_MS2, signal, "decrypt");
|
|
2972
3033
|
attemptIndex += 1;
|
|
2973
3034
|
}
|
|
2974
3035
|
}
|
|
2975
|
-
async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartTime, timeoutMs, onPoll) {
|
|
3036
|
+
async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartTime, timeoutMs, onPoll, signal) {
|
|
2976
3037
|
let attemptIndex = 0;
|
|
2977
3038
|
while (true) {
|
|
3039
|
+
throwIfAborted(signal, "decrypt");
|
|
2978
3040
|
const elapsedMs = Date.now() - overallStartTime;
|
|
2979
3041
|
const intervalMs = computeMinuteRampPollIntervalMs(elapsedMs, {
|
|
2980
3042
|
minIntervalMs: POLL_INTERVAL_MS2,
|
|
@@ -3009,7 +3071,8 @@ async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartT
|
|
|
3009
3071
|
method: "GET",
|
|
3010
3072
|
headers: {
|
|
3011
3073
|
"Content-Type": "application/json"
|
|
3012
|
-
}
|
|
3074
|
+
},
|
|
3075
|
+
signal
|
|
3013
3076
|
}
|
|
3014
3077
|
);
|
|
3015
3078
|
} catch (e) {
|
|
@@ -3071,7 +3134,7 @@ async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartT
|
|
|
3071
3134
|
requestId
|
|
3072
3135
|
});
|
|
3073
3136
|
}
|
|
3074
|
-
await
|
|
3137
|
+
await sleepUnlessAborted(intervalMs, signal, "decrypt");
|
|
3075
3138
|
attemptIndex += 1;
|
|
3076
3139
|
}
|
|
3077
3140
|
throw new CofheError({
|
|
@@ -3084,7 +3147,7 @@ async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartT
|
|
|
3084
3147
|
});
|
|
3085
3148
|
}
|
|
3086
3149
|
async function tnDecryptV2(params) {
|
|
3087
|
-
const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll } = params;
|
|
3150
|
+
const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll, signal } = params;
|
|
3088
3151
|
const timeoutMs = params.timeoutMs ?? DEFAULT_DECRYPT_TIMEOUT_MS;
|
|
3089
3152
|
const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
|
|
3090
3153
|
timeoutMs: retry404TimeoutMs,
|
|
@@ -3100,12 +3163,20 @@ async function tnDecryptV2(params) {
|
|
|
3100
3163
|
overallStartTime,
|
|
3101
3164
|
normalized404RetryTimeoutMs,
|
|
3102
3165
|
timeoutMs,
|
|
3103
|
-
onPoll
|
|
3166
|
+
onPoll,
|
|
3167
|
+
signal
|
|
3104
3168
|
);
|
|
3105
3169
|
if (submitResult.kind === "completed") {
|
|
3106
3170
|
return submitResult;
|
|
3107
3171
|
}
|
|
3108
|
-
return await pollDecryptStatusV2(
|
|
3172
|
+
return await pollDecryptStatusV2(
|
|
3173
|
+
thresholdNetworkUrl,
|
|
3174
|
+
submitResult.requestId,
|
|
3175
|
+
overallStartTime,
|
|
3176
|
+
timeoutMs,
|
|
3177
|
+
onPoll,
|
|
3178
|
+
signal
|
|
3179
|
+
);
|
|
3109
3180
|
}
|
|
3110
3181
|
|
|
3111
3182
|
// core/decrypt/decryptForTxBuilder.ts
|
|
@@ -3118,6 +3189,8 @@ var DecryptForTxBuilder = class extends BaseBuilder {
|
|
|
3118
3189
|
pollCallback;
|
|
3119
3190
|
retry404TimeoutMs = DEFAULT_404_RETRY_TIMEOUT_MS3;
|
|
3120
3191
|
timeoutMs;
|
|
3192
|
+
signal;
|
|
3193
|
+
limit;
|
|
3121
3194
|
constructor(params) {
|
|
3122
3195
|
super({
|
|
3123
3196
|
config: params.config,
|
|
@@ -3128,6 +3201,7 @@ var DecryptForTxBuilder = class extends BaseBuilder {
|
|
|
3128
3201
|
requireConnected: params.requireConnected
|
|
3129
3202
|
});
|
|
3130
3203
|
this.ctHash = params.ctHash;
|
|
3204
|
+
this.limit = params.limit ?? ((task) => task());
|
|
3131
3205
|
}
|
|
3132
3206
|
setChainId(chainId) {
|
|
3133
3207
|
this.chainId = chainId;
|
|
@@ -3165,6 +3239,10 @@ var DecryptForTxBuilder = class extends BaseBuilder {
|
|
|
3165
3239
|
this.timeoutMs = timeoutMs;
|
|
3166
3240
|
return this;
|
|
3167
3241
|
}
|
|
3242
|
+
setSignal(signal) {
|
|
3243
|
+
this.signal = signal;
|
|
3244
|
+
return this;
|
|
3245
|
+
}
|
|
3168
3246
|
withACP(acpOrACPHash) {
|
|
3169
3247
|
if (this.acpSelection === "with-acp") {
|
|
3170
3248
|
throw new CofheError({
|
|
@@ -3292,15 +3370,20 @@ var DecryptForTxBuilder = class extends BaseBuilder {
|
|
|
3292
3370
|
this.assertPublicClient();
|
|
3293
3371
|
const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
|
|
3294
3372
|
const wireAcp = acp ? ACPUtils.getPublic(acp, true) : null;
|
|
3295
|
-
const { decryptedValue, signature } = await
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3373
|
+
const { decryptedValue, signature } = await this.limit(
|
|
3374
|
+
() => tnDecryptV2({
|
|
3375
|
+
ctHash: this.ctHash,
|
|
3376
|
+
chainId: this.chainId,
|
|
3377
|
+
acp: wireAcp,
|
|
3378
|
+
thresholdNetworkUrl,
|
|
3379
|
+
retry404TimeoutMs: this.retry404TimeoutMs,
|
|
3380
|
+
timeoutMs: this.timeoutMs,
|
|
3381
|
+
onPoll: this.pollCallback,
|
|
3382
|
+
signal: this.signal
|
|
3383
|
+
}),
|
|
3384
|
+
"decrypt",
|
|
3385
|
+
this.signal
|
|
3386
|
+
);
|
|
3304
3387
|
return {
|
|
3305
3388
|
ctHash: this.ctHash,
|
|
3306
3389
|
decryptedValue,
|
|
@@ -3338,6 +3421,39 @@ var DecryptForTxBuilder = class extends BaseBuilder {
|
|
|
3338
3421
|
}
|
|
3339
3422
|
}
|
|
3340
3423
|
};
|
|
3424
|
+
|
|
3425
|
+
// core/decrypt/decryptLimiter.ts
|
|
3426
|
+
function createDecryptLimiter(max) {
|
|
3427
|
+
let active = 0;
|
|
3428
|
+
const waiting = [];
|
|
3429
|
+
return async (task, operationLabel, signal) => {
|
|
3430
|
+
if (signal?.aborted)
|
|
3431
|
+
throw decryptAbortedError(operationLabel, signal);
|
|
3432
|
+
if (active >= max) {
|
|
3433
|
+
await new Promise((resume, reject) => {
|
|
3434
|
+
const onAbort = () => {
|
|
3435
|
+
const index = waiting.indexOf(start);
|
|
3436
|
+
if (index >= 0)
|
|
3437
|
+
waiting.splice(index, 1);
|
|
3438
|
+
reject(decryptAbortedError(operationLabel, signal));
|
|
3439
|
+
};
|
|
3440
|
+
const start = () => {
|
|
3441
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3442
|
+
resume();
|
|
3443
|
+
};
|
|
3444
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
3445
|
+
waiting.push(start);
|
|
3446
|
+
});
|
|
3447
|
+
}
|
|
3448
|
+
active += 1;
|
|
3449
|
+
try {
|
|
3450
|
+
return await task();
|
|
3451
|
+
} finally {
|
|
3452
|
+
active -= 1;
|
|
3453
|
+
waiting.shift()?.();
|
|
3454
|
+
}
|
|
3455
|
+
};
|
|
3456
|
+
}
|
|
3341
3457
|
var decryptResultSignerAbi = parseAbi(["function decryptResultSigner() view returns (address)"]);
|
|
3342
3458
|
var UINT_TYPE_MASK2 = 0x7fn;
|
|
3343
3459
|
var TYPE_BYTE_OFFSET2 = 8n;
|
|
@@ -3483,11 +3599,13 @@ function createCofheClientBase(opts) {
|
|
|
3483
3599
|
requireConnected: _requireConnected
|
|
3484
3600
|
});
|
|
3485
3601
|
}
|
|
3602
|
+
const decryptLimiter = createDecryptLimiter(opts.config.decryptConcurrency);
|
|
3486
3603
|
function decryptForView(ctHash, utype) {
|
|
3487
3604
|
const state = connectStore.getState();
|
|
3488
3605
|
return new DecryptForViewBuilder({
|
|
3489
3606
|
ctHash,
|
|
3490
3607
|
utype,
|
|
3608
|
+
limit: decryptLimiter,
|
|
3491
3609
|
chainId: state.chainId,
|
|
3492
3610
|
account: state.account,
|
|
3493
3611
|
config: opts.config,
|
|
@@ -3500,6 +3618,7 @@ function createCofheClientBase(opts) {
|
|
|
3500
3618
|
const state = connectStore.getState();
|
|
3501
3619
|
return new DecryptForTxBuilder({
|
|
3502
3620
|
ctHash,
|
|
3621
|
+
limit: decryptLimiter,
|
|
3503
3622
|
chainId: state.chainId,
|
|
3504
3623
|
account: state.account,
|
|
3505
3624
|
config: opts.config,
|