@aztec/prover-client 0.42.0 → 0.44.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/dest/config.d.ts.map +1 -1
- package/dest/config.js +12 -8
- package/dest/mocks/test_context.d.ts.map +1 -1
- package/dest/mocks/test_context.js +8 -5
- package/dest/orchestrator/block-building-helpers.d.ts +5 -5
- package/dest/orchestrator/orchestrator.d.ts +3 -1
- package/dest/orchestrator/orchestrator.d.ts.map +1 -1
- package/dest/orchestrator/orchestrator.js +599 -518
- package/dest/prover-agent/memory-proving-queue.d.ts +14 -1
- package/dest/prover-agent/memory-proving-queue.d.ts.map +1 -1
- package/dest/prover-agent/memory-proving-queue.js +77 -11
- package/dest/prover-agent/prover-agent.d.ts.map +1 -1
- package/dest/prover-agent/prover-agent.js +32 -13
- package/dest/tx-prover/tx-prover.d.ts +3 -1
- package/dest/tx-prover/tx-prover.d.ts.map +1 -1
- package/dest/tx-prover/tx-prover.js +14 -11
- package/package.json +19 -10
- package/src/config.ts +13 -6
- package/src/mocks/test_context.ts +7 -3
- package/src/orchestrator/orchestrator.ts +159 -45
- package/src/prover-agent/memory-proving-queue.ts +98 -12
- package/src/prover-agent/prover-agent.ts +40 -15
- package/src/tx-prover/tx-prover.ts +22 -8
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type TxEffect,
|
|
10
10
|
makeEmptyProcessedTx,
|
|
11
11
|
makePaddingProcessedTx,
|
|
12
|
+
mapPublicKernelToCircuitName,
|
|
12
13
|
toTxEffect,
|
|
13
14
|
} from '@aztec/circuit-types';
|
|
14
15
|
import {
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
type PublicInputsAndRecursiveProof,
|
|
21
22
|
type ServerCircuitProver,
|
|
22
23
|
} from '@aztec/circuit-types/interfaces';
|
|
24
|
+
import { type CircuitName } from '@aztec/circuit-types/stats';
|
|
23
25
|
import {
|
|
24
26
|
AGGREGATION_OBJECT_LENGTH,
|
|
25
27
|
AvmCircuitInputs,
|
|
@@ -48,11 +50,12 @@ import {
|
|
|
48
50
|
} from '@aztec/circuits.js';
|
|
49
51
|
import { makeTuple } from '@aztec/foundation/array';
|
|
50
52
|
import { padArrayEnd } from '@aztec/foundation/collection';
|
|
51
|
-
import {
|
|
53
|
+
import { AbortError } from '@aztec/foundation/error';
|
|
52
54
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
53
55
|
import { promiseWithResolvers } from '@aztec/foundation/promise';
|
|
54
56
|
import { BufferReader, type Tuple } from '@aztec/foundation/serialize';
|
|
55
57
|
import { pushTestData } from '@aztec/foundation/testing';
|
|
58
|
+
import { Attributes, type TelemetryClient, type Tracer, trackSpan, wrapCallbackInSpan } from '@aztec/telemetry-client';
|
|
56
59
|
import { type MerkleTreeOperations } from '@aztec/world-state';
|
|
57
60
|
|
|
58
61
|
import { inspect } from 'util';
|
|
@@ -91,7 +94,16 @@ export class ProvingOrchestrator {
|
|
|
91
94
|
private pendingProvingJobs: AbortController[] = [];
|
|
92
95
|
private paddingTx: PaddingProcessedTx | undefined = undefined;
|
|
93
96
|
|
|
94
|
-
|
|
97
|
+
public readonly tracer: Tracer;
|
|
98
|
+
|
|
99
|
+
constructor(
|
|
100
|
+
private db: MerkleTreeOperations,
|
|
101
|
+
private prover: ServerCircuitProver,
|
|
102
|
+
telemetryClient: TelemetryClient,
|
|
103
|
+
private initialHeader?: Header,
|
|
104
|
+
) {
|
|
105
|
+
this.tracer = telemetryClient.getTracer('ProvingOrchestrator');
|
|
106
|
+
}
|
|
95
107
|
|
|
96
108
|
/**
|
|
97
109
|
* Resets the orchestrator's cached padding tx.
|
|
@@ -108,6 +120,10 @@ export class ProvingOrchestrator {
|
|
|
108
120
|
* @param verificationKeys - The private kernel verification keys
|
|
109
121
|
* @returns A proving ticket, containing a promise notifying of proving completion
|
|
110
122
|
*/
|
|
123
|
+
@trackSpan('ProvingOrchestrator.startNewBlock', (numTxs, globalVariables) => ({
|
|
124
|
+
[Attributes.BLOCK_SIZE]: numTxs,
|
|
125
|
+
[Attributes.BLOCK_NUMBER]: globalVariables.blockNumber.toNumber(),
|
|
126
|
+
}))
|
|
111
127
|
public async startNewBlock(
|
|
112
128
|
numTxs: number,
|
|
113
129
|
globalVariables: GlobalVariables,
|
|
@@ -193,6 +209,9 @@ export class ProvingOrchestrator {
|
|
|
193
209
|
* The interface to add a simulated transaction to the scheduler
|
|
194
210
|
* @param tx - The transaction to be proven
|
|
195
211
|
*/
|
|
212
|
+
@trackSpan('ProvingOrchestrator.addNewTx', tx => ({
|
|
213
|
+
[Attributes.TX_HASH]: tx.hash.toString(),
|
|
214
|
+
}))
|
|
196
215
|
public async addNewTx(tx: ProcessedTx): Promise<void> {
|
|
197
216
|
if (!this.provingState) {
|
|
198
217
|
throw new Error(`Invalid proving state, call startNewBlock before adding transactions`);
|
|
@@ -213,6 +232,17 @@ export class ProvingOrchestrator {
|
|
|
213
232
|
/**
|
|
214
233
|
* Marks the block as full and pads it to the full power of 2 block size, no more transactions will be accepted.
|
|
215
234
|
*/
|
|
235
|
+
@trackSpan('ProvingOrchestrator.setBlockCompleted', function () {
|
|
236
|
+
if (!this.provingState) {
|
|
237
|
+
return {};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
[Attributes.BLOCK_NUMBER]: this.provingState!.globalVariables.blockNumber.toNumber(),
|
|
242
|
+
[Attributes.BLOCK_SIZE]: this.provingState!.totalNumTxs,
|
|
243
|
+
[Attributes.BLOCK_TXS_COUNT]: this.provingState!.transactionsReceived,
|
|
244
|
+
};
|
|
245
|
+
})
|
|
216
246
|
public async setBlockCompleted() {
|
|
217
247
|
if (!this.provingState) {
|
|
218
248
|
throw new Error(`Invalid proving state, call startNewBlock before adding transactions or completing the block`);
|
|
@@ -264,18 +294,26 @@ export class ProvingOrchestrator {
|
|
|
264
294
|
logger.debug(`Enqueuing deferred proving for padding txs to enqueue ${txInputs.length} paddings`);
|
|
265
295
|
this.deferredProving(
|
|
266
296
|
provingState,
|
|
267
|
-
|
|
268
|
-
this.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
297
|
+
wrapCallbackInSpan(
|
|
298
|
+
this.tracer,
|
|
299
|
+
'ProvingOrchestrator.prover.getEmptyPrivateKernelProof',
|
|
300
|
+
{
|
|
301
|
+
[Attributes.PROTOCOL_CIRCUIT_TYPE]: 'server',
|
|
302
|
+
[Attributes.PROTOCOL_CIRCUIT_NAME]: 'private-kernel-empty' as CircuitName,
|
|
303
|
+
},
|
|
304
|
+
signal =>
|
|
305
|
+
this.prover.getEmptyPrivateKernelProof(
|
|
306
|
+
{
|
|
307
|
+
// Chain id and version should not change even if the proving state does, so it's safe to use them for the padding tx
|
|
308
|
+
// which gets cached across multiple runs of the orchestrator with different proving states. If they were to change,
|
|
309
|
+
// we'd have to clear out the paddingTx here and regenerate it when they do.
|
|
310
|
+
chainId: unprovenPaddingTx.data.constants.txContext.chainId,
|
|
311
|
+
version: unprovenPaddingTx.data.constants.txContext.version,
|
|
312
|
+
header: unprovenPaddingTx.data.constants.historicalHeader,
|
|
313
|
+
},
|
|
314
|
+
signal,
|
|
315
|
+
),
|
|
316
|
+
),
|
|
279
317
|
result => {
|
|
280
318
|
logger.debug(`Completed proof for padding tx, now enqueuing ${txInputs.length} padding txs`);
|
|
281
319
|
this.paddingTx = makePaddingProcessedTx(result);
|
|
@@ -319,6 +357,13 @@ export class ProvingOrchestrator {
|
|
|
319
357
|
* Performs the final tree update for the block and returns the fully proven block.
|
|
320
358
|
* @returns The fully proven block and proof.
|
|
321
359
|
*/
|
|
360
|
+
@trackSpan('ProvingOrchestrator.finaliseBlock', function () {
|
|
361
|
+
return {
|
|
362
|
+
[Attributes.BLOCK_NUMBER]: this.provingState!.globalVariables.blockNumber.toNumber(),
|
|
363
|
+
[Attributes.BLOCK_TXS_COUNT]: this.provingState!.transactionsReceived,
|
|
364
|
+
[Attributes.BLOCK_SIZE]: this.provingState!.totalNumTxs,
|
|
365
|
+
};
|
|
366
|
+
})
|
|
322
367
|
public async finaliseBlock() {
|
|
323
368
|
try {
|
|
324
369
|
if (
|
|
@@ -475,7 +520,7 @@ export class ProvingOrchestrator {
|
|
|
475
520
|
|
|
476
521
|
await callback(result);
|
|
477
522
|
} catch (err) {
|
|
478
|
-
if (err instanceof
|
|
523
|
+
if (err instanceof AbortError) {
|
|
479
524
|
// operation was cancelled, probably because the block was cancelled
|
|
480
525
|
// drop this result
|
|
481
526
|
return;
|
|
@@ -496,6 +541,9 @@ export class ProvingOrchestrator {
|
|
|
496
541
|
}
|
|
497
542
|
|
|
498
543
|
// Updates the merkle trees for a transaction. The first enqueued job for a transaction
|
|
544
|
+
@trackSpan('ProvingOrchestrator.prepareBaseRollupInputs', (_, tx) => ({
|
|
545
|
+
[Attributes.TX_HASH]: tx.hash.toString(),
|
|
546
|
+
}))
|
|
499
547
|
private async prepareBaseRollupInputs(
|
|
500
548
|
provingState: ProvingState | undefined,
|
|
501
549
|
tx: ProcessedTx,
|
|
@@ -593,7 +641,16 @@ export class ProvingOrchestrator {
|
|
|
593
641
|
|
|
594
642
|
this.deferredProving(
|
|
595
643
|
provingState,
|
|
596
|
-
|
|
644
|
+
wrapCallbackInSpan(
|
|
645
|
+
this.tracer,
|
|
646
|
+
'ProvingOrchestrator.prover.getBaseRollupProof',
|
|
647
|
+
{
|
|
648
|
+
[Attributes.TX_HASH]: tx.processedTx.hash.toString(),
|
|
649
|
+
[Attributes.PROTOCOL_CIRCUIT_TYPE]: 'server',
|
|
650
|
+
[Attributes.PROTOCOL_CIRCUIT_NAME]: 'base-rollup' as CircuitName,
|
|
651
|
+
},
|
|
652
|
+
signal => this.prover.getBaseRollupProof(tx.baseRollupInputs, signal),
|
|
653
|
+
),
|
|
597
654
|
result => {
|
|
598
655
|
logger.debug(`Completed proof for base rollup for tx ${tx.processedTx.hash.toString()}`);
|
|
599
656
|
validatePartialState(result.inputs.end, tx.treeSnapshots);
|
|
@@ -622,7 +679,15 @@ export class ProvingOrchestrator {
|
|
|
622
679
|
|
|
623
680
|
this.deferredProving(
|
|
624
681
|
provingState,
|
|
625
|
-
|
|
682
|
+
wrapCallbackInSpan(
|
|
683
|
+
this.tracer,
|
|
684
|
+
'ProvingOrchestrator.prover.getMergeRollupProof',
|
|
685
|
+
{
|
|
686
|
+
[Attributes.PROTOCOL_CIRCUIT_TYPE]: 'server',
|
|
687
|
+
[Attributes.PROTOCOL_CIRCUIT_NAME]: 'merge-rollup' as CircuitName,
|
|
688
|
+
},
|
|
689
|
+
signal => this.prover.getMergeRollupProof(inputs, signal),
|
|
690
|
+
),
|
|
626
691
|
result => {
|
|
627
692
|
this.storeAndExecuteNextMergeLevel(provingState, level, index, [
|
|
628
693
|
result.inputs,
|
|
@@ -658,7 +723,15 @@ export class ProvingOrchestrator {
|
|
|
658
723
|
|
|
659
724
|
this.deferredProving(
|
|
660
725
|
provingState,
|
|
661
|
-
|
|
726
|
+
wrapCallbackInSpan(
|
|
727
|
+
this.tracer,
|
|
728
|
+
'ProvingOrchestrator.prover.getRootRollupProof',
|
|
729
|
+
{
|
|
730
|
+
[Attributes.PROTOCOL_CIRCUIT_TYPE]: 'server',
|
|
731
|
+
[Attributes.PROTOCOL_CIRCUIT_NAME]: 'root-rollup' as CircuitName,
|
|
732
|
+
},
|
|
733
|
+
signal => this.prover.getRootRollupProof(inputs, signal),
|
|
734
|
+
),
|
|
662
735
|
result => {
|
|
663
736
|
provingState.rootRollupPublicInputs = result.inputs;
|
|
664
737
|
provingState.finalAggregationObject = extractAggregationObject(
|
|
@@ -680,7 +753,15 @@ export class ProvingOrchestrator {
|
|
|
680
753
|
private enqueueBaseParityCircuit(provingState: ProvingState, inputs: BaseParityInputs, index: number) {
|
|
681
754
|
this.deferredProving(
|
|
682
755
|
provingState,
|
|
683
|
-
|
|
756
|
+
wrapCallbackInSpan(
|
|
757
|
+
this.tracer,
|
|
758
|
+
'ProvingOrchestrator.prover.getBaseParityProof',
|
|
759
|
+
{
|
|
760
|
+
[Attributes.PROTOCOL_CIRCUIT_TYPE]: 'server',
|
|
761
|
+
[Attributes.PROTOCOL_CIRCUIT_NAME]: 'base-parity' as CircuitName,
|
|
762
|
+
},
|
|
763
|
+
signal => this.prover.getBaseParityProof(inputs, signal),
|
|
764
|
+
),
|
|
684
765
|
rootInput => {
|
|
685
766
|
provingState.setRootParityInputs(rootInput, index);
|
|
686
767
|
if (provingState.areRootParityInputsReady()) {
|
|
@@ -701,7 +782,15 @@ export class ProvingOrchestrator {
|
|
|
701
782
|
private enqueueRootParityCircuit(provingState: ProvingState | undefined, inputs: RootParityInputs) {
|
|
702
783
|
this.deferredProving(
|
|
703
784
|
provingState,
|
|
704
|
-
|
|
785
|
+
wrapCallbackInSpan(
|
|
786
|
+
this.tracer,
|
|
787
|
+
'ProvingOrchestrator.prover.getRootParityProof',
|
|
788
|
+
{
|
|
789
|
+
[Attributes.PROTOCOL_CIRCUIT_TYPE]: 'server',
|
|
790
|
+
[Attributes.PROTOCOL_CIRCUIT_NAME]: 'root-parity' as CircuitName,
|
|
791
|
+
},
|
|
792
|
+
signal => this.prover.getRootParityProof(inputs, signal),
|
|
793
|
+
),
|
|
705
794
|
async rootInput => {
|
|
706
795
|
provingState!.finalRootParityInput = rootInput;
|
|
707
796
|
await this.checkAndEnqueueRootRollup(provingState);
|
|
@@ -770,25 +859,34 @@ export class ProvingOrchestrator {
|
|
|
770
859
|
if (publicFunction.vmRequest) {
|
|
771
860
|
// This function tries to do AVM proving. If there is a failure, it fakes the proof unless AVM_PROVING_STRICT is defined.
|
|
772
861
|
// Nothing downstream depends on the AVM proof yet. So having this mode lets us incrementally build the AVM circuit.
|
|
773
|
-
const doAvmProving =
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
publicFunction.vmRequest!.
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
862
|
+
const doAvmProving = wrapCallbackInSpan(
|
|
863
|
+
this.tracer,
|
|
864
|
+
'ProvingOrchestrator.prover.getAvmProof',
|
|
865
|
+
{
|
|
866
|
+
[Attributes.TX_HASH]: txProvingState.processedTx.hash.toString(),
|
|
867
|
+
[Attributes.APP_CIRCUIT_NAME]: publicFunction.vmRequest!.functionName,
|
|
868
|
+
},
|
|
869
|
+
async (signal: AbortSignal) => {
|
|
870
|
+
const inputs: AvmCircuitInputs = new AvmCircuitInputs(
|
|
871
|
+
publicFunction.vmRequest!.functionName,
|
|
872
|
+
publicFunction.vmRequest!.bytecode,
|
|
873
|
+
publicFunction.vmRequest!.calldata,
|
|
874
|
+
publicFunction.vmRequest!.kernelRequest.inputs.publicCall.callStackItem.publicInputs,
|
|
875
|
+
publicFunction.vmRequest!.avmHints,
|
|
876
|
+
);
|
|
877
|
+
try {
|
|
878
|
+
return await this.prover.getAvmProof(inputs, signal);
|
|
879
|
+
} catch (err) {
|
|
880
|
+
if (process.env.AVM_PROVING_STRICT) {
|
|
881
|
+
throw err;
|
|
882
|
+
} else {
|
|
883
|
+
logger.warn(`Error thrown when proving AVM circuit: ${err}`);
|
|
884
|
+
logger.warn(`AVM_PROVING_STRICT is off, faking AVM proof and carrying on...`);
|
|
885
|
+
return { proof: makeEmptyProof(), verificationKey: VerificationKeyData.makeFake() };
|
|
886
|
+
}
|
|
789
887
|
}
|
|
790
|
-
}
|
|
791
|
-
|
|
888
|
+
},
|
|
889
|
+
);
|
|
792
890
|
this.deferredProving(provingState, doAvmProving, proofAndVk => {
|
|
793
891
|
logger.debug(`Proven VM for function index ${functionIndex} of tx index ${txIndex}`);
|
|
794
892
|
this.checkAndEnqueuePublicKernel(provingState, txIndex, functionIndex, proofAndVk.proof);
|
|
@@ -834,13 +932,25 @@ export class ProvingOrchestrator {
|
|
|
834
932
|
|
|
835
933
|
this.deferredProving(
|
|
836
934
|
provingState,
|
|
837
|
-
(
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
935
|
+
wrapCallbackInSpan(
|
|
936
|
+
this.tracer,
|
|
937
|
+
request.type === PublicKernelType.TAIL
|
|
938
|
+
? 'ProvingOrchestrator.prover.getPublicTailProof'
|
|
939
|
+
: 'ProvingOrchestrator.prover.getPublicKernelProof',
|
|
940
|
+
{
|
|
941
|
+
[Attributes.PROTOCOL_CIRCUIT_TYPE]: 'server',
|
|
942
|
+
[Attributes.PROTOCOL_CIRCUIT_NAME]: mapPublicKernelToCircuitName(request.type),
|
|
943
|
+
},
|
|
944
|
+
(
|
|
945
|
+
signal,
|
|
946
|
+
): Promise<PublicInputsAndRecursiveProof<KernelCircuitPublicInputs | PublicKernelCircuitPublicInputs>> => {
|
|
947
|
+
if (request.type === PublicKernelType.TAIL) {
|
|
948
|
+
return this.prover.getPublicTailProof(request, signal);
|
|
949
|
+
} else {
|
|
950
|
+
return this.prover.getPublicKernelProof(request, signal);
|
|
951
|
+
}
|
|
952
|
+
},
|
|
953
|
+
),
|
|
844
954
|
result => {
|
|
845
955
|
const nextKernelRequest = txProvingState.getNextPublicKernelFromKernelProof(
|
|
846
956
|
functionIndex,
|
|
@@ -879,5 +989,9 @@ function extractAggregationObject(proof: Proof, numPublicInputs: number): Fr[] {
|
|
|
879
989
|
Fr.SIZE_IN_BYTES * (numPublicInputs - AGGREGATION_OBJECT_LENGTH),
|
|
880
990
|
Fr.SIZE_IN_BYTES * numPublicInputs,
|
|
881
991
|
);
|
|
992
|
+
// TODO(#7159): Remove the following workaround
|
|
993
|
+
if (buffer.length === 0) {
|
|
994
|
+
return Array.from({ length: AGGREGATION_OBJECT_LENGTH }, () => Fr.ZERO);
|
|
995
|
+
}
|
|
882
996
|
return BufferReader.asReader(buffer).readArray(AGGREGATION_OBJECT_LENGTH, Fr);
|
|
883
997
|
}
|
|
@@ -27,21 +27,23 @@ import type {
|
|
|
27
27
|
RootRollupPublicInputs,
|
|
28
28
|
} from '@aztec/circuits.js';
|
|
29
29
|
import { randomBytes } from '@aztec/foundation/crypto';
|
|
30
|
-
import {
|
|
30
|
+
import { AbortError, TimeoutError } from '@aztec/foundation/error';
|
|
31
31
|
import { MemoryFifo } from '@aztec/foundation/fifo';
|
|
32
32
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
33
|
-
import { type PromiseWithResolvers, promiseWithResolvers } from '@aztec/foundation/promise';
|
|
33
|
+
import { type PromiseWithResolvers, RunningPromise, promiseWithResolvers } from '@aztec/foundation/promise';
|
|
34
34
|
|
|
35
35
|
type ProvingJobWithResolvers<T extends ProvingRequest = ProvingRequest> = {
|
|
36
36
|
id: string;
|
|
37
37
|
request: T;
|
|
38
38
|
signal?: AbortSignal;
|
|
39
39
|
attempts: number;
|
|
40
|
+
heartbeat: number;
|
|
40
41
|
} & PromiseWithResolvers<ProvingRequestResult<T['type']>>;
|
|
41
42
|
|
|
42
43
|
const MAX_RETRIES = 3;
|
|
43
44
|
|
|
44
45
|
const defaultIdGenerator = () => randomBytes(4).toString('hex');
|
|
46
|
+
const defaultTimeSource = () => Date.now();
|
|
45
47
|
|
|
46
48
|
/**
|
|
47
49
|
* A helper class that sits in between services that need proofs created and agents that can create them.
|
|
@@ -52,9 +54,44 @@ export class MemoryProvingQueue implements ServerCircuitProver, ProvingJobSource
|
|
|
52
54
|
private queue = new MemoryFifo<ProvingJobWithResolvers>();
|
|
53
55
|
private jobsInProgress = new Map<string, ProvingJobWithResolvers>();
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
private runningPromise: RunningPromise;
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
/** Timeout the job if an agent doesn't report back in this time */
|
|
61
|
+
private jobTimeoutMs = 60 * 1000,
|
|
62
|
+
/** How often to check for timed out jobs */
|
|
63
|
+
pollingIntervalMs = 1000,
|
|
64
|
+
private generateId = defaultIdGenerator,
|
|
65
|
+
private timeSource = defaultTimeSource,
|
|
66
|
+
) {
|
|
67
|
+
this.runningPromise = new RunningPromise(this.poll, pollingIntervalMs);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public start() {
|
|
71
|
+
if (this.runningPromise.isRunning()) {
|
|
72
|
+
this.log.warn('Proving queue is already running');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.runningPromise.start();
|
|
77
|
+
this.log.info('Proving queue started');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public async stop() {
|
|
81
|
+
if (!this.runningPromise.isRunning()) {
|
|
82
|
+
this.log.warn('Proving queue is already stopped');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await this.runningPromise.stop();
|
|
87
|
+
this.log.info('Proving queue stopped');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public async getProvingJob({ timeoutSec = 1 } = {}): Promise<ProvingJob<ProvingRequest> | undefined> {
|
|
91
|
+
if (!this.runningPromise.isRunning()) {
|
|
92
|
+
throw new Error('Proving queue is not running. Start the queue before getting jobs.');
|
|
93
|
+
}
|
|
56
94
|
|
|
57
|
-
async getProvingJob({ timeoutSec = 1 } = {}): Promise<ProvingJob<ProvingRequest> | undefined> {
|
|
58
95
|
try {
|
|
59
96
|
const job = await this.queue.get(timeoutSec);
|
|
60
97
|
if (!job) {
|
|
@@ -62,10 +99,10 @@ export class MemoryProvingQueue implements ServerCircuitProver, ProvingJobSource
|
|
|
62
99
|
}
|
|
63
100
|
|
|
64
101
|
if (job.signal?.aborted) {
|
|
65
|
-
this.log.debug(`Job ${job.id} type=${job.request.type} has been aborted`);
|
|
66
102
|
return undefined;
|
|
67
103
|
}
|
|
68
104
|
|
|
105
|
+
job.heartbeat = this.timeSource();
|
|
69
106
|
this.jobsInProgress.set(job.id, job);
|
|
70
107
|
return {
|
|
71
108
|
id: job.id,
|
|
@@ -81,25 +118,33 @@ export class MemoryProvingQueue implements ServerCircuitProver, ProvingJobSource
|
|
|
81
118
|
}
|
|
82
119
|
|
|
83
120
|
resolveProvingJob<T extends ProvingRequestType>(jobId: string, result: ProvingRequestResult<T>): Promise<void> {
|
|
121
|
+
if (!this.runningPromise.isRunning()) {
|
|
122
|
+
throw new Error('Proving queue is not running.');
|
|
123
|
+
}
|
|
124
|
+
|
|
84
125
|
const job = this.jobsInProgress.get(jobId);
|
|
85
126
|
if (!job) {
|
|
86
|
-
|
|
127
|
+
this.log.warn(`Job id=${jobId} not found. Can't resolve`);
|
|
128
|
+
return Promise.resolve();
|
|
87
129
|
}
|
|
88
130
|
|
|
89
131
|
this.jobsInProgress.delete(jobId);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return Promise.resolve();
|
|
132
|
+
if (!job.signal?.aborted) {
|
|
133
|
+
job.resolve(result);
|
|
93
134
|
}
|
|
94
135
|
|
|
95
|
-
job.resolve(result);
|
|
96
136
|
return Promise.resolve();
|
|
97
137
|
}
|
|
98
138
|
|
|
99
139
|
rejectProvingJob(jobId: string, err: any): Promise<void> {
|
|
140
|
+
if (!this.runningPromise.isRunning()) {
|
|
141
|
+
throw new Error('Proving queue is not running.');
|
|
142
|
+
}
|
|
143
|
+
|
|
100
144
|
const job = this.jobsInProgress.get(jobId);
|
|
101
145
|
if (!job) {
|
|
102
|
-
|
|
146
|
+
this.log.warn(`Job id=${jobId} not found. Can't reject`);
|
|
147
|
+
return Promise.resolve();
|
|
103
148
|
}
|
|
104
149
|
|
|
105
150
|
this.jobsInProgress.delete(jobId);
|
|
@@ -123,10 +168,50 @@ export class MemoryProvingQueue implements ServerCircuitProver, ProvingJobSource
|
|
|
123
168
|
return Promise.resolve();
|
|
124
169
|
}
|
|
125
170
|
|
|
171
|
+
public heartbeat(jobId: string): Promise<void> {
|
|
172
|
+
if (!this.runningPromise.isRunning()) {
|
|
173
|
+
throw new Error('Proving queue is not running.');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const job = this.jobsInProgress.get(jobId);
|
|
177
|
+
if (job) {
|
|
178
|
+
job.heartbeat = this.timeSource();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return Promise.resolve();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public isJobRunning(jobId: string): boolean {
|
|
185
|
+
return this.jobsInProgress.has(jobId);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private poll = () => {
|
|
189
|
+
const now = this.timeSource();
|
|
190
|
+
|
|
191
|
+
for (const job of this.jobsInProgress.values()) {
|
|
192
|
+
if (job.signal?.aborted) {
|
|
193
|
+
this.jobsInProgress.delete(job.id);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (job.heartbeat + this.jobTimeoutMs < now) {
|
|
198
|
+
this.log.warn(`Job ${job.id} type=${ProvingRequestType[job.request.type]} has timed out`);
|
|
199
|
+
|
|
200
|
+
this.jobsInProgress.delete(job.id);
|
|
201
|
+
job.heartbeat = 0;
|
|
202
|
+
this.queue.put(job);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
126
207
|
private enqueue<T extends ProvingRequest>(
|
|
127
208
|
request: T,
|
|
128
209
|
signal?: AbortSignal,
|
|
129
210
|
): Promise<ProvingRequestResult<T['type']>> {
|
|
211
|
+
if (!this.runningPromise.isRunning()) {
|
|
212
|
+
return Promise.reject(new Error('Proving queue is not running.'));
|
|
213
|
+
}
|
|
214
|
+
|
|
130
215
|
const { promise, resolve, reject } = promiseWithResolvers<ProvingRequestResult<T['type']>>();
|
|
131
216
|
const item: ProvingJobWithResolvers<T> = {
|
|
132
217
|
id: this.generateId(),
|
|
@@ -136,10 +221,11 @@ export class MemoryProvingQueue implements ServerCircuitProver, ProvingJobSource
|
|
|
136
221
|
resolve,
|
|
137
222
|
reject,
|
|
138
223
|
attempts: 1,
|
|
224
|
+
heartbeat: 0,
|
|
139
225
|
};
|
|
140
226
|
|
|
141
227
|
if (signal) {
|
|
142
|
-
signal.addEventListener('abort', () => reject(new
|
|
228
|
+
signal.addEventListener('abort', () => reject(new AbortError('Operation has been aborted')));
|
|
143
229
|
}
|
|
144
230
|
|
|
145
231
|
this.log.debug(
|
|
@@ -16,7 +16,7 @@ import { ProvingError } from './proving-error.js';
|
|
|
16
16
|
* A helper class that encapsulates a circuit prover and connects it to a job source.
|
|
17
17
|
*/
|
|
18
18
|
export class ProverAgent {
|
|
19
|
-
private inFlightPromises = new
|
|
19
|
+
private inFlightPromises = new Map<string, Promise<any>>();
|
|
20
20
|
private runningPromise?: RunningPromise;
|
|
21
21
|
|
|
22
22
|
constructor(
|
|
@@ -50,20 +50,28 @@ export class ProverAgent {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
this.runningPromise = new RunningPromise(async () => {
|
|
53
|
+
for (const jobId of this.inFlightPromises.keys()) {
|
|
54
|
+
await jobSource.heartbeat(jobId);
|
|
55
|
+
}
|
|
56
|
+
|
|
53
57
|
while (this.inFlightPromises.size < this.maxConcurrency) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
try {
|
|
59
|
+
const job = await jobSource.getProvingJob();
|
|
60
|
+
if (!job) {
|
|
61
|
+
// job source is fully drained, sleep for a bit and try again
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const promise = this.work(jobSource, job).finally(() => this.inFlightPromises.delete(job.id));
|
|
66
|
+
this.inFlightPromises.set(job.id, promise);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
this.log.warn(`Error processing job: ${err}`);
|
|
58
69
|
}
|
|
59
|
-
|
|
60
|
-
const promise = this.work(jobSource, job).finally(() => this.inFlightPromises.delete(promise));
|
|
61
|
-
this.inFlightPromises.add(promise);
|
|
62
70
|
}
|
|
63
71
|
}, this.pollIntervalMs);
|
|
64
72
|
|
|
65
73
|
this.runningPromise.start();
|
|
66
|
-
this.log.info(
|
|
74
|
+
this.log.info(`Agent started with concurrency=${this.maxConcurrency}`);
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
async stop(): Promise<void> {
|
|
@@ -79,14 +87,31 @@ export class ProverAgent {
|
|
|
79
87
|
|
|
80
88
|
private async work(jobSource: ProvingJobSource, job: ProvingJob<ProvingRequest>): Promise<void> {
|
|
81
89
|
try {
|
|
90
|
+
this.log.debug(`Picked up proving job id=${job.id} type=${ProvingRequestType[job.request.type]}`);
|
|
82
91
|
const [time, result] = await elapsed(this.getProof(job.request));
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
if (this.isRunning()) {
|
|
93
|
+
this.log.debug(
|
|
94
|
+
`Processed proving job id=${job.id} type=${ProvingRequestType[job.request.type]} duration=${time}ms`,
|
|
95
|
+
);
|
|
96
|
+
await jobSource.resolveProvingJob(job.id, result);
|
|
97
|
+
} else {
|
|
98
|
+
this.log.debug(
|
|
99
|
+
`Dropping proving job id=${job.id} type=${
|
|
100
|
+
ProvingRequestType[job.request.type]
|
|
101
|
+
} duration=${time}ms: agent stopped`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
87
104
|
} catch (err) {
|
|
88
|
-
this.
|
|
89
|
-
|
|
105
|
+
if (this.isRunning()) {
|
|
106
|
+
this.log.error(
|
|
107
|
+
`Error processing proving job id=${job.id} type=${ProvingRequestType[job.request.type]}: ${err}`,
|
|
108
|
+
);
|
|
109
|
+
await jobSource.rejectProvingJob(job.id, new ProvingError((err as any)?.message ?? String(err)));
|
|
110
|
+
} else {
|
|
111
|
+
this.log.debug(
|
|
112
|
+
`Dropping proving job id=${job.id} type=${ProvingRequestType[job.request.type]}: agent stopped: ${err}`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
90
115
|
}
|
|
91
116
|
}
|
|
92
117
|
|