@cofhe/sdk 1.1.0-alpha.0 → 1.1.0-alpha.1
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/decrypt/decryptForTxBuilder.ts +16 -0
- package/core/decrypt/decryptForViewBuilder.ts +14 -0
- package/core/decrypt/submitRetry.ts +16 -0
- package/core/decrypt/tnDecryptV2.ts +19 -9
- package/core/decrypt/tnSealOutputV2.ts +19 -9
- package/core/test/decryptBuilders.test.ts +51 -0
- package/dist/{chunk-2EO4JSR7.js → chunk-VE7DKXSF.js} +51 -20
- package/dist/{clientTypes-CnslFzxB.d.ts → clientTypes-B-qOZCx0.d.ts} +15 -0
- package/dist/{clientTypes-RXwLVWou.d.cts → clientTypes-D2djRDoH.d.cts} +15 -0
- package/dist/core.cjs +51 -20
- package/dist/core.d.cts +2 -2
- package/dist/core.d.ts +2 -2
- package/dist/core.js +1 -1
- package/dist/node.cjs +51 -20
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +1 -1
- package/dist/web.cjs +51 -20
- package/dist/web.d.cts +1 -1
- package/dist/web.d.ts +1 -1
- package/dist/web.js +1 -1
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ import { cofheMocksDecryptForTx } from './cofheMocksDecryptForTx';
|
|
|
11
11
|
import { getPublicClientChainID, sleep } from '../utils';
|
|
12
12
|
import { type DecryptPollCallbackFunction } from '../types';
|
|
13
13
|
import { tnDecryptV2 } from './tnDecryptV2';
|
|
14
|
+
import { assertDecryptTimeoutMs } from './submitRetry';
|
|
14
15
|
|
|
15
16
|
const DEFAULT_404_RETRY_TIMEOUT_MS = 10_000;
|
|
16
17
|
|
|
@@ -65,6 +66,7 @@ export class DecryptForTxBuilder extends BaseBuilder {
|
|
|
65
66
|
private acpSelection: DecryptForTxACPSelection = 'unset';
|
|
66
67
|
private pollCallback?: DecryptPollCallbackFunction;
|
|
67
68
|
private retry404TimeoutMs = DEFAULT_404_RETRY_TIMEOUT_MS;
|
|
69
|
+
private timeoutMs?: number;
|
|
68
70
|
|
|
69
71
|
constructor(params: DecryptForTxBuilderParams) {
|
|
70
72
|
super({
|
|
@@ -153,6 +155,19 @@ export class DecryptForTxBuilder extends BaseBuilder {
|
|
|
153
155
|
return this;
|
|
154
156
|
}
|
|
155
157
|
|
|
158
|
+
/**
|
|
159
|
+
* The whole budget of this decrypt in milliseconds, submit retries and status polling together
|
|
160
|
+
* (5 minutes by default). Past it, `execute()` rejects with `DecryptFailed`; a request still
|
|
161
|
+
* processing on the threshold network is not cancelled.
|
|
162
|
+
*/
|
|
163
|
+
setTimeout(this: DecryptForTxBuilderUnset, timeoutMs: number): DecryptForTxBuilderUnset;
|
|
164
|
+
setTimeout(this: DecryptForTxBuilderSelected, timeoutMs: number): DecryptForTxBuilderSelected;
|
|
165
|
+
setTimeout(timeoutMs: number): DecryptForTxBuilder {
|
|
166
|
+
assertDecryptTimeoutMs(timeoutMs, 'decryptForTx');
|
|
167
|
+
this.timeoutMs = timeoutMs;
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
|
|
156
171
|
/**
|
|
157
172
|
* Select "use acp" mode.
|
|
158
173
|
*
|
|
@@ -326,6 +341,7 @@ export class DecryptForTxBuilder extends BaseBuilder {
|
|
|
326
341
|
acp: wireAcp,
|
|
327
342
|
thresholdNetworkUrl,
|
|
328
343
|
retry404TimeoutMs: this.retry404TimeoutMs,
|
|
344
|
+
timeoutMs: this.timeoutMs,
|
|
329
345
|
onPoll: this.pollCallback,
|
|
330
346
|
});
|
|
331
347
|
|
|
@@ -13,6 +13,7 @@ import { cofheMocksDecryptForView } from './cofheMocksDecryptForView.js';
|
|
|
13
13
|
import { tnSealOutputV2 } from './tnSealOutputV2.js';
|
|
14
14
|
import { sleep } from '../utils.js';
|
|
15
15
|
import { type DecryptPollCallbackFunction } from '../types.js';
|
|
16
|
+
import { assertDecryptTimeoutMs } from './submitRetry.js';
|
|
16
17
|
|
|
17
18
|
const DEFAULT_404_RETRY_TIMEOUT_MS = 10_000;
|
|
18
19
|
|
|
@@ -51,6 +52,7 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
|
|
|
51
52
|
private acp?: ACP;
|
|
52
53
|
private pollCallback?: DecryptPollCallbackFunction;
|
|
53
54
|
private retry404TimeoutMs = DEFAULT_404_RETRY_TIMEOUT_MS;
|
|
55
|
+
private timeoutMs?: number;
|
|
54
56
|
|
|
55
57
|
constructor(params: DecryptForViewBuilderParams<U>) {
|
|
56
58
|
super({
|
|
@@ -134,6 +136,17 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
|
|
|
134
136
|
return this;
|
|
135
137
|
}
|
|
136
138
|
|
|
139
|
+
/**
|
|
140
|
+
* The whole budget of this decrypt in milliseconds, submit retries and status polling together
|
|
141
|
+
* (5 minutes by default). Past it, `execute()` rejects with `SealOutputFailed`; a request still
|
|
142
|
+
* processing on the threshold network is not cancelled.
|
|
143
|
+
*/
|
|
144
|
+
setTimeout(timeoutMs: number): DecryptForViewBuilder<U> {
|
|
145
|
+
assertDecryptTimeoutMs(timeoutMs, 'decryptForView');
|
|
146
|
+
this.timeoutMs = timeoutMs;
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
|
|
137
150
|
/**
|
|
138
151
|
* Select "use acp" mode (optional).
|
|
139
152
|
*
|
|
@@ -295,6 +308,7 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
|
|
|
295
308
|
acp: wireAcp,
|
|
296
309
|
thresholdNetworkUrl,
|
|
297
310
|
retry404TimeoutMs: this.retry404TimeoutMs,
|
|
311
|
+
timeoutMs: this.timeoutMs,
|
|
298
312
|
onPoll: this.pollCallback,
|
|
299
313
|
});
|
|
300
314
|
return ACPUtils.unseal(acp, sealed);
|
|
@@ -2,6 +2,22 @@ import { CofheError, CofheErrorCode } from '../error.js';
|
|
|
2
2
|
import { mapApiErrorCodeToCofheErrorCode, parseApiErrorResponseBody } from './apiError.js';
|
|
3
3
|
|
|
4
4
|
export const DEFAULT_404_RETRY_TIMEOUT_MS = 10_000;
|
|
5
|
+
/** The default budget of one decrypt, submit retries and status polling together. */
|
|
6
|
+
export const DEFAULT_DECRYPT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validates a builder's `setTimeout(timeoutMs)` argument: the overall budget must be a positive,
|
|
10
|
+
* finite number of milliseconds (0 would time out before the first attempt).
|
|
11
|
+
*/
|
|
12
|
+
export function assertDecryptTimeoutMs(timeoutMs: number, operationLabel: string): void {
|
|
13
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
14
|
+
throw new CofheError({
|
|
15
|
+
code: CofheErrorCode.InternalError,
|
|
16
|
+
message: `${operationLabel}: setTimeout(timeoutMs) expects a finite number greater than 0.`,
|
|
17
|
+
context: { timeoutMs },
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
5
21
|
|
|
6
22
|
export type SubmitRetryableStatus = 204 | 404;
|
|
7
23
|
|
|
@@ -6,12 +6,16 @@ import { type DecryptPollCallbackFunction } from '../types';
|
|
|
6
6
|
import { normalizeTnSignature, parseDecryptedBytesToBigInt } from './tnDecryptUtils';
|
|
7
7
|
import { computeMinuteRampPollIntervalMs } from './polling.js';
|
|
8
8
|
import { mapApiErrorCodeToCofheErrorCode, parseApiErrorResponseBody } from './apiError.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_DECRYPT_TIMEOUT_MS,
|
|
11
|
+
classifySubmitResponse,
|
|
12
|
+
normalize404RetryTimeoutMs,
|
|
13
|
+
throwIfSubmitRetryTimedOut,
|
|
14
|
+
} from './submitRetry.js';
|
|
10
15
|
|
|
11
16
|
// Polling configuration
|
|
12
17
|
const POLL_INTERVAL_MS = 1000; // 1 second
|
|
13
18
|
const POLL_MAX_INTERVAL_MS = 10_000; // 10 seconds
|
|
14
|
-
const DECRYPT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes total across submit + poll
|
|
15
19
|
const SUBMIT_RETRY_INTERVAL_MS = 1000; // 1 second
|
|
16
20
|
|
|
17
21
|
type DecryptSubmitResponseV2 = {
|
|
@@ -182,6 +186,7 @@ async function submitDecryptRequestV2(
|
|
|
182
186
|
acp: ACPPublic | null,
|
|
183
187
|
overallStartTime: number,
|
|
184
188
|
retry404TimeoutMs: number,
|
|
189
|
+
timeoutMs: number,
|
|
185
190
|
onPoll?: DecryptPollCallbackFunction
|
|
186
191
|
): Promise<DecryptSubmitResultV2> {
|
|
187
192
|
const body: {
|
|
@@ -306,7 +311,7 @@ async function submitDecryptRequestV2(
|
|
|
306
311
|
status: responseClassification.status,
|
|
307
312
|
elapsedMs,
|
|
308
313
|
retry404TimeoutMs,
|
|
309
|
-
overallTimeoutMs:
|
|
314
|
+
overallTimeoutMs: timeoutMs,
|
|
310
315
|
thresholdNetworkUrl,
|
|
311
316
|
body,
|
|
312
317
|
attemptIndex,
|
|
@@ -319,7 +324,7 @@ async function submitDecryptRequestV2(
|
|
|
319
324
|
attemptIndex,
|
|
320
325
|
elapsedMs,
|
|
321
326
|
intervalMs: SUBMIT_RETRY_INTERVAL_MS,
|
|
322
|
-
timeoutMs
|
|
327
|
+
timeoutMs,
|
|
323
328
|
});
|
|
324
329
|
|
|
325
330
|
await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS));
|
|
@@ -331,6 +336,7 @@ async function pollDecryptStatusV2(
|
|
|
331
336
|
thresholdNetworkUrl: string,
|
|
332
337
|
requestId: string,
|
|
333
338
|
overallStartTime: number,
|
|
339
|
+
timeoutMs: number,
|
|
334
340
|
onPoll?: DecryptPollCallbackFunction
|
|
335
341
|
): Promise<{ decryptedValue: bigint; signature: `0x${string}` }> {
|
|
336
342
|
let attemptIndex = 0;
|
|
@@ -348,18 +354,18 @@ async function pollDecryptStatusV2(
|
|
|
348
354
|
attemptIndex,
|
|
349
355
|
elapsedMs,
|
|
350
356
|
intervalMs,
|
|
351
|
-
timeoutMs
|
|
357
|
+
timeoutMs,
|
|
352
358
|
});
|
|
353
359
|
|
|
354
|
-
if (elapsedMs >
|
|
360
|
+
if (elapsedMs > timeoutMs) {
|
|
355
361
|
throw new CofheError({
|
|
356
362
|
code: CofheErrorCode.DecryptFailed,
|
|
357
|
-
message: `decrypt polling timed out after ${
|
|
363
|
+
message: `decrypt polling timed out after ${timeoutMs}ms`,
|
|
358
364
|
hint: 'The request may still be processing. Try again later.',
|
|
359
365
|
context: {
|
|
360
366
|
thresholdNetworkUrl,
|
|
361
367
|
requestId,
|
|
362
|
-
timeoutMs
|
|
368
|
+
timeoutMs,
|
|
363
369
|
},
|
|
364
370
|
});
|
|
365
371
|
}
|
|
@@ -462,9 +468,12 @@ export async function tnDecryptV2(params: {
|
|
|
462
468
|
acp: ACPPublic | null;
|
|
463
469
|
thresholdNetworkUrl: string;
|
|
464
470
|
retry404TimeoutMs?: number;
|
|
471
|
+
/** The whole budget, submit retries and status polling together; `DEFAULT_DECRYPT_TIMEOUT_MS` by default. */
|
|
472
|
+
timeoutMs?: number;
|
|
465
473
|
onPoll?: DecryptPollCallbackFunction;
|
|
466
474
|
}): Promise<{ decryptedValue: bigint; signature: `0x${string}` }> {
|
|
467
475
|
const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll } = params;
|
|
476
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_DECRYPT_TIMEOUT_MS;
|
|
468
477
|
const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
|
|
469
478
|
timeoutMs: retry404TimeoutMs,
|
|
470
479
|
operationLabel: 'decrypt',
|
|
@@ -478,6 +487,7 @@ export async function tnDecryptV2(params: {
|
|
|
478
487
|
acp,
|
|
479
488
|
overallStartTime,
|
|
480
489
|
normalized404RetryTimeoutMs,
|
|
490
|
+
timeoutMs,
|
|
481
491
|
onPoll
|
|
482
492
|
);
|
|
483
493
|
|
|
@@ -485,5 +495,5 @@ export async function tnDecryptV2(params: {
|
|
|
485
495
|
return submitResult;
|
|
486
496
|
}
|
|
487
497
|
|
|
488
|
-
return await pollDecryptStatusV2(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
|
|
498
|
+
return await pollDecryptStatusV2(thresholdNetworkUrl, submitResult.requestId, overallStartTime, timeoutMs, onPoll);
|
|
489
499
|
}
|
|
@@ -5,13 +5,17 @@ import { CofheError, CofheErrorCode } from '../error.js';
|
|
|
5
5
|
import { type DecryptPollCallbackFunction } from '../types.js';
|
|
6
6
|
import { computeMinuteRampPollIntervalMs } from './polling.js';
|
|
7
7
|
import { mapApiErrorCodeToCofheErrorCode, parseApiErrorResponseBody } from './apiError.js';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_DECRYPT_TIMEOUT_MS,
|
|
10
|
+
classifySubmitResponse,
|
|
11
|
+
normalize404RetryTimeoutMs,
|
|
12
|
+
throwIfSubmitRetryTimedOut,
|
|
13
|
+
} from './submitRetry.js';
|
|
9
14
|
import {} from './tnDecryptUtils.js';
|
|
10
15
|
|
|
11
16
|
// Polling configuration
|
|
12
17
|
const POLL_INTERVAL_MS = 1000; // 1 second
|
|
13
18
|
const POLL_MAX_INTERVAL_MS = 10_000; // 10 seconds
|
|
14
|
-
const SEAL_OUTPUT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes total across submit + poll
|
|
15
19
|
const SUBMIT_RETRY_INTERVAL_MS = 1000; // 1 second
|
|
16
20
|
|
|
17
21
|
// V2 API response types
|
|
@@ -141,6 +145,7 @@ async function submitSealOutputRequest(
|
|
|
141
145
|
acp: ACPPublic,
|
|
142
146
|
overallStartTime: number,
|
|
143
147
|
retry404TimeoutMs: number,
|
|
148
|
+
timeoutMs: number,
|
|
144
149
|
onPoll?: DecryptPollCallbackFunction
|
|
145
150
|
): Promise<SealOutputSubmitResult> {
|
|
146
151
|
const body = {
|
|
@@ -254,7 +259,7 @@ async function submitSealOutputRequest(
|
|
|
254
259
|
status: responseClassification.status,
|
|
255
260
|
elapsedMs,
|
|
256
261
|
retry404TimeoutMs,
|
|
257
|
-
overallTimeoutMs:
|
|
262
|
+
overallTimeoutMs: timeoutMs,
|
|
258
263
|
thresholdNetworkUrl,
|
|
259
264
|
body,
|
|
260
265
|
attemptIndex,
|
|
@@ -267,7 +272,7 @@ async function submitSealOutputRequest(
|
|
|
267
272
|
attemptIndex,
|
|
268
273
|
elapsedMs,
|
|
269
274
|
intervalMs: SUBMIT_RETRY_INTERVAL_MS,
|
|
270
|
-
timeoutMs
|
|
275
|
+
timeoutMs,
|
|
271
276
|
});
|
|
272
277
|
|
|
273
278
|
await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS));
|
|
@@ -282,6 +287,7 @@ async function pollSealOutputStatus(
|
|
|
282
287
|
thresholdNetworkUrl: string,
|
|
283
288
|
requestId: string,
|
|
284
289
|
overallStartTime: number,
|
|
290
|
+
timeoutMs: number,
|
|
285
291
|
onPoll?: DecryptPollCallbackFunction
|
|
286
292
|
): Promise<EthEncryptedData> {
|
|
287
293
|
let attemptIndex = 0;
|
|
@@ -299,19 +305,19 @@ async function pollSealOutputStatus(
|
|
|
299
305
|
attemptIndex,
|
|
300
306
|
elapsedMs,
|
|
301
307
|
intervalMs,
|
|
302
|
-
timeoutMs
|
|
308
|
+
timeoutMs,
|
|
303
309
|
});
|
|
304
310
|
|
|
305
311
|
// Check timeout
|
|
306
|
-
if (elapsedMs >
|
|
312
|
+
if (elapsedMs > timeoutMs) {
|
|
307
313
|
throw new CofheError({
|
|
308
314
|
code: CofheErrorCode.SealOutputFailed,
|
|
309
|
-
message: `sealOutput polling timed out after ${
|
|
315
|
+
message: `sealOutput polling timed out after ${timeoutMs}ms`,
|
|
310
316
|
hint: 'The request may still be processing. Try again later.',
|
|
311
317
|
context: {
|
|
312
318
|
thresholdNetworkUrl,
|
|
313
319
|
requestId,
|
|
314
|
-
timeoutMs
|
|
320
|
+
timeoutMs,
|
|
315
321
|
},
|
|
316
322
|
});
|
|
317
323
|
}
|
|
@@ -416,9 +422,12 @@ export async function tnSealOutputV2(params: {
|
|
|
416
422
|
acp: ACPPublic;
|
|
417
423
|
thresholdNetworkUrl: string;
|
|
418
424
|
retry404TimeoutMs?: number;
|
|
425
|
+
/** The whole budget, submit retries and status polling together; `DEFAULT_DECRYPT_TIMEOUT_MS` by default. */
|
|
426
|
+
timeoutMs?: number;
|
|
419
427
|
onPoll?: DecryptPollCallbackFunction;
|
|
420
428
|
}): Promise<EthEncryptedData> {
|
|
421
429
|
const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll } = params;
|
|
430
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_DECRYPT_TIMEOUT_MS;
|
|
422
431
|
const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
|
|
423
432
|
timeoutMs: retry404TimeoutMs,
|
|
424
433
|
operationLabel: 'sealOutput',
|
|
@@ -434,6 +443,7 @@ export async function tnSealOutputV2(params: {
|
|
|
434
443
|
acp,
|
|
435
444
|
overallStartTime,
|
|
436
445
|
normalized404RetryTimeoutMs,
|
|
446
|
+
timeoutMs,
|
|
437
447
|
onPoll
|
|
438
448
|
);
|
|
439
449
|
|
|
@@ -442,5 +452,5 @@ export async function tnSealOutputV2(params: {
|
|
|
442
452
|
}
|
|
443
453
|
|
|
444
454
|
// Step 2: Poll for status until completed
|
|
445
|
-
return await pollSealOutputStatus(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
|
|
455
|
+
return await pollSealOutputStatus(thresholdNetworkUrl, submitResult.requestId, overallStartTime, timeoutMs, onPoll);
|
|
446
456
|
}
|
|
@@ -224,6 +224,21 @@ describe('DecryptForTxBuilder', () => {
|
|
|
224
224
|
|
|
225
225
|
expect(() => builder.set404RetryTimeout(-1)).toThrow('set404RetryTimeout(timeoutMs) expects');
|
|
226
226
|
});
|
|
227
|
+
|
|
228
|
+
it('should allow configuring the overall timeout', () => {
|
|
229
|
+
const builder = createTxBuilder();
|
|
230
|
+
const result = builder.setTimeout(20_000);
|
|
231
|
+
|
|
232
|
+
expect(result).toBe(builder);
|
|
233
|
+
expect((builder as any).timeoutMs).toBe(20_000);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should throw for an overall timeout that is not a positive number', () => {
|
|
237
|
+
const builder = createTxBuilder();
|
|
238
|
+
|
|
239
|
+
expect(() => builder.setTimeout(0)).toThrow('setTimeout(timeoutMs) expects');
|
|
240
|
+
expect(() => builder.setTimeout(Number.NaN)).toThrow('setTimeout(timeoutMs) expects');
|
|
241
|
+
});
|
|
227
242
|
});
|
|
228
243
|
|
|
229
244
|
// --- execute error paths ---
|
|
@@ -367,6 +382,21 @@ describe('DecryptForViewBuilder', () => {
|
|
|
367
382
|
|
|
368
383
|
expect(() => builder.set404RetryTimeout(-1)).toThrow('set404RetryTimeout(timeoutMs) expects');
|
|
369
384
|
});
|
|
385
|
+
|
|
386
|
+
it('should allow configuring the overall timeout', () => {
|
|
387
|
+
const builder = createViewBuilder(FheTypes.Uint32);
|
|
388
|
+
const result = builder.setTimeout(20_000);
|
|
389
|
+
|
|
390
|
+
expect(result).toBe(builder);
|
|
391
|
+
expect((builder as any).timeoutMs).toBe(20_000);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('should throw for an overall timeout that is not a positive number', () => {
|
|
395
|
+
const builder = createViewBuilder(FheTypes.Uint32);
|
|
396
|
+
|
|
397
|
+
expect(() => builder.setTimeout(0)).toThrow('setTimeout(timeoutMs) expects');
|
|
398
|
+
expect(() => builder.setTimeout(-5)).toThrow('setTimeout(timeoutMs) expects');
|
|
399
|
+
});
|
|
370
400
|
});
|
|
371
401
|
|
|
372
402
|
// --- execute error paths ---
|
|
@@ -503,6 +533,16 @@ describe('execute – expired acp (regression, issue #272)', () => {
|
|
|
503
533
|
expect(result.decryptedValue).toBe(7n);
|
|
504
534
|
expect(tnDecryptV2).toHaveBeenCalledOnce();
|
|
505
535
|
});
|
|
536
|
+
|
|
537
|
+
it('passes the overall timeout to the threshold network call', async () => {
|
|
538
|
+
vi.mocked(tnDecryptV2).mockResolvedValue({ decryptedValue: 7n, signature: '0x1234' });
|
|
539
|
+
const acp = createSignedSelfACP(now() + 3600);
|
|
540
|
+
seedACP(acp);
|
|
541
|
+
|
|
542
|
+
await selectTxACP('active acp', acp).setTimeout(20_000).execute();
|
|
543
|
+
|
|
544
|
+
expect(tnDecryptV2).toHaveBeenCalledWith(expect.objectContaining({ timeoutMs: 20_000 }));
|
|
545
|
+
});
|
|
506
546
|
});
|
|
507
547
|
|
|
508
548
|
describe('decryptForView', () => {
|
|
@@ -522,5 +562,16 @@ describe('execute – expired acp (regression, issue #272)', () => {
|
|
|
522
562
|
await expect(selectViewACP('active acp', acp).execute()).rejects.toThrow('stubbed threshold network');
|
|
523
563
|
expect(tnSealOutputV2).toHaveBeenCalledOnce();
|
|
524
564
|
});
|
|
565
|
+
|
|
566
|
+
it('passes the overall timeout to the threshold network call', async () => {
|
|
567
|
+
vi.mocked(tnSealOutputV2).mockRejectedValue(new Error('stubbed threshold network'));
|
|
568
|
+
const acp = createSignedSelfACP(now() + 3600);
|
|
569
|
+
seedACP(acp);
|
|
570
|
+
|
|
571
|
+
await expect(selectViewACP('active acp', acp).setTimeout(20_000).execute()).rejects.toThrow(
|
|
572
|
+
'stubbed threshold network'
|
|
573
|
+
);
|
|
574
|
+
expect(tnSealOutputV2).toHaveBeenCalledWith(expect.objectContaining({ timeoutMs: 20_000 }));
|
|
575
|
+
});
|
|
525
576
|
});
|
|
526
577
|
});
|
|
@@ -1929,6 +1929,16 @@ function mapApiErrorCodeToCofheErrorCode(apiErrorCode, fallback) {
|
|
|
1929
1929
|
|
|
1930
1930
|
// core/decrypt/submitRetry.ts
|
|
1931
1931
|
var DEFAULT_404_RETRY_TIMEOUT_MS = 1e4;
|
|
1932
|
+
var DEFAULT_DECRYPT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1933
|
+
function assertDecryptTimeoutMs(timeoutMs, operationLabel) {
|
|
1934
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
1935
|
+
throw new CofheError({
|
|
1936
|
+
code: "INTERNAL_ERROR" /* InternalError */,
|
|
1937
|
+
message: `${operationLabel}: setTimeout(timeoutMs) expects a finite number greater than 0.`,
|
|
1938
|
+
context: { timeoutMs }
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1932
1942
|
function normalize404RetryTimeoutMs(params) {
|
|
1933
1943
|
const { timeoutMs, operationLabel, errorCode } = params;
|
|
1934
1944
|
if (timeoutMs === void 0)
|
|
@@ -2067,7 +2077,6 @@ function parseDecryptedBytesToBigInt(decrypted) {
|
|
|
2067
2077
|
// core/decrypt/tnSealOutputV2.ts
|
|
2068
2078
|
var POLL_INTERVAL_MS = 1e3;
|
|
2069
2079
|
var POLL_MAX_INTERVAL_MS = 1e4;
|
|
2070
|
-
var SEAL_OUTPUT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2071
2080
|
var SUBMIT_RETRY_INTERVAL_MS = 1e3;
|
|
2072
2081
|
function numberArrayToUint8Array(arr) {
|
|
2073
2082
|
return new Uint8Array(arr);
|
|
@@ -2125,7 +2134,7 @@ function parseCompletedSealOutputResponse(params) {
|
|
|
2125
2134
|
}
|
|
2126
2135
|
return convertSealedData(sealed);
|
|
2127
2136
|
}
|
|
2128
|
-
async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, acp, overallStartTime, retry404TimeoutMs, onPoll) {
|
|
2137
|
+
async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, acp, overallStartTime, retry404TimeoutMs, timeoutMs, onPoll) {
|
|
2129
2138
|
const body = {
|
|
2130
2139
|
ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
|
|
2131
2140
|
host_chain_id: chainId,
|
|
@@ -2228,7 +2237,7 @@ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, acp
|
|
|
2228
2237
|
status: responseClassification.status,
|
|
2229
2238
|
elapsedMs,
|
|
2230
2239
|
retry404TimeoutMs,
|
|
2231
|
-
overallTimeoutMs:
|
|
2240
|
+
overallTimeoutMs: timeoutMs,
|
|
2232
2241
|
thresholdNetworkUrl,
|
|
2233
2242
|
body,
|
|
2234
2243
|
attemptIndex,
|
|
@@ -2240,13 +2249,13 @@ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, acp
|
|
|
2240
2249
|
attemptIndex,
|
|
2241
2250
|
elapsedMs,
|
|
2242
2251
|
intervalMs: SUBMIT_RETRY_INTERVAL_MS,
|
|
2243
|
-
timeoutMs
|
|
2252
|
+
timeoutMs
|
|
2244
2253
|
});
|
|
2245
2254
|
await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS));
|
|
2246
2255
|
attemptIndex += 1;
|
|
2247
2256
|
}
|
|
2248
2257
|
}
|
|
2249
|
-
async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStartTime, onPoll) {
|
|
2258
|
+
async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStartTime, timeoutMs, onPoll) {
|
|
2250
2259
|
let attemptIndex = 0;
|
|
2251
2260
|
while (true) {
|
|
2252
2261
|
const elapsedMs = Date.now() - overallStartTime;
|
|
@@ -2260,17 +2269,17 @@ async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStart
|
|
|
2260
2269
|
attemptIndex,
|
|
2261
2270
|
elapsedMs,
|
|
2262
2271
|
intervalMs,
|
|
2263
|
-
timeoutMs
|
|
2272
|
+
timeoutMs
|
|
2264
2273
|
});
|
|
2265
|
-
if (elapsedMs >
|
|
2274
|
+
if (elapsedMs > timeoutMs) {
|
|
2266
2275
|
throw new CofheError({
|
|
2267
2276
|
code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
|
|
2268
|
-
message: `sealOutput polling timed out after ${
|
|
2277
|
+
message: `sealOutput polling timed out after ${timeoutMs}ms`,
|
|
2269
2278
|
hint: "The request may still be processing. Try again later.",
|
|
2270
2279
|
context: {
|
|
2271
2280
|
thresholdNetworkUrl,
|
|
2272
2281
|
requestId,
|
|
2273
|
-
timeoutMs
|
|
2282
|
+
timeoutMs
|
|
2274
2283
|
}
|
|
2275
2284
|
});
|
|
2276
2285
|
}
|
|
@@ -2358,6 +2367,7 @@ async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStart
|
|
|
2358
2367
|
}
|
|
2359
2368
|
async function tnSealOutputV2(params) {
|
|
2360
2369
|
const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll } = params;
|
|
2370
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_DECRYPT_TIMEOUT_MS;
|
|
2361
2371
|
const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
|
|
2362
2372
|
timeoutMs: retry404TimeoutMs,
|
|
2363
2373
|
operationLabel: "sealOutput",
|
|
@@ -2371,12 +2381,13 @@ async function tnSealOutputV2(params) {
|
|
|
2371
2381
|
acp,
|
|
2372
2382
|
overallStartTime,
|
|
2373
2383
|
normalized404RetryTimeoutMs,
|
|
2384
|
+
timeoutMs,
|
|
2374
2385
|
onPoll
|
|
2375
2386
|
);
|
|
2376
2387
|
if (submitResult.kind === "completed") {
|
|
2377
2388
|
return submitResult.sealed;
|
|
2378
2389
|
}
|
|
2379
|
-
return await pollSealOutputStatus(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
|
|
2390
|
+
return await pollSealOutputStatus(thresholdNetworkUrl, submitResult.requestId, overallStartTime, timeoutMs, onPoll);
|
|
2380
2391
|
}
|
|
2381
2392
|
|
|
2382
2393
|
// core/decrypt/decryptForViewBuilder.ts
|
|
@@ -2388,6 +2399,7 @@ var DecryptForViewBuilder = class extends BaseBuilder {
|
|
|
2388
2399
|
acp;
|
|
2389
2400
|
pollCallback;
|
|
2390
2401
|
retry404TimeoutMs = DEFAULT_404_RETRY_TIMEOUT_MS2;
|
|
2402
|
+
timeoutMs;
|
|
2391
2403
|
constructor(params) {
|
|
2392
2404
|
super({
|
|
2393
2405
|
config: params.config,
|
|
@@ -2461,6 +2473,16 @@ var DecryptForViewBuilder = class extends BaseBuilder {
|
|
|
2461
2473
|
this.retry404TimeoutMs = timeoutMs;
|
|
2462
2474
|
return this;
|
|
2463
2475
|
}
|
|
2476
|
+
/**
|
|
2477
|
+
* The whole budget of this decrypt in milliseconds, submit retries and status polling together
|
|
2478
|
+
* (5 minutes by default). Past it, `execute()` rejects with `SealOutputFailed`; a request still
|
|
2479
|
+
* processing on the threshold network is not cancelled.
|
|
2480
|
+
*/
|
|
2481
|
+
setTimeout(timeoutMs) {
|
|
2482
|
+
assertDecryptTimeoutMs(timeoutMs, "decryptForView");
|
|
2483
|
+
this.timeoutMs = timeoutMs;
|
|
2484
|
+
return this;
|
|
2485
|
+
}
|
|
2464
2486
|
withACP(acpOrACPHash) {
|
|
2465
2487
|
if (typeof acpOrACPHash === "string") {
|
|
2466
2488
|
this.acpHash = acpOrACPHash;
|
|
@@ -2590,6 +2612,7 @@ var DecryptForViewBuilder = class extends BaseBuilder {
|
|
|
2590
2612
|
acp: wireAcp,
|
|
2591
2613
|
thresholdNetworkUrl,
|
|
2592
2614
|
retry404TimeoutMs: this.retry404TimeoutMs,
|
|
2615
|
+
timeoutMs: this.timeoutMs,
|
|
2593
2616
|
onPoll: this.pollCallback
|
|
2594
2617
|
});
|
|
2595
2618
|
return ACPUtils.unseal(acp, sealed);
|
|
@@ -2692,7 +2715,6 @@ async function cofheMocksDecryptForTx(ctHash, utype, acp, publicClient) {
|
|
|
2692
2715
|
// core/decrypt/tnDecryptV2.ts
|
|
2693
2716
|
var POLL_INTERVAL_MS2 = 1e3;
|
|
2694
2717
|
var POLL_MAX_INTERVAL_MS2 = 1e4;
|
|
2695
|
-
var DECRYPT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2696
2718
|
var SUBMIT_RETRY_INTERVAL_MS2 = 1e3;
|
|
2697
2719
|
function assertDecryptSubmitResponseV2(value) {
|
|
2698
2720
|
if (value == null || typeof value !== "object") {
|
|
@@ -2809,7 +2831,7 @@ function assertDecryptStatusResponseV2(value) {
|
|
|
2809
2831
|
}
|
|
2810
2832
|
return value;
|
|
2811
2833
|
}
|
|
2812
|
-
async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, acp, overallStartTime, retry404TimeoutMs, onPoll) {
|
|
2834
|
+
async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, acp, overallStartTime, retry404TimeoutMs, timeoutMs, onPoll) {
|
|
2813
2835
|
const body = {
|
|
2814
2836
|
ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
|
|
2815
2837
|
host_chain_id: chainId
|
|
@@ -2916,7 +2938,7 @@ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, acp,
|
|
|
2916
2938
|
status: responseClassification.status,
|
|
2917
2939
|
elapsedMs,
|
|
2918
2940
|
retry404TimeoutMs,
|
|
2919
|
-
overallTimeoutMs:
|
|
2941
|
+
overallTimeoutMs: timeoutMs,
|
|
2920
2942
|
thresholdNetworkUrl,
|
|
2921
2943
|
body,
|
|
2922
2944
|
attemptIndex,
|
|
@@ -2928,13 +2950,13 @@ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, acp,
|
|
|
2928
2950
|
attemptIndex,
|
|
2929
2951
|
elapsedMs,
|
|
2930
2952
|
intervalMs: SUBMIT_RETRY_INTERVAL_MS2,
|
|
2931
|
-
timeoutMs
|
|
2953
|
+
timeoutMs
|
|
2932
2954
|
});
|
|
2933
2955
|
await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS2));
|
|
2934
2956
|
attemptIndex += 1;
|
|
2935
2957
|
}
|
|
2936
2958
|
}
|
|
2937
|
-
async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartTime, onPoll) {
|
|
2959
|
+
async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartTime, timeoutMs, onPoll) {
|
|
2938
2960
|
let attemptIndex = 0;
|
|
2939
2961
|
while (true) {
|
|
2940
2962
|
const elapsedMs = Date.now() - overallStartTime;
|
|
@@ -2948,17 +2970,17 @@ async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartT
|
|
|
2948
2970
|
attemptIndex,
|
|
2949
2971
|
elapsedMs,
|
|
2950
2972
|
intervalMs,
|
|
2951
|
-
timeoutMs
|
|
2973
|
+
timeoutMs
|
|
2952
2974
|
});
|
|
2953
|
-
if (elapsedMs >
|
|
2975
|
+
if (elapsedMs > timeoutMs) {
|
|
2954
2976
|
throw new CofheError({
|
|
2955
2977
|
code: "DECRYPT_FAILED" /* DecryptFailed */,
|
|
2956
|
-
message: `decrypt polling timed out after ${
|
|
2978
|
+
message: `decrypt polling timed out after ${timeoutMs}ms`,
|
|
2957
2979
|
hint: "The request may still be processing. Try again later.",
|
|
2958
2980
|
context: {
|
|
2959
2981
|
thresholdNetworkUrl,
|
|
2960
2982
|
requestId,
|
|
2961
|
-
timeoutMs
|
|
2983
|
+
timeoutMs
|
|
2962
2984
|
}
|
|
2963
2985
|
});
|
|
2964
2986
|
}
|
|
@@ -3047,6 +3069,7 @@ async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartT
|
|
|
3047
3069
|
}
|
|
3048
3070
|
async function tnDecryptV2(params) {
|
|
3049
3071
|
const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll } = params;
|
|
3072
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_DECRYPT_TIMEOUT_MS;
|
|
3050
3073
|
const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
|
|
3051
3074
|
timeoutMs: retry404TimeoutMs,
|
|
3052
3075
|
operationLabel: "decrypt",
|
|
@@ -3060,12 +3083,13 @@ async function tnDecryptV2(params) {
|
|
|
3060
3083
|
acp,
|
|
3061
3084
|
overallStartTime,
|
|
3062
3085
|
normalized404RetryTimeoutMs,
|
|
3086
|
+
timeoutMs,
|
|
3063
3087
|
onPoll
|
|
3064
3088
|
);
|
|
3065
3089
|
if (submitResult.kind === "completed") {
|
|
3066
3090
|
return submitResult;
|
|
3067
3091
|
}
|
|
3068
|
-
return await pollDecryptStatusV2(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
|
|
3092
|
+
return await pollDecryptStatusV2(thresholdNetworkUrl, submitResult.requestId, overallStartTime, timeoutMs, onPoll);
|
|
3069
3093
|
}
|
|
3070
3094
|
|
|
3071
3095
|
// core/decrypt/decryptForTxBuilder.ts
|
|
@@ -3077,6 +3101,7 @@ var DecryptForTxBuilder = class extends BaseBuilder {
|
|
|
3077
3101
|
acpSelection = "unset";
|
|
3078
3102
|
pollCallback;
|
|
3079
3103
|
retry404TimeoutMs = DEFAULT_404_RETRY_TIMEOUT_MS3;
|
|
3104
|
+
timeoutMs;
|
|
3080
3105
|
constructor(params) {
|
|
3081
3106
|
super({
|
|
3082
3107
|
config: params.config,
|
|
@@ -3119,6 +3144,11 @@ var DecryptForTxBuilder = class extends BaseBuilder {
|
|
|
3119
3144
|
this.retry404TimeoutMs = timeoutMs;
|
|
3120
3145
|
return this;
|
|
3121
3146
|
}
|
|
3147
|
+
setTimeout(timeoutMs) {
|
|
3148
|
+
assertDecryptTimeoutMs(timeoutMs, "decryptForTx");
|
|
3149
|
+
this.timeoutMs = timeoutMs;
|
|
3150
|
+
return this;
|
|
3151
|
+
}
|
|
3122
3152
|
withACP(acpOrACPHash) {
|
|
3123
3153
|
if (this.acpSelection === "with-acp") {
|
|
3124
3154
|
throw new CofheError({
|
|
@@ -3252,6 +3282,7 @@ var DecryptForTxBuilder = class extends BaseBuilder {
|
|
|
3252
3282
|
acp: wireAcp,
|
|
3253
3283
|
thresholdNetworkUrl,
|
|
3254
3284
|
retry404TimeoutMs: this.retry404TimeoutMs,
|
|
3285
|
+
timeoutMs: this.timeoutMs,
|
|
3255
3286
|
onPoll: this.pollCallback
|
|
3256
3287
|
});
|
|
3257
3288
|
return {
|
|
@@ -465,6 +465,7 @@ declare class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
|
|
|
465
465
|
private acp?;
|
|
466
466
|
private pollCallback?;
|
|
467
467
|
private retry404TimeoutMs;
|
|
468
|
+
private timeoutMs?;
|
|
468
469
|
constructor(params: DecryptForViewBuilderParams<U>);
|
|
469
470
|
/**
|
|
470
471
|
* @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct acp.
|
|
@@ -500,6 +501,12 @@ declare class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
|
|
|
500
501
|
getAccount(): string | undefined;
|
|
501
502
|
onPoll(callback: DecryptPollCallbackFunction): DecryptForViewBuilder<U>;
|
|
502
503
|
set404RetryTimeout(timeoutMs: number): DecryptForViewBuilder<U>;
|
|
504
|
+
/**
|
|
505
|
+
* The whole budget of this decrypt in milliseconds, submit retries and status polling together
|
|
506
|
+
* (5 minutes by default). Past it, `execute()` rejects with `SealOutputFailed`; a request still
|
|
507
|
+
* processing on the threshold network is not cancelled.
|
|
508
|
+
*/
|
|
509
|
+
setTimeout(timeoutMs: number): DecryptForViewBuilder<U>;
|
|
503
510
|
/**
|
|
504
511
|
* Select "use acp" mode (optional).
|
|
505
512
|
*
|
|
@@ -603,6 +610,7 @@ declare class DecryptForTxBuilder extends BaseBuilder {
|
|
|
603
610
|
private acpSelection;
|
|
604
611
|
private pollCallback?;
|
|
605
612
|
private retry404TimeoutMs;
|
|
613
|
+
private timeoutMs?;
|
|
606
614
|
constructor(params: DecryptForTxBuilderParams);
|
|
607
615
|
/**
|
|
608
616
|
* @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct acp.
|
|
@@ -642,6 +650,13 @@ declare class DecryptForTxBuilder extends BaseBuilder {
|
|
|
642
650
|
onPoll(this: DecryptForTxBuilderSelected, callback: DecryptPollCallbackFunction): DecryptForTxBuilderSelected;
|
|
643
651
|
set404RetryTimeout(this: DecryptForTxBuilderUnset, timeoutMs: number): DecryptForTxBuilderUnset;
|
|
644
652
|
set404RetryTimeout(this: DecryptForTxBuilderSelected, timeoutMs: number): DecryptForTxBuilderSelected;
|
|
653
|
+
/**
|
|
654
|
+
* The whole budget of this decrypt in milliseconds, submit retries and status polling together
|
|
655
|
+
* (5 minutes by default). Past it, `execute()` rejects with `DecryptFailed`; a request still
|
|
656
|
+
* processing on the threshold network is not cancelled.
|
|
657
|
+
*/
|
|
658
|
+
setTimeout(this: DecryptForTxBuilderUnset, timeoutMs: number): DecryptForTxBuilderUnset;
|
|
659
|
+
setTimeout(this: DecryptForTxBuilderSelected, timeoutMs: number): DecryptForTxBuilderSelected;
|
|
645
660
|
/**
|
|
646
661
|
* Select "use acp" mode.
|
|
647
662
|
*
|