@aztec/prover-client 0.0.0-test.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.
Files changed (154) hide show
  1. package/README.md +1 -0
  2. package/dest/bin/get-proof-inputs.d.ts +2 -0
  3. package/dest/bin/get-proof-inputs.d.ts.map +1 -0
  4. package/dest/bin/get-proof-inputs.js +51 -0
  5. package/dest/block_builder/index.d.ts +6 -0
  6. package/dest/block_builder/index.d.ts.map +1 -0
  7. package/dest/block_builder/index.js +1 -0
  8. package/dest/block_builder/light.d.ts +33 -0
  9. package/dest/block_builder/light.d.ts.map +1 -0
  10. package/dest/block_builder/light.js +82 -0
  11. package/dest/config.d.ts +17 -0
  12. package/dest/config.d.ts.map +1 -0
  13. package/dest/config.js +39 -0
  14. package/dest/index.d.ts +4 -0
  15. package/dest/index.d.ts.map +1 -0
  16. package/dest/index.js +2 -0
  17. package/dest/mocks/fixtures.d.ts +20 -0
  18. package/dest/mocks/fixtures.d.ts.map +1 -0
  19. package/dest/mocks/fixtures.js +77 -0
  20. package/dest/mocks/test_context.d.ts +55 -0
  21. package/dest/mocks/test_context.d.ts.map +1 -0
  22. package/dest/mocks/test_context.js +193 -0
  23. package/dest/orchestrator/block-building-helpers.d.ts +55 -0
  24. package/dest/orchestrator/block-building-helpers.d.ts.map +1 -0
  25. package/dest/orchestrator/block-building-helpers.js +285 -0
  26. package/dest/orchestrator/block-proving-state.d.ts +76 -0
  27. package/dest/orchestrator/block-proving-state.d.ts.map +1 -0
  28. package/dest/orchestrator/block-proving-state.js +269 -0
  29. package/dest/orchestrator/epoch-proving-state.d.ts +60 -0
  30. package/dest/orchestrator/epoch-proving-state.d.ts.map +1 -0
  31. package/dest/orchestrator/epoch-proving-state.js +163 -0
  32. package/dest/orchestrator/index.d.ts +2 -0
  33. package/dest/orchestrator/index.d.ts.map +1 -0
  34. package/dest/orchestrator/index.js +1 -0
  35. package/dest/orchestrator/orchestrator.d.ts +110 -0
  36. package/dest/orchestrator/orchestrator.d.ts.map +1 -0
  37. package/dest/orchestrator/orchestrator.js +690 -0
  38. package/dest/orchestrator/orchestrator_metrics.d.ts +8 -0
  39. package/dest/orchestrator/orchestrator_metrics.d.ts.map +1 -0
  40. package/dest/orchestrator/orchestrator_metrics.js +17 -0
  41. package/dest/orchestrator/tx-proving-state.d.ts +34 -0
  42. package/dest/orchestrator/tx-proving-state.d.ts.map +1 -0
  43. package/dest/orchestrator/tx-proving-state.js +94 -0
  44. package/dest/prover-client/factory.d.ts +6 -0
  45. package/dest/prover-client/factory.d.ts.map +1 -0
  46. package/dest/prover-client/factory.js +5 -0
  47. package/dest/prover-client/index.d.ts +3 -0
  48. package/dest/prover-client/index.d.ts.map +1 -0
  49. package/dest/prover-client/index.js +2 -0
  50. package/dest/prover-client/prover-client.d.ts +42 -0
  51. package/dest/prover-client/prover-client.d.ts.map +1 -0
  52. package/dest/prover-client/prover-client.js +110 -0
  53. package/dest/prover-client/server-epoch-prover.d.ts +28 -0
  54. package/dest/prover-client/server-epoch-prover.d.ts.map +1 -0
  55. package/dest/prover-client/server-epoch-prover.js +40 -0
  56. package/dest/proving_broker/broker_prover_facade.d.ts +46 -0
  57. package/dest/proving_broker/broker_prover_facade.d.ts.map +1 -0
  58. package/dest/proving_broker/broker_prover_facade.js +344 -0
  59. package/dest/proving_broker/config.d.ts +83 -0
  60. package/dest/proving_broker/config.d.ts.map +1 -0
  61. package/dest/proving_broker/config.js +104 -0
  62. package/dest/proving_broker/factory.d.ts +5 -0
  63. package/dest/proving_broker/factory.d.ts.map +1 -0
  64. package/dest/proving_broker/factory.js +9 -0
  65. package/dest/proving_broker/fixtures.d.ts +5 -0
  66. package/dest/proving_broker/fixtures.d.ts.map +1 -0
  67. package/dest/proving_broker/fixtures.js +12 -0
  68. package/dest/proving_broker/index.d.ts +10 -0
  69. package/dest/proving_broker/index.d.ts.map +1 -0
  70. package/dest/proving_broker/index.js +9 -0
  71. package/dest/proving_broker/proof_store/factory.d.ts +6 -0
  72. package/dest/proving_broker/proof_store/factory.d.ts.map +1 -0
  73. package/dest/proving_broker/proof_store/factory.js +36 -0
  74. package/dest/proving_broker/proof_store/gcs_proof_store.d.ts +14 -0
  75. package/dest/proving_broker/proof_store/gcs_proof_store.d.ts.map +1 -0
  76. package/dest/proving_broker/proof_store/gcs_proof_store.js +51 -0
  77. package/dest/proving_broker/proof_store/index.d.ts +4 -0
  78. package/dest/proving_broker/proof_store/index.d.ts.map +1 -0
  79. package/dest/proving_broker/proof_store/index.js +3 -0
  80. package/dest/proving_broker/proof_store/inline_proof_store.d.ts +15 -0
  81. package/dest/proving_broker/proof_store/inline_proof_store.d.ts.map +1 -0
  82. package/dest/proving_broker/proof_store/inline_proof_store.js +41 -0
  83. package/dest/proving_broker/proof_store/proof_store.d.ts +36 -0
  84. package/dest/proving_broker/proof_store/proof_store.d.ts.map +1 -0
  85. package/dest/proving_broker/proof_store/proof_store.js +3 -0
  86. package/dest/proving_broker/proving_agent.d.ts +46 -0
  87. package/dest/proving_broker/proving_agent.d.ts.map +1 -0
  88. package/dest/proving_broker/proving_agent.js +134 -0
  89. package/dest/proving_broker/proving_agent_instrumentation.d.ts +8 -0
  90. package/dest/proving_broker/proving_agent_instrumentation.d.ts.map +1 -0
  91. package/dest/proving_broker/proving_agent_instrumentation.js +16 -0
  92. package/dest/proving_broker/proving_broker.d.ts +64 -0
  93. package/dest/proving_broker/proving_broker.d.ts.map +1 -0
  94. package/dest/proving_broker/proving_broker.js +570 -0
  95. package/dest/proving_broker/proving_broker_database/memory.d.ts +16 -0
  96. package/dest/proving_broker/proving_broker_database/memory.d.ts.map +1 -0
  97. package/dest/proving_broker/proving_broker_database/memory.js +54 -0
  98. package/dest/proving_broker/proving_broker_database/persisted.d.ts +25 -0
  99. package/dest/proving_broker/proving_broker_database/persisted.d.ts.map +1 -0
  100. package/dest/proving_broker/proving_broker_database/persisted.js +182 -0
  101. package/dest/proving_broker/proving_broker_database.d.ts +39 -0
  102. package/dest/proving_broker/proving_broker_database.d.ts.map +1 -0
  103. package/dest/proving_broker/proving_broker_database.js +3 -0
  104. package/dest/proving_broker/proving_broker_instrumentation.d.ts +29 -0
  105. package/dest/proving_broker/proving_broker_instrumentation.d.ts.map +1 -0
  106. package/dest/proving_broker/proving_broker_instrumentation.js +110 -0
  107. package/dest/proving_broker/proving_job_controller.d.ts +33 -0
  108. package/dest/proving_broker/proving_job_controller.d.ts.map +1 -0
  109. package/dest/proving_broker/proving_job_controller.js +166 -0
  110. package/dest/proving_broker/rpc.d.ts +27 -0
  111. package/dest/proving_broker/rpc.d.ts.map +1 -0
  112. package/dest/proving_broker/rpc.js +66 -0
  113. package/dest/test/mock_prover.d.ts +35 -0
  114. package/dest/test/mock_prover.d.ts.map +1 -0
  115. package/dest/test/mock_prover.js +82 -0
  116. package/package.json +112 -0
  117. package/src/bin/get-proof-inputs.ts +59 -0
  118. package/src/block_builder/index.ts +6 -0
  119. package/src/block_builder/light.ts +101 -0
  120. package/src/config.ts +55 -0
  121. package/src/index.ts +4 -0
  122. package/src/mocks/fixtures.ts +117 -0
  123. package/src/mocks/test_context.ts +257 -0
  124. package/src/orchestrator/block-building-helpers.ts +553 -0
  125. package/src/orchestrator/block-proving-state.ts +379 -0
  126. package/src/orchestrator/epoch-proving-state.ts +252 -0
  127. package/src/orchestrator/index.ts +1 -0
  128. package/src/orchestrator/orchestrator.ts +971 -0
  129. package/src/orchestrator/orchestrator_metrics.ts +22 -0
  130. package/src/orchestrator/tx-proving-state.ts +139 -0
  131. package/src/prover-client/factory.ts +14 -0
  132. package/src/prover-client/index.ts +2 -0
  133. package/src/prover-client/prover-client.ts +162 -0
  134. package/src/prover-client/server-epoch-prover.ts +51 -0
  135. package/src/proving_broker/broker_prover_facade.ts +585 -0
  136. package/src/proving_broker/config.ts +138 -0
  137. package/src/proving_broker/factory.ts +18 -0
  138. package/src/proving_broker/fixtures.ts +15 -0
  139. package/src/proving_broker/index.ts +9 -0
  140. package/src/proving_broker/proof_store/factory.ts +42 -0
  141. package/src/proving_broker/proof_store/gcs_proof_store.ts +72 -0
  142. package/src/proving_broker/proof_store/index.ts +3 -0
  143. package/src/proving_broker/proof_store/inline_proof_store.ts +63 -0
  144. package/src/proving_broker/proof_store/proof_store.ts +54 -0
  145. package/src/proving_broker/proving_agent.ts +181 -0
  146. package/src/proving_broker/proving_agent_instrumentation.ts +21 -0
  147. package/src/proving_broker/proving_broker.ts +687 -0
  148. package/src/proving_broker/proving_broker_database/memory.ts +63 -0
  149. package/src/proving_broker/proving_broker_database/persisted.ts +218 -0
  150. package/src/proving_broker/proving_broker_database.ts +44 -0
  151. package/src/proving_broker/proving_broker_instrumentation.ts +145 -0
  152. package/src/proving_broker/proving_job_controller.ts +194 -0
  153. package/src/proving_broker/rpc.ts +95 -0
  154. package/src/test/mock_prover.ts +253 -0
@@ -0,0 +1,687 @@
1
+ import { createLogger } from '@aztec/foundation/log';
2
+ import { type PromiseWithResolvers, RunningPromise, promiseWithResolvers } from '@aztec/foundation/promise';
3
+ import { PriorityMemoryQueue } from '@aztec/foundation/queue';
4
+ import { Timer } from '@aztec/foundation/timer';
5
+ import type {
6
+ GetProvingJobResponse,
7
+ ProofUri,
8
+ ProvingJob,
9
+ ProvingJobConsumer,
10
+ ProvingJobFilter,
11
+ ProvingJobId,
12
+ ProvingJobProducer,
13
+ ProvingJobSettledResult,
14
+ ProvingJobStatus,
15
+ } from '@aztec/stdlib/interfaces/server';
16
+ import { ProvingRequestType } from '@aztec/stdlib/proofs';
17
+ import {
18
+ type TelemetryClient,
19
+ type Traceable,
20
+ type Tracer,
21
+ getTelemetryClient,
22
+ trackSpan,
23
+ } from '@aztec/telemetry-client';
24
+
25
+ import assert from 'assert';
26
+
27
+ import { type ProverBrokerConfig, defaultProverBrokerConfig } from './config.js';
28
+ import type { ProvingBrokerDatabase } from './proving_broker_database.js';
29
+ import { type MonitorCallback, ProvingBrokerInstrumentation } from './proving_broker_instrumentation.js';
30
+
31
+ type InProgressMetadata = {
32
+ id: ProvingJobId;
33
+ startedAt: number;
34
+ lastUpdatedAt: number;
35
+ };
36
+
37
+ type EnqueuedProvingJob = Pick<ProvingJob, 'id' | 'epochNumber'>;
38
+
39
+ /**
40
+ * A broker that manages proof requests and distributes them to workers based on their priority.
41
+ * It takes a backend that is responsible for storing and retrieving proof requests and results.
42
+ */
43
+ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Traceable {
44
+ private queues: ProvingQueues = {
45
+ [ProvingRequestType.PUBLIC_VM]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
46
+ [ProvingRequestType.TUBE_PROOF]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
47
+
48
+ [ProvingRequestType.PRIVATE_BASE_ROLLUP]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
49
+ [ProvingRequestType.PUBLIC_BASE_ROLLUP]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
50
+ [ProvingRequestType.MERGE_ROLLUP]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
51
+ [ProvingRequestType.ROOT_ROLLUP]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
52
+
53
+ [ProvingRequestType.BLOCK_MERGE_ROLLUP]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
54
+ [ProvingRequestType.BLOCK_ROOT_ROLLUP]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
55
+ [ProvingRequestType.SINGLE_TX_BLOCK_ROOT_ROLLUP]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
56
+ [ProvingRequestType.EMPTY_BLOCK_ROOT_ROLLUP]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
57
+
58
+ [ProvingRequestType.BASE_PARITY]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
59
+ [ProvingRequestType.ROOT_PARITY]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
60
+ };
61
+
62
+ // holds a copy of the database in memory in order to quickly fulfill requests
63
+ // this is fine because this broker is the only one that can modify the database
64
+ private jobsCache = new Map<ProvingJobId, ProvingJob>();
65
+ // as above, but for results
66
+ private resultsCache = new Map<ProvingJobId, ProvingJobSettledResult>();
67
+
68
+ // tracks when each job was enqueued
69
+ private enqueuedAt = new Map<ProvingJobId, Timer>();
70
+
71
+ // keeps track of which jobs are currently being processed
72
+ // in the event of a crash this information is lost, but that's ok
73
+ // the next time the broker starts it will recreate jobsCache and still
74
+ // accept results from the workers
75
+ private inProgress = new Map<ProvingJobId, InProgressMetadata>();
76
+
77
+ // keep track of which proving job has been retried
78
+ private retries = new Map<ProvingJobId, number>();
79
+
80
+ // a map of promises that will be resolved when a job is settled
81
+ private promises = new Map<ProvingJobId, PromiseWithResolvers<ProvingJobSettledResult>>();
82
+
83
+ private cleanupPromise: RunningPromise;
84
+ private msTimeSource = () => Date.now();
85
+ private jobTimeoutMs: number;
86
+ private maxRetries: number;
87
+
88
+ private instrumentation: ProvingBrokerInstrumentation;
89
+ public readonly tracer: Tracer;
90
+
91
+ private completedJobNotifications: ProvingJobId[] = [];
92
+
93
+ /**
94
+ * The broker keeps track of the highest epoch its seen.
95
+ * This information is used for garbage collection: once it reaches the next epoch, it can start pruning the database of old state.
96
+ * It is important that this value is initialised to zero. This ensures that we don't delete any old jobs until the current
97
+ * process instance receives a job request informing it of the actual current highest epoch
98
+ * Example:
99
+ * proving epoch 11 - the broker will wipe all jobs for epochs 9 and lower
100
+ * finished proving epoch 11 and got first job for epoch 12 -> the broker will wipe all settled jobs for epochs 10 and lower
101
+ * reorged back to end of epoch 10 -> epoch 11 is skipped and epoch 12 starts -> the broker will wipe all settled jobs for epochs 10 and lower
102
+ */
103
+ private epochHeight = 0;
104
+ private maxEpochsToKeepResultsFor = 1;
105
+
106
+ private started = false;
107
+
108
+ public constructor(
109
+ private database: ProvingBrokerDatabase,
110
+ {
111
+ proverBrokerJobTimeoutMs,
112
+ proverBrokerPollIntervalMs,
113
+ proverBrokerJobMaxRetries,
114
+ proverBrokerMaxEpochsToKeepResultsFor,
115
+ }: Required<
116
+ Pick<
117
+ ProverBrokerConfig,
118
+ | 'proverBrokerJobTimeoutMs'
119
+ | 'proverBrokerPollIntervalMs'
120
+ | 'proverBrokerJobMaxRetries'
121
+ | 'proverBrokerMaxEpochsToKeepResultsFor'
122
+ >
123
+ > = defaultProverBrokerConfig,
124
+ client: TelemetryClient = getTelemetryClient(),
125
+ private logger = createLogger('prover-client:proving-broker'),
126
+ ) {
127
+ this.tracer = client.getTracer('ProvingBroker');
128
+ this.instrumentation = new ProvingBrokerInstrumentation(client);
129
+ this.cleanupPromise = new RunningPromise(this.cleanupPass.bind(this), this.logger, proverBrokerPollIntervalMs);
130
+ this.jobTimeoutMs = proverBrokerJobTimeoutMs!;
131
+ this.maxRetries = proverBrokerJobMaxRetries!;
132
+ this.maxEpochsToKeepResultsFor = proverBrokerMaxEpochsToKeepResultsFor!;
133
+ }
134
+
135
+ private measureQueueDepth: MonitorCallback = (type: ProvingRequestType) => {
136
+ return this.queues[type].length();
137
+ };
138
+
139
+ private countActiveJobs: MonitorCallback = (type: ProvingRequestType) => {
140
+ let count = 0;
141
+ for (const { id } of this.inProgress.values()) {
142
+ const job = this.jobsCache.get(id);
143
+ if (job?.type === type) {
144
+ count++;
145
+ }
146
+ }
147
+
148
+ return count;
149
+ };
150
+
151
+ public async start(): Promise<void> {
152
+ if (this.started) {
153
+ this.logger.info('Proving Broker already started');
154
+ return Promise.resolve();
155
+ }
156
+ this.logger.info('Proving Broker started');
157
+ for await (const [item, result] of this.database.allProvingJobs()) {
158
+ this.logger.info(`Restoring proving job id=${item.id} settled=${!!result}`, {
159
+ provingJobId: item.id,
160
+ status: result ? result.status : 'pending',
161
+ });
162
+
163
+ this.jobsCache.set(item.id, item);
164
+ this.promises.set(item.id, promiseWithResolvers());
165
+
166
+ if (result) {
167
+ this.promises.get(item.id)!.resolve(result);
168
+ this.resultsCache.set(item.id, result);
169
+ } else {
170
+ this.enqueueJobInternal(item);
171
+ }
172
+ }
173
+
174
+ this.cleanupPromise.start();
175
+
176
+ this.instrumentation.monitorQueueDepth(this.measureQueueDepth);
177
+ this.instrumentation.monitorActiveJobs(this.countActiveJobs);
178
+
179
+ this.started = true;
180
+ }
181
+
182
+ public async stop(): Promise<void> {
183
+ if (!this.started) {
184
+ this.logger.warn('ProvingBroker not started');
185
+ return Promise.resolve();
186
+ }
187
+ await this.cleanupPromise.stop();
188
+ }
189
+
190
+ public enqueueProvingJob(job: ProvingJob): Promise<ProvingJobStatus> {
191
+ return this.#enqueueProvingJob(job);
192
+ }
193
+
194
+ public cancelProvingJob(id: ProvingJobId): Promise<void> {
195
+ return this.#cancelProvingJob(id);
196
+ }
197
+
198
+ public getProvingJobStatus(id: ProvingJobId): Promise<ProvingJobStatus> {
199
+ return Promise.resolve(this.#getProvingJobStatus(id));
200
+ }
201
+
202
+ public getCompletedJobs(ids: ProvingJobId[]): Promise<ProvingJobId[]> {
203
+ return this.#getCompletedJobs(ids);
204
+ }
205
+
206
+ public getProvingJob(filter?: ProvingJobFilter): Promise<GetProvingJobResponse | undefined> {
207
+ return Promise.resolve(this.#getProvingJob(filter));
208
+ }
209
+
210
+ public reportProvingJobSuccess(
211
+ id: ProvingJobId,
212
+ value: ProofUri,
213
+ filter?: ProvingJobFilter,
214
+ ): Promise<GetProvingJobResponse | undefined> {
215
+ return this.#reportProvingJobSuccess(id, value, filter);
216
+ }
217
+
218
+ public reportProvingJobError(
219
+ id: ProvingJobId,
220
+ err: string,
221
+ retry = false,
222
+ filter?: ProvingJobFilter,
223
+ ): Promise<GetProvingJobResponse | undefined> {
224
+ return this.#reportProvingJobError(id, err, retry, filter);
225
+ }
226
+
227
+ public reportProvingJobProgress(
228
+ id: ProvingJobId,
229
+ startedAt: number,
230
+ filter?: ProvingJobFilter,
231
+ ): Promise<{ job: ProvingJob; time: number } | undefined> {
232
+ return Promise.resolve(this.#reportProvingJobProgress(id, startedAt, filter));
233
+ }
234
+
235
+ async #enqueueProvingJob(job: ProvingJob): Promise<ProvingJobStatus> {
236
+ // We return the job status at the start of this call
237
+ const jobStatus = this.#getProvingJobStatus(job.id);
238
+ if (this.jobsCache.has(job.id)) {
239
+ const existing = this.jobsCache.get(job.id);
240
+ assert.deepStrictEqual(job, existing, 'Duplicate proving job ID');
241
+ this.logger.warn(`Cached proving job id=${job.id} epochNumber=${job.epochNumber}. Not enqueuing again`, {
242
+ provingJobId: job.id,
243
+ });
244
+ this.instrumentation.incCachedJobs(job.type);
245
+ return jobStatus;
246
+ }
247
+
248
+ if (this.isJobStale(job)) {
249
+ this.logger.warn(`Tried enqueueing stale proving job id=${job.id} epochNumber=${job.epochNumber}`, {
250
+ provingJobId: job.id,
251
+ });
252
+ throw new Error(`Epoch too old: job epoch ${job.epochNumber}, current epoch: ${this.epochHeight}`);
253
+ }
254
+
255
+ this.logger.info(`New proving job id=${job.id} epochNumber=${job.epochNumber}`, { provingJobId: job.id });
256
+ try {
257
+ // do this first so it acts as a "lock". If this job is enqueued again while we're saving it the if at the top will catch it.
258
+ this.jobsCache.set(job.id, job);
259
+ await this.database.addProvingJob(job);
260
+ this.enqueueJobInternal(job);
261
+ this.instrumentation.incTotalJobs(job.type);
262
+ } catch (err) {
263
+ this.logger.error(`Failed to save proving job id=${job.id}: ${err}`, err, { provingJobId: job.id });
264
+ this.jobsCache.delete(job.id);
265
+ throw err;
266
+ }
267
+ return jobStatus;
268
+ }
269
+
270
+ async #cancelProvingJob(id: ProvingJobId): Promise<void> {
271
+ if (!this.jobsCache.has(id)) {
272
+ this.logger.warn(`Can't cancel a job that doesn't exist id=${id}`, { provingJobId: id });
273
+ return;
274
+ }
275
+
276
+ // notify listeners of the cancellation
277
+ if (!this.resultsCache.has(id)) {
278
+ this.logger.info(`Cancelling job id=${id}`, { provingJobId: id });
279
+ await this.#reportProvingJobError(id, 'Aborted', false);
280
+ }
281
+ }
282
+
283
+ private cleanUpProvingJobState(ids: ProvingJobId[]) {
284
+ for (const id of ids) {
285
+ this.jobsCache.delete(id);
286
+ this.promises.delete(id);
287
+ this.resultsCache.delete(id);
288
+ this.inProgress.delete(id);
289
+ this.retries.delete(id);
290
+ }
291
+ }
292
+
293
+ #getProvingJobStatus(id: ProvingJobId): ProvingJobStatus {
294
+ const result = this.resultsCache.get(id);
295
+ if (result) {
296
+ return result;
297
+ } else {
298
+ // no result yet, check if we know the item
299
+ const item = this.jobsCache.get(id);
300
+
301
+ if (!item) {
302
+ return { status: 'not-found' };
303
+ }
304
+
305
+ return { status: this.inProgress.has(id) ? 'in-progress' : 'in-queue' };
306
+ }
307
+ }
308
+
309
+ #getCompletedJobs(ids: ProvingJobId[]): Promise<ProvingJobId[]> {
310
+ const completedJobs = ids.filter(id => this.resultsCache.has(id));
311
+ const notifications = this.completedJobNotifications;
312
+ this.completedJobNotifications = [];
313
+ return Promise.resolve(notifications.concat(completedJobs));
314
+ }
315
+
316
+ // eslint-disable-next-line require-await
317
+ #getProvingJob(filter: ProvingJobFilter = { allowList: [] }): { job: ProvingJob; time: number } | undefined {
318
+ const allowedProofs: ProvingRequestType[] =
319
+ Array.isArray(filter.allowList) && filter.allowList.length > 0
320
+ ? [...filter.allowList]
321
+ : Object.values(ProvingRequestType).filter((x): x is ProvingRequestType => typeof x === 'number');
322
+ allowedProofs.sort(proofTypeComparator);
323
+
324
+ for (const proofType of allowedProofs) {
325
+ const queue = this.queues[proofType];
326
+ let enqueuedJob: EnqueuedProvingJob | undefined;
327
+ // exhaust the queue and make sure we're not sending a job that's already in progress
328
+ // or has already been completed
329
+ // this can happen if the broker crashes and restarts
330
+ // it's possible agents will report progress or results for jobs that are in the queue (after the restart)
331
+ while ((enqueuedJob = queue.getImmediate())) {
332
+ const job = this.jobsCache.get(enqueuedJob.id);
333
+ if (job && !this.inProgress.has(enqueuedJob.id) && !this.resultsCache.has(enqueuedJob.id)) {
334
+ const time = this.msTimeSource();
335
+ this.inProgress.set(job.id, {
336
+ id: job.id,
337
+ startedAt: time,
338
+ lastUpdatedAt: time,
339
+ });
340
+ const enqueuedAt = this.enqueuedAt.get(job.id);
341
+ if (enqueuedAt) {
342
+ this.instrumentation.recordJobWait(job.type, enqueuedAt);
343
+ }
344
+
345
+ return { job, time };
346
+ }
347
+ }
348
+ }
349
+
350
+ return undefined;
351
+ }
352
+
353
+ async #reportProvingJobError(
354
+ id: ProvingJobId,
355
+ err: string,
356
+ retry = false,
357
+ filter?: ProvingJobFilter,
358
+ ): Promise<GetProvingJobResponse | undefined> {
359
+ const info = this.inProgress.get(id);
360
+ const item = this.jobsCache.get(id);
361
+ const retries = this.retries.get(id) ?? 0;
362
+
363
+ if (!item) {
364
+ this.logger.warn(`Can't set error on unknown proving job id=${id} err=${err}`, { provingJoId: id });
365
+ return;
366
+ }
367
+
368
+ if (!info) {
369
+ this.logger.warn(`Proving job id=${id} type=${ProvingRequestType[item.type]} not in the in-progress set`, {
370
+ provingJobId: id,
371
+ });
372
+ } else {
373
+ this.inProgress.delete(id);
374
+ }
375
+
376
+ if (this.resultsCache.has(id)) {
377
+ this.logger.warn(`Proving job id=${id} is already settled, ignoring err=${err}`, {
378
+ provingJobId: id,
379
+ });
380
+ return this.#getProvingJob(filter);
381
+ }
382
+
383
+ if (retry && retries + 1 < this.maxRetries && !this.isJobStale(item)) {
384
+ this.logger.info(
385
+ `Retrying proving job id=${id} type=${ProvingRequestType[item.type]} retry=${retries + 1} err=${err}`,
386
+ {
387
+ provingJobId: id,
388
+ },
389
+ );
390
+
391
+ // assign another job to this agent
392
+ // do this first, before we put the failed job back in the queue
393
+ const maybeAnotherJob = this.#getProvingJob(filter);
394
+
395
+ this.retries.set(id, retries + 1);
396
+ this.enqueueJobInternal(item);
397
+ this.instrumentation.incRetriedJobs(item.type);
398
+
399
+ return maybeAnotherJob;
400
+ }
401
+
402
+ this.logger.info(
403
+ `Marking proving job as failed id=${id} type=${ProvingRequestType[item.type]} totalAttempts=${
404
+ retries + 1
405
+ } err=${err}`,
406
+ {
407
+ provingJobId: id,
408
+ },
409
+ );
410
+
411
+ // save the result to the cache and notify clients of the job status
412
+ // this should work even if our database breaks because the result is cached in memory
413
+ const result: ProvingJobSettledResult = { status: 'rejected', reason: String(err) };
414
+ this.resultsCache.set(id, result);
415
+ this.promises.get(id)!.resolve(result);
416
+ this.completedJobNotifications.push(id);
417
+
418
+ this.instrumentation.incRejectedJobs(item.type);
419
+ if (info) {
420
+ const duration = this.msTimeSource() - info.startedAt;
421
+ this.instrumentation.recordJobDuration(item.type, duration);
422
+ }
423
+
424
+ try {
425
+ await this.database.setProvingJobError(id, err);
426
+ } catch (saveErr) {
427
+ this.logger.error(`Failed to save proving job error status id=${id} jobErr=${err}`, saveErr, {
428
+ provingJobId: id,
429
+ });
430
+
431
+ throw saveErr;
432
+ }
433
+
434
+ return this.#getProvingJob(filter);
435
+ }
436
+
437
+ #reportProvingJobProgress(
438
+ id: ProvingJobId,
439
+ startedAt: number,
440
+ filter?: ProvingJobFilter,
441
+ ): { job: ProvingJob; time: number } | undefined {
442
+ const job = this.jobsCache.get(id);
443
+ if (!job) {
444
+ this.logger.warn(`Proving job id=${id} does not exist`, { provingJobId: id });
445
+ return this.#getProvingJob(filter);
446
+ }
447
+
448
+ if (this.resultsCache.has(id)) {
449
+ this.logger.warn(`Proving job id=${id} has already been completed`, { provingJobId: id });
450
+ return this.#getProvingJob(filter);
451
+ }
452
+
453
+ const metadata = this.inProgress.get(id);
454
+ const now = this.msTimeSource();
455
+ if (!metadata) {
456
+ this.logger.warn(
457
+ `Proving job id=${id} type=${ProvingRequestType[job.type]} not found in the in-progress cache, adding it`,
458
+ { provingJobId: id },
459
+ );
460
+ // the queue will still contain the item at this point!
461
+ // we need to be careful when popping off the queue to make sure we're not sending
462
+ // a job that's already in progress
463
+ this.inProgress.set(id, {
464
+ id,
465
+ startedAt,
466
+ lastUpdatedAt: this.msTimeSource(),
467
+ });
468
+ return undefined;
469
+ } else if (startedAt <= metadata.startedAt) {
470
+ if (startedAt < metadata.startedAt) {
471
+ this.logger.info(
472
+ `Proving job id=${id} type=${ProvingRequestType[job.type]} startedAt=${startedAt} older agent has taken job`,
473
+ { provingJobId: id },
474
+ );
475
+ } else {
476
+ this.logger.debug(`Proving job id=${id} type=${ProvingRequestType[job.type]} heartbeat`, { provingJobId: id });
477
+ }
478
+ metadata.startedAt = startedAt;
479
+ metadata.lastUpdatedAt = now;
480
+ return undefined;
481
+ }
482
+
483
+ this.logger.warn(
484
+ `Proving job id=${id} type=${
485
+ ProvingRequestType[job.type]
486
+ } already being worked on by another agent. Sending new one`,
487
+ { provingJobId: id },
488
+ );
489
+
490
+ return this.#getProvingJob(filter);
491
+ }
492
+
493
+ async #reportProvingJobSuccess(
494
+ id: ProvingJobId,
495
+ value: ProofUri,
496
+ filter?: ProvingJobFilter,
497
+ ): Promise<GetProvingJobResponse | undefined> {
498
+ const info = this.inProgress.get(id);
499
+ const item = this.jobsCache.get(id);
500
+ const retries = this.retries.get(id) ?? 0;
501
+ if (!item) {
502
+ this.logger.warn(`Proving job id=${id} not found`, { provingJobId: id });
503
+ return;
504
+ }
505
+
506
+ if (!info) {
507
+ this.logger.warn(`Proving job id=${id} type=${ProvingRequestType[item.type]} not in the in-progress set`, {
508
+ provingJobId: id,
509
+ });
510
+ } else {
511
+ this.inProgress.delete(id);
512
+ }
513
+
514
+ if (this.resultsCache.has(id)) {
515
+ this.logger.warn(`Proving job id=${id} already settled, ignoring result`, { provingJobId: id });
516
+ return;
517
+ }
518
+
519
+ this.logger.info(
520
+ `Proving job complete id=${id} type=${ProvingRequestType[item.type]} totalAttempts=${retries + 1}`,
521
+ { provingJobId: id },
522
+ );
523
+
524
+ // save result to our local cache and notify clients
525
+ // if save to database fails, that's ok because we have the result in memory
526
+ // if the broker crashes and needs the result again, we're covered because we can just recompute it
527
+ const result: ProvingJobSettledResult = { status: 'fulfilled', value };
528
+ this.resultsCache.set(id, result);
529
+ this.promises.get(id)!.resolve(result);
530
+ this.completedJobNotifications.push(id);
531
+
532
+ this.instrumentation.incResolvedJobs(item.type);
533
+ if (info) {
534
+ const duration = this.msTimeSource() - info.startedAt;
535
+ this.instrumentation.recordJobDuration(item.type, duration);
536
+ }
537
+
538
+ try {
539
+ await this.database.setProvingJobResult(id, value);
540
+ } catch (saveErr) {
541
+ this.logger.error(`Failed to save proving job result id=${id}`, saveErr, {
542
+ provingJobId: id,
543
+ });
544
+
545
+ throw saveErr;
546
+ }
547
+
548
+ return this.#getProvingJob(filter);
549
+ }
550
+
551
+ @trackSpan('ProvingBroker.cleanupPass')
552
+ private async cleanupPass() {
553
+ this.cleanupStaleJobs();
554
+ this.reEnqueueExpiredJobs();
555
+ const oldestEpochToKeep = this.oldestEpochToKeep();
556
+ if (oldestEpochToKeep > 0) {
557
+ await this.database.deleteAllProvingJobsOlderThanEpoch(oldestEpochToKeep);
558
+ this.logger.trace(`Deleted all epochs older than ${oldestEpochToKeep}`);
559
+ }
560
+ }
561
+
562
+ private cleanupStaleJobs() {
563
+ const jobIds = Array.from(this.jobsCache.keys());
564
+ const jobsToClean: ProvingJobId[] = [];
565
+ for (const id of jobIds) {
566
+ const job = this.jobsCache.get(id)!;
567
+ if (this.isJobStale(job)) {
568
+ jobsToClean.push(id);
569
+ }
570
+ }
571
+
572
+ if (jobsToClean.length > 0) {
573
+ this.cleanUpProvingJobState(jobsToClean);
574
+ this.logger.info(`Cleaned up jobs=${jobsToClean.length}`);
575
+ }
576
+ }
577
+
578
+ private reEnqueueExpiredJobs() {
579
+ const inProgressEntries = Array.from(this.inProgress.entries());
580
+ for (const [id, metadata] of inProgressEntries) {
581
+ const item = this.jobsCache.get(id);
582
+ if (!item) {
583
+ this.logger.warn(`Proving job id=${id} not found. Removing it from the queue.`, { provingJobId: id });
584
+ this.inProgress.delete(id);
585
+ continue;
586
+ }
587
+
588
+ const now = this.msTimeSource();
589
+ const msSinceLastUpdate = now - metadata.lastUpdatedAt;
590
+ if (msSinceLastUpdate >= this.jobTimeoutMs) {
591
+ this.logger.warn(`Proving job id=${id} timed out. Adding it back to the queue.`, { provingJobId: id });
592
+ this.inProgress.delete(id);
593
+ this.enqueueJobInternal(item);
594
+ this.instrumentation.incTimedOutJobs(item.type);
595
+ }
596
+ }
597
+ }
598
+
599
+ private enqueueJobInternal(job: ProvingJob): void {
600
+ if (!this.promises.has(job.id)) {
601
+ this.promises.set(job.id, promiseWithResolvers());
602
+ }
603
+ this.queues[job.type].put({
604
+ epochNumber: job.epochNumber,
605
+ id: job.id,
606
+ });
607
+ this.enqueuedAt.set(job.id, new Timer());
608
+ this.epochHeight = Math.max(this.epochHeight, job.epochNumber);
609
+ }
610
+
611
+ private isJobStale(job: ProvingJob) {
612
+ return job.epochNumber < this.oldestEpochToKeep();
613
+ }
614
+
615
+ private oldestEpochToKeep() {
616
+ return this.epochHeight - this.maxEpochsToKeepResultsFor;
617
+ }
618
+ }
619
+
620
+ type ProvingQueues = {
621
+ [K in ProvingRequestType]: PriorityMemoryQueue<EnqueuedProvingJob>;
622
+ };
623
+
624
+ /**
625
+ * Compares two proving jobs and selects which one's more important
626
+ * @param a - A proving job
627
+ * @param b - Another proving job
628
+ * @returns A number indicating the relative priority of the two proving jobs
629
+ */
630
+ function provingJobComparator(a: EnqueuedProvingJob, b: EnqueuedProvingJob): -1 | 0 | 1 {
631
+ if (a.epochNumber < b.epochNumber) {
632
+ return -1;
633
+ } else if (a.epochNumber > b.epochNumber) {
634
+ return 1;
635
+ } else {
636
+ return 0;
637
+ }
638
+ }
639
+
640
+ /**
641
+ * Compares two proofs and selects which one's more important.
642
+ * If some proofs does not exist in the priority array then it's considered the least important.
643
+ *
644
+ * @param a - A proof type
645
+ * @param b - Another proof type
646
+ * @returns A number indicating the relative priority of the two proof types
647
+ */
648
+ function proofTypeComparator(a: ProvingRequestType, b: ProvingRequestType): -1 | 0 | 1 {
649
+ const indexOfA = PROOF_TYPES_IN_PRIORITY_ORDER.indexOf(a);
650
+ const indexOfB = PROOF_TYPES_IN_PRIORITY_ORDER.indexOf(b);
651
+ if (indexOfA === indexOfB) {
652
+ return 0;
653
+ } else if (indexOfA === -1) {
654
+ // a is some new proof that didn't get added to the array
655
+ // b is more important because we know about it
656
+ return 1;
657
+ } else if (indexOfB === -1) {
658
+ // the opposite of the previous if branch
659
+ return -1;
660
+ } else if (indexOfA < indexOfB) {
661
+ return -1;
662
+ } else {
663
+ return 1;
664
+ }
665
+ }
666
+
667
+ /**
668
+ * Relative priority of each proof type. Proofs higher up on the list are more important and should be prioritized
669
+ * over proofs lower on the list.
670
+ *
671
+ * The aim is that this will speed up block proving as the closer we get to a block's root proof the more likely it
672
+ * is to get picked up by agents
673
+ */
674
+ const PROOF_TYPES_IN_PRIORITY_ORDER: ProvingRequestType[] = [
675
+ ProvingRequestType.BLOCK_ROOT_ROLLUP,
676
+ ProvingRequestType.SINGLE_TX_BLOCK_ROOT_ROLLUP,
677
+ ProvingRequestType.BLOCK_MERGE_ROLLUP,
678
+ ProvingRequestType.ROOT_ROLLUP,
679
+ ProvingRequestType.MERGE_ROLLUP,
680
+ ProvingRequestType.PUBLIC_BASE_ROLLUP,
681
+ ProvingRequestType.PRIVATE_BASE_ROLLUP,
682
+ ProvingRequestType.PUBLIC_VM,
683
+ ProvingRequestType.TUBE_PROOF,
684
+ ProvingRequestType.ROOT_PARITY,
685
+ ProvingRequestType.BASE_PARITY,
686
+ ProvingRequestType.EMPTY_BLOCK_ROOT_ROLLUP,
687
+ ];