@aztec/prover-client 0.0.1-commit.c31f2472 → 0.0.1-commit.c52d6e7
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 +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +16 -2
- package/dest/light/lightweight_checkpoint_builder.d.ts +12 -5
- package/dest/light/lightweight_checkpoint_builder.d.ts.map +1 -1
- package/dest/light/lightweight_checkpoint_builder.js +59 -23
- package/dest/mocks/fixtures.d.ts +1 -1
- package/dest/mocks/fixtures.d.ts.map +1 -1
- package/dest/mocks/fixtures.js +3 -3
- package/dest/mocks/test_context.d.ts +11 -11
- package/dest/mocks/test_context.d.ts.map +1 -1
- package/dest/mocks/test_context.js +31 -30
- package/dest/orchestrator/block-building-helpers.d.ts +4 -4
- package/dest/orchestrator/block-building-helpers.d.ts.map +1 -1
- package/dest/orchestrator/block-building-helpers.js +4 -4
- package/dest/orchestrator/block-proving-state.d.ts +4 -1
- package/dest/orchestrator/block-proving-state.d.ts.map +1 -1
- package/dest/orchestrator/block-proving-state.js +7 -0
- package/dest/orchestrator/checkpoint-proving-state.d.ts +20 -36
- package/dest/orchestrator/checkpoint-proving-state.d.ts.map +1 -1
- package/dest/orchestrator/checkpoint-proving-state.js +22 -120
- package/dest/orchestrator/checkpoint-sub-tree-orchestrator.d.ts +161 -0
- package/dest/orchestrator/checkpoint-sub-tree-orchestrator.d.ts.map +1 -0
- package/dest/orchestrator/{orchestrator.js → checkpoint-sub-tree-orchestrator.js} +285 -462
- package/dest/orchestrator/chonk-cache.d.ts +39 -0
- package/dest/orchestrator/chonk-cache.d.ts.map +1 -0
- package/dest/orchestrator/chonk-cache.js +80 -0
- package/dest/orchestrator/index.d.ts +4 -2
- package/dest/orchestrator/index.d.ts.map +1 -1
- package/dest/orchestrator/index.js +3 -1
- package/dest/orchestrator/proving-scheduler.d.ts +81 -0
- package/dest/orchestrator/proving-scheduler.d.ts.map +1 -0
- package/dest/orchestrator/proving-scheduler.js +120 -0
- package/dest/orchestrator/top-tree-orchestrator.d.ts +88 -0
- package/dest/orchestrator/top-tree-orchestrator.d.ts.map +1 -0
- package/dest/orchestrator/top-tree-orchestrator.js +232 -0
- package/dest/orchestrator/top-tree-proving-state.d.ts +61 -0
- package/dest/orchestrator/top-tree-proving-state.d.ts.map +1 -0
- package/dest/orchestrator/top-tree-proving-state.js +185 -0
- package/dest/orchestrator/tx-proving-state.d.ts +3 -3
- package/dest/orchestrator/tx-proving-state.d.ts.map +1 -1
- package/dest/prover-client/prover-client.d.ts +64 -7
- package/dest/prover-client/prover-client.d.ts.map +1 -1
- package/dest/prover-client/prover-client.js +62 -13
- package/dest/proving_broker/broker_prover_facade.d.ts +3 -3
- package/dest/proving_broker/broker_prover_facade.d.ts.map +1 -1
- package/dest/proving_broker/broker_prover_facade.js +26 -20
- package/dest/proving_broker/config.d.ts +11 -67
- package/dest/proving_broker/config.d.ts.map +1 -1
- package/dest/proving_broker/config.js +16 -5
- package/dest/proving_broker/index.d.ts +2 -1
- package/dest/proving_broker/index.d.ts.map +1 -1
- package/dest/proving_broker/index.js +1 -0
- package/dest/proving_broker/proof_store/factory.d.ts +2 -5
- package/dest/proving_broker/proof_store/factory.d.ts.map +1 -1
- package/dest/proving_broker/proof_store/factory.js +7 -30
- package/dest/proving_broker/proof_store/file_store_proof_store.d.ts +18 -0
- package/dest/proving_broker/proof_store/file_store_proof_store.d.ts.map +1 -0
- package/dest/proving_broker/proof_store/file_store_proof_store.js +60 -0
- package/dest/proving_broker/proof_store/index.d.ts +2 -2
- package/dest/proving_broker/proof_store/index.d.ts.map +1 -1
- package/dest/proving_broker/proof_store/index.js +1 -1
- package/dest/proving_broker/proving_broker.d.ts +9 -5
- package/dest/proving_broker/proving_broker.d.ts.map +1 -1
- package/dest/proving_broker/proving_broker.js +245 -70
- package/dest/proving_broker/proving_broker_database/memory.d.ts +5 -2
- package/dest/proving_broker/proving_broker_database/memory.d.ts.map +1 -1
- package/dest/proving_broker/proving_broker_database/memory.js +14 -1
- package/dest/proving_broker/proving_broker_database/persisted.d.ts +15 -2
- package/dest/proving_broker/proving_broker_database/persisted.d.ts.map +1 -1
- package/dest/proving_broker/proving_broker_database/persisted.js +57 -6
- package/dest/proving_broker/proving_broker_database.d.ts +27 -1
- package/dest/proving_broker/proving_broker_database.d.ts.map +1 -1
- package/dest/proving_broker/proving_broker_instrumentation.d.ts +3 -1
- package/dest/proving_broker/proving_broker_instrumentation.d.ts.map +1 -1
- package/dest/proving_broker/proving_broker_instrumentation.js +7 -0
- package/dest/proving_broker/rpc.d.ts +18 -5
- package/dest/proving_broker/rpc.d.ts.map +1 -1
- package/dest/proving_broker/rpc.js +98 -23
- package/dest/test/epoch_settlement.d.ts +36 -0
- package/dest/test/epoch_settlement.d.ts.map +1 -0
- package/dest/test/epoch_settlement.js +52 -0
- package/dest/test/index.d.ts +2 -0
- package/dest/test/index.d.ts.map +1 -0
- package/dest/test/index.js +1 -0
- package/dest/test/mock_prover.d.ts +4 -4
- package/dest/test/mock_prover.js +2 -2
- package/package.json +20 -20
- package/src/config.ts +18 -2
- package/src/light/lightweight_checkpoint_builder.ts +63 -25
- package/src/mocks/fixtures.ts +2 -4
- package/src/mocks/test_context.ts +27 -43
- package/src/orchestrator/block-building-helpers.ts +4 -4
- package/src/orchestrator/block-proving-state.ts +9 -0
- package/src/orchestrator/checkpoint-proving-state.ts +21 -162
- package/src/orchestrator/checkpoint-sub-tree-orchestrator.ts +1044 -0
- package/src/orchestrator/chonk-cache.ts +99 -0
- package/src/orchestrator/index.ts +8 -1
- package/src/orchestrator/proving-scheduler.ts +160 -0
- package/src/orchestrator/top-tree-orchestrator.ts +384 -0
- package/src/orchestrator/top-tree-proving-state.ts +219 -0
- package/src/orchestrator/tx-proving-state.ts +3 -3
- package/src/prover-client/prover-client.ts +134 -25
- package/src/proving_broker/broker_prover_facade.ts +29 -23
- package/src/proving_broker/config.ts +16 -2
- package/src/proving_broker/index.ts +1 -0
- package/src/proving_broker/proof_store/factory.ts +10 -32
- package/src/proving_broker/proof_store/file_store_proof_store.ts +78 -0
- package/src/proving_broker/proof_store/index.ts +1 -1
- package/src/proving_broker/proving_broker.ts +237 -72
- package/src/proving_broker/proving_broker_database/memory.ts +16 -2
- package/src/proving_broker/proving_broker_database/persisted.ts +71 -8
- package/src/proving_broker/proving_broker_database.ts +30 -0
- package/src/proving_broker/proving_broker_instrumentation.ts +9 -0
- package/src/proving_broker/rpc.ts +62 -20
- package/src/test/epoch_settlement.ts +82 -0
- package/src/test/index.ts +1 -0
- package/src/test/mock_prover.ts +2 -2
- package/dest/orchestrator/epoch-proving-state.d.ts +0 -75
- package/dest/orchestrator/epoch-proving-state.d.ts.map +0 -1
- package/dest/orchestrator/epoch-proving-state.js +0 -267
- package/dest/orchestrator/orchestrator.d.ts +0 -128
- package/dest/orchestrator/orchestrator.d.ts.map +0 -1
- package/dest/prover-client/server-epoch-prover.d.ts +0 -32
- package/dest/prover-client/server-epoch-prover.d.ts.map +0 -1
- package/dest/prover-client/server-epoch-prover.js +0 -40
- package/dest/proving_broker/proof_store/gcs_proof_store.d.ts +0 -14
- package/dest/proving_broker/proof_store/gcs_proof_store.d.ts.map +0 -1
- package/dest/proving_broker/proof_store/gcs_proof_store.js +0 -52
- package/src/orchestrator/epoch-proving-state.ts +0 -378
- package/src/orchestrator/orchestrator.ts +0 -1281
- package/src/prover-client/server-epoch-prover.ts +0 -69
- package/src/proving_broker/proof_store/gcs_proof_store.ts +0 -76
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type GetProvingJobResponse,
|
|
8
8
|
type ProofUri,
|
|
9
9
|
type ProvingJob,
|
|
10
|
+
type ProvingJobBrokerDebug,
|
|
10
11
|
type ProvingJobConsumer,
|
|
11
12
|
type ProvingJobFilter,
|
|
12
13
|
type ProvingJobId,
|
|
@@ -32,11 +33,28 @@ type InProgressMetadata = {
|
|
|
32
33
|
|
|
33
34
|
type EnqueuedProvingJob = Pick<ProvingJob, 'id' | 'epochNumber'>;
|
|
34
35
|
|
|
36
|
+
/** In-memory scheduling metadata for a job. The large `inputsUri` is deliberately excluded — it lives
|
|
37
|
+
* in the database and is read on demand when dispatching to an agent. */
|
|
38
|
+
type ProvingJobMetadata = Pick<ProvingJob, 'id' | 'type' | 'epochNumber'>;
|
|
39
|
+
|
|
40
|
+
/** Settled state kept in memory: the status plus the small payloads (rejected reason, aborted). The
|
|
41
|
+
* large fulfilled proof `value` is NOT kept here — it lives in the database (read on demand) and,
|
|
42
|
+
* transiently, in `pendingResults` while its database write is in flight. */
|
|
43
|
+
type SettledRecord = { status: 'fulfilled' } | { status: 'rejected'; reason: string } | { status: 'aborted' };
|
|
44
|
+
|
|
45
|
+
function toJobMetadata(job: ProvingJob): ProvingJobMetadata {
|
|
46
|
+
return { id: job.id, type: job.type, epochNumber: job.epochNumber };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function toSettledRecord(result: ProvingJobSettledResult): SettledRecord {
|
|
50
|
+
return result.status === 'fulfilled' ? { status: 'fulfilled' } : result;
|
|
51
|
+
}
|
|
52
|
+
|
|
35
53
|
/**
|
|
36
54
|
* A broker that manages proof requests and distributes them to workers based on their priority.
|
|
37
55
|
* It takes a backend that is responsible for storing and retrieving proof requests and results.
|
|
38
56
|
*/
|
|
39
|
-
export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Traceable {
|
|
57
|
+
export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, ProvingJobBrokerDebug, Traceable {
|
|
40
58
|
private queues: ProvingQueues = {
|
|
41
59
|
[ProvingRequestType.PUBLIC_VM]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
|
|
42
60
|
[ProvingRequestType.PUBLIC_CHONK_VERIFIER]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
|
|
@@ -68,11 +86,17 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
68
86
|
[ProvingRequestType.PARITY_ROOT]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
|
|
69
87
|
};
|
|
70
88
|
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
private jobsCache = new Map<ProvingJobId,
|
|
74
|
-
//
|
|
75
|
-
|
|
89
|
+
// Scheduling metadata for every known job (id, type, epochNumber). The large `inputsUri` is NOT held
|
|
90
|
+
// here — it lives in the database and is read on demand when a job is dispatched to an agent.
|
|
91
|
+
private jobsCache = new Map<ProvingJobId, ProvingJobMetadata>();
|
|
92
|
+
// Settled status for every settled job. The large fulfilled proof `value` is NOT held here — it lives
|
|
93
|
+
// in the database (read on demand in getProvingJobStatus) and, transiently, in `pendingResults` while
|
|
94
|
+
// its database write is in flight. Small payloads (rejected reason, aborted) are kept inline.
|
|
95
|
+
private resultsCache = new Map<ProvingJobId, SettledRecord>();
|
|
96
|
+
// Transient hold of a fulfilled result's full value, from the moment it settles until its database
|
|
97
|
+
// write commits, so status reads are read-your-writes without retaining every proof. Evicted once the
|
|
98
|
+
// value is durable in the database; kept only if that write failed (preserving the result in memory).
|
|
99
|
+
private pendingResults = new Map<ProvingJobId, ProvingJobSettledResult>();
|
|
76
100
|
|
|
77
101
|
// tracks when each job was enqueued
|
|
78
102
|
private enqueuedAt = new Map<ProvingJobId, Timer>();
|
|
@@ -86,8 +110,9 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
86
110
|
// keep track of which proving job has been retried
|
|
87
111
|
private retries = new Map<ProvingJobId, number>();
|
|
88
112
|
|
|
89
|
-
// a map of promises that
|
|
90
|
-
|
|
113
|
+
// a map of promises that are resolved when a job settles. Carries no payload (settled status/results
|
|
114
|
+
// are read from resultsCache/pendingResults/DB) so it never pins a proof value in memory.
|
|
115
|
+
private promises = new Map<ProvingJobId, PromiseWithResolvers<void>>();
|
|
91
116
|
|
|
92
117
|
private cleanupPromise: RunningPromise;
|
|
93
118
|
private msTimeSource = () => Date.now();
|
|
@@ -114,6 +139,8 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
114
139
|
|
|
115
140
|
private started = false;
|
|
116
141
|
|
|
142
|
+
private debugReplayEnabled: boolean;
|
|
143
|
+
|
|
117
144
|
public constructor(
|
|
118
145
|
private database: ProvingBrokerDatabase,
|
|
119
146
|
{
|
|
@@ -121,6 +148,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
121
148
|
proverBrokerPollIntervalMs,
|
|
122
149
|
proverBrokerJobMaxRetries,
|
|
123
150
|
proverBrokerMaxEpochsToKeepResultsFor,
|
|
151
|
+
proverBrokerDebugReplayEnabled,
|
|
124
152
|
}: Required<
|
|
125
153
|
Pick<
|
|
126
154
|
ProverBrokerConfig,
|
|
@@ -128,6 +156,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
128
156
|
| 'proverBrokerPollIntervalMs'
|
|
129
157
|
| 'proverBrokerJobMaxRetries'
|
|
130
158
|
| 'proverBrokerMaxEpochsToKeepResultsFor'
|
|
159
|
+
| 'proverBrokerDebugReplayEnabled'
|
|
131
160
|
>
|
|
132
161
|
> = defaultProverBrokerConfig,
|
|
133
162
|
client: TelemetryClient = getTelemetryClient(),
|
|
@@ -139,6 +168,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
139
168
|
this.jobTimeoutMs = proverBrokerJobTimeoutMs!;
|
|
140
169
|
this.maxRetries = proverBrokerJobMaxRetries!;
|
|
141
170
|
this.maxEpochsToKeepResultsFor = proverBrokerMaxEpochsToKeepResultsFor!;
|
|
171
|
+
this.debugReplayEnabled = proverBrokerDebugReplayEnabled ?? false;
|
|
142
172
|
}
|
|
143
173
|
|
|
144
174
|
private measureQueueDepth: MonitorCallback = (type: ProvingRequestType) => {
|
|
@@ -169,12 +199,12 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
169
199
|
status: result ? result.status : 'pending',
|
|
170
200
|
});
|
|
171
201
|
|
|
172
|
-
this.jobsCache.set(item.id, item);
|
|
202
|
+
this.jobsCache.set(item.id, toJobMetadata(item));
|
|
173
203
|
this.promises.set(item.id, promiseWithResolvers());
|
|
174
204
|
|
|
175
205
|
if (result) {
|
|
176
|
-
this.promises.get(item.id)!.resolve(
|
|
177
|
-
this.resultsCache.set(item.id, result);
|
|
206
|
+
this.promises.get(item.id)!.resolve();
|
|
207
|
+
this.resultsCache.set(item.id, toSettledRecord(result));
|
|
178
208
|
} else {
|
|
179
209
|
this.enqueueJobInternal(item);
|
|
180
210
|
}
|
|
@@ -205,7 +235,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
205
235
|
}
|
|
206
236
|
|
|
207
237
|
public getProvingJobStatus(id: ProvingJobId): Promise<ProvingJobStatus> {
|
|
208
|
-
return
|
|
238
|
+
return this.#getProvingJobStatus(id);
|
|
209
239
|
}
|
|
210
240
|
|
|
211
241
|
public getCompletedJobs(ids: ProvingJobId[]): Promise<ProvingJobId[]> {
|
|
@@ -213,7 +243,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
213
243
|
}
|
|
214
244
|
|
|
215
245
|
public getProvingJob(filter?: ProvingJobFilter): Promise<GetProvingJobResponse | undefined> {
|
|
216
|
-
return
|
|
246
|
+
return this.#getProvingJob(filter);
|
|
217
247
|
}
|
|
218
248
|
|
|
219
249
|
public reportProvingJobSuccess(
|
|
@@ -238,20 +268,79 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
238
268
|
startedAt: number,
|
|
239
269
|
filter?: ProvingJobFilter,
|
|
240
270
|
): Promise<{ job: ProvingJob; time: number } | undefined> {
|
|
241
|
-
return
|
|
271
|
+
return this.#reportProvingJobProgress(id, startedAt, filter);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
public async replayProvingJob(
|
|
275
|
+
jobId: ProvingJobId,
|
|
276
|
+
type: ProvingRequestType,
|
|
277
|
+
epochNumber: EpochNumber,
|
|
278
|
+
inputsUri: ProofUri,
|
|
279
|
+
): Promise<ProvingJobStatus> {
|
|
280
|
+
if (!this.debugReplayEnabled) {
|
|
281
|
+
throw new Error('Debug replay not enabled. Set PROVER_BROKER_DEBUG_REPLAY_ENABLED=true');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
this.logger.info(`Replaying proving job`, { provingJobId: jobId, epochNumber, inputsUri });
|
|
285
|
+
|
|
286
|
+
// Clear existing state and enqueue
|
|
287
|
+
this.cleanUpProvingJobState([jobId]);
|
|
288
|
+
|
|
289
|
+
const job: ProvingJob = { id: jobId, type, epochNumber, inputsUri };
|
|
290
|
+
this.jobsCache.set(jobId, toJobMetadata(job));
|
|
291
|
+
await this.database.addProvingJob(job);
|
|
292
|
+
this.enqueueJobInternal(job);
|
|
293
|
+
|
|
294
|
+
return { status: 'in-queue' };
|
|
242
295
|
}
|
|
243
296
|
|
|
244
297
|
async #enqueueProvingJob(job: ProvingJob): Promise<ProvingJobStatus> {
|
|
245
|
-
//
|
|
246
|
-
|
|
298
|
+
// The status returned to the caller reflects the job's state at the start of this call: `not-found`
|
|
299
|
+
// for a brand-new job (it did not exist yet), or the cached status for one already known. Crucially
|
|
300
|
+
// this must NOT `await` before the synchronous `jobsCache`/`resultsCache` gate below, or the
|
|
301
|
+
// enqueue/revive lock would no longer hold. So the not-found default is set synchronously here and
|
|
302
|
+
// the cached branch computes the real status (which may read the DB) only when it returns.
|
|
303
|
+
let jobStatus: ProvingJobStatus = { status: 'not-found' };
|
|
247
304
|
if (this.jobsCache.has(job.id)) {
|
|
248
305
|
const existing = this.jobsCache.get(job.id);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
this.
|
|
254
|
-
|
|
306
|
+
// Identity check is metadata-only: `inputsUri` now lives in the database, not memory, and job ids
|
|
307
|
+
// are content-addressed (same id ⇒ same inputs), so a mismatch shows up in id/type/epochNumber.
|
|
308
|
+
assert.deepStrictEqual(toJobMetadata(job), existing, 'Duplicate proving job ID');
|
|
309
|
+
|
|
310
|
+
if (this.resultsCache.get(job.id)?.status === 'aborted') {
|
|
311
|
+
// The producer is re-requesting a job it previously cancelled: revive it rather than
|
|
312
|
+
// returning the cached abort, clearing the aborted state in memory and in the database so the
|
|
313
|
+
// revival survives a restart.
|
|
314
|
+
//
|
|
315
|
+
// Concurrency model: `jobsCache` is the enqueue lock. Every path that puts a job on the queue
|
|
316
|
+
// populates `jobsCache` *synchronously, before its first await* (see the "New proving job"
|
|
317
|
+
// block below), so a second concurrent enqueue of the same id observes the entry at the top
|
|
318
|
+
// of this method and takes a cached, no-op branch instead of enqueuing a duplicate. The revive
|
|
319
|
+
// must keep holding that lock: we tear down the settled state and re-set `jobsCache` in a
|
|
320
|
+
// single synchronous span (no await in between), and only then await the database. Because a
|
|
321
|
+
// concurrent re-request can only interleave at that await — by which point `jobsCache` is
|
|
322
|
+
// populated again and the aborted result is gone — it falls into the cached branch and no-ops,
|
|
323
|
+
// so the job is enqueued exactly once. (`cleanUpProvingJobState` also drops the promise, so
|
|
324
|
+
// `enqueueJobInternal` below mints a fresh one for the retry.) This holds unchanged with the
|
|
325
|
+
// slimmer caches: `jobsCache` (now metadata) and `resultsCache` (now settled status) are still
|
|
326
|
+
// synchronous in-memory maps, so the tear-down + re-set span still contains no await.
|
|
327
|
+
this.logger.info(`Reviving aborted proving job id=${job.id} epochNumber=${job.epochNumber}`, {
|
|
328
|
+
provingJobId: job.id,
|
|
329
|
+
});
|
|
330
|
+
this.cleanUpProvingJobState([job.id]);
|
|
331
|
+
this.jobsCache.set(job.id, toJobMetadata(job));
|
|
332
|
+
await this.database.deleteProvingJobResult(job.id);
|
|
333
|
+
// The job is re-set in the cache and about to be re-enqueued below: its start status is in-queue.
|
|
334
|
+
jobStatus = { status: 'in-queue' };
|
|
335
|
+
} else {
|
|
336
|
+
this.logger.warn(`Cached proving job id=${job.id} epochNumber=${job.epochNumber}. Not enqueuing again`, {
|
|
337
|
+
provingJobId: job.id,
|
|
338
|
+
});
|
|
339
|
+
this.instrumentation.incCachedJobs(job.type);
|
|
340
|
+
// Return the job's current status. This reads the fulfilled proof value from the DB when needed;
|
|
341
|
+
// the await is fine here because this branch does not enqueue, so it is outside the lock span.
|
|
342
|
+
return await this.#getProvingJobStatus(job.id);
|
|
343
|
+
}
|
|
255
344
|
}
|
|
256
345
|
|
|
257
346
|
if (this.isJobStale(job)) {
|
|
@@ -264,7 +353,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
264
353
|
this.logger.info(`New proving job id=${job.id} epochNumber=${job.epochNumber}`, { provingJobId: job.id });
|
|
265
354
|
try {
|
|
266
355
|
// 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.
|
|
267
|
-
this.jobsCache.set(job.id, job);
|
|
356
|
+
this.jobsCache.set(job.id, toJobMetadata(job));
|
|
268
357
|
await this.database.addProvingJob(job);
|
|
269
358
|
this.enqueueJobInternal(job);
|
|
270
359
|
this.instrumentation.incTotalJobs(job.type);
|
|
@@ -277,43 +366,80 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
277
366
|
}
|
|
278
367
|
|
|
279
368
|
async #cancelProvingJob(id: ProvingJobId): Promise<void> {
|
|
280
|
-
|
|
369
|
+
const job = this.jobsCache.get(id);
|
|
370
|
+
if (!job) {
|
|
281
371
|
this.logger.warn(`Can't cancel a job that doesn't exist id=${id}`, { provingJobId: id });
|
|
282
372
|
return;
|
|
283
373
|
}
|
|
284
374
|
|
|
285
|
-
//
|
|
286
|
-
if (
|
|
287
|
-
|
|
288
|
-
|
|
375
|
+
// Leave jobs that have already settled (completed or failed) alone: those results are terminal.
|
|
376
|
+
if (this.resultsCache.has(id)) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
this.logger.info(`Cancelling job id=${id}`, { provingJobId: id });
|
|
381
|
+
this.inProgress.delete(id);
|
|
382
|
+
|
|
383
|
+
// Record the cancellation as its own settled state and persist it, so it survives a restart and
|
|
384
|
+
// notifies the current waiter. Unlike a completion or failure this is not terminal: re-enqueuing
|
|
385
|
+
// the same job id revives it (see #enqueueProvingJob), so the abort never permanently blocks the
|
|
386
|
+
// proof.
|
|
387
|
+
this.resultsCache.set(id, { status: 'aborted' });
|
|
388
|
+
this.promises.get(id)?.resolve();
|
|
389
|
+
this.completedJobNotifications.push(id);
|
|
390
|
+
this.instrumentation.incAbortedJobs(job.type);
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
await this.database.setProvingJobAborted(id);
|
|
394
|
+
} catch (saveErr) {
|
|
395
|
+
this.logger.error(`Failed to save proving job aborted status id=${id}`, saveErr, { provingJobId: id });
|
|
396
|
+
throw saveErr;
|
|
289
397
|
}
|
|
290
398
|
}
|
|
291
399
|
|
|
292
400
|
private cleanUpProvingJobState(ids: ProvingJobId[]) {
|
|
401
|
+
const idsToClean = new Set(ids);
|
|
293
402
|
for (const id of ids) {
|
|
294
403
|
this.jobsCache.delete(id);
|
|
404
|
+
const deferred = this.promises.get(id);
|
|
405
|
+
if (deferred) {
|
|
406
|
+
deferred.resolve();
|
|
407
|
+
}
|
|
295
408
|
this.promises.delete(id);
|
|
296
409
|
this.resultsCache.delete(id);
|
|
410
|
+
this.pendingResults.delete(id);
|
|
297
411
|
this.inProgress.delete(id);
|
|
298
412
|
this.retries.delete(id);
|
|
299
413
|
this.enqueuedAt.delete(id);
|
|
300
414
|
}
|
|
415
|
+
this.completedJobNotifications = this.completedJobNotifications.filter(id => !idsToClean.has(id));
|
|
301
416
|
}
|
|
302
417
|
|
|
303
|
-
#getProvingJobStatus(id: ProvingJobId): ProvingJobStatus {
|
|
304
|
-
const
|
|
305
|
-
if (
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const item = this.jobsCache.get(id);
|
|
310
|
-
|
|
311
|
-
if (!item) {
|
|
312
|
-
return { status: 'not-found' };
|
|
418
|
+
async #getProvingJobStatus(id: ProvingJobId): Promise<ProvingJobStatus> {
|
|
419
|
+
const settled = this.resultsCache.get(id);
|
|
420
|
+
if (settled) {
|
|
421
|
+
if (settled.status !== 'fulfilled') {
|
|
422
|
+
// rejected/aborted carry their (small) payload inline.
|
|
423
|
+
return settled;
|
|
313
424
|
}
|
|
425
|
+
// A fulfilled job's proof value is not held in memory: serve it from the transient hold if its
|
|
426
|
+
// database write is still in flight, otherwise read it back from the database.
|
|
427
|
+
const full = this.pendingResults.get(id) ?? (await this.database.getProvingJobResult(id));
|
|
428
|
+
if (full?.status === 'fulfilled') {
|
|
429
|
+
return full;
|
|
430
|
+
}
|
|
431
|
+
// Settled as fulfilled but the value is nowhere to be found — should not happen (it is held in
|
|
432
|
+
// pendingResults until its DB write commits). Report not-found rather than a torn result.
|
|
433
|
+
this.logger.error(`Fulfilled proving job id=${id} has no retrievable result value`, { provingJobId: id });
|
|
434
|
+
return { status: 'not-found' };
|
|
435
|
+
}
|
|
314
436
|
|
|
315
|
-
|
|
437
|
+
// no result yet, check if we know the item
|
|
438
|
+
const item = this.jobsCache.get(id);
|
|
439
|
+
if (!item) {
|
|
440
|
+
return { status: 'not-found' };
|
|
316
441
|
}
|
|
442
|
+
return { status: this.inProgress.has(id) ? 'in-progress' : 'in-queue' };
|
|
317
443
|
}
|
|
318
444
|
|
|
319
445
|
#getCompletedJobs(ids: ProvingJobId[]): Promise<ProvingJobId[]> {
|
|
@@ -323,7 +449,9 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
323
449
|
return Promise.resolve(notifications.concat(completedJobs));
|
|
324
450
|
}
|
|
325
451
|
|
|
326
|
-
#getProvingJob(
|
|
452
|
+
async #getProvingJob(
|
|
453
|
+
filter: ProvingJobFilter = { allowList: [] },
|
|
454
|
+
): Promise<{ job: ProvingJob; time: number } | undefined> {
|
|
327
455
|
const allowedProofs: ProvingRequestType[] =
|
|
328
456
|
Array.isArray(filter.allowList) && filter.allowList.length > 0
|
|
329
457
|
? [...filter.allowList]
|
|
@@ -338,21 +466,40 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
338
466
|
// this can happen if the broker crashes and restarts
|
|
339
467
|
// it's possible agents will report progress or results for jobs that are in the queue (after the restart)
|
|
340
468
|
while ((enqueuedJob = queue.getImmediate())) {
|
|
341
|
-
const
|
|
342
|
-
if (
|
|
469
|
+
const meta = this.jobsCache.get(enqueuedJob.id);
|
|
470
|
+
if (meta && !this.inProgress.has(enqueuedJob.id) && !this.resultsCache.has(enqueuedJob.id)) {
|
|
343
471
|
const time = this.msTimeSource();
|
|
344
|
-
|
|
345
|
-
|
|
472
|
+
// Claim the job synchronously (before the await below) so a concurrent dispatch can't re-pick
|
|
473
|
+
// it. The same id can sit in a queue more than once (an abort leaves a stale entry that a
|
|
474
|
+
// later revive re-enqueues alongside), so this in-progress claim — not the queue pop — is what
|
|
475
|
+
// dedups those copies across concurrently-polling agents. It must be set before the await.
|
|
476
|
+
this.inProgress.set(meta.id, {
|
|
477
|
+
id: meta.id,
|
|
346
478
|
startedAt: time,
|
|
347
479
|
lastUpdatedAt: time,
|
|
348
480
|
});
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
481
|
+
|
|
482
|
+
// The large inputs are not kept in memory; read them from the database. They were durably
|
|
483
|
+
// persisted (addProvingJob's write commits) before the job became dispatchable, so this hits.
|
|
484
|
+
const inputsUri = await this.database.getProvingJobInputs(meta.id);
|
|
485
|
+
if (!inputsUri) {
|
|
486
|
+
// The job was cleaned up (or its inputs lost) after we claimed it — release the claim and
|
|
487
|
+
// keep draining the queue for another candidate. Nothing else was mutated yet, so there is
|
|
488
|
+
// no wait metric or enqueued-at timer to unwind.
|
|
489
|
+
this.inProgress.delete(meta.id);
|
|
490
|
+
this.logger.warn(`No inputs found for proving job id=${meta.id}; skipping dispatch`, {
|
|
491
|
+
provingJobId: meta.id,
|
|
492
|
+
});
|
|
493
|
+
continue;
|
|
354
494
|
}
|
|
355
495
|
|
|
496
|
+
// The dispatch is now committed: record the queue wait and clear the enqueued-at timer.
|
|
497
|
+
const enqueuedAt = this.enqueuedAt.get(meta.id);
|
|
498
|
+
if (enqueuedAt) {
|
|
499
|
+
this.instrumentation.recordJobWait(meta.type, enqueuedAt);
|
|
500
|
+
this.enqueuedAt.delete(meta.id);
|
|
501
|
+
}
|
|
502
|
+
const job: ProvingJob = { id: meta.id, type: meta.type, epochNumber: meta.epochNumber, inputsUri };
|
|
356
503
|
return { job, time };
|
|
357
504
|
}
|
|
358
505
|
}
|
|
@@ -419,11 +566,10 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
419
566
|
},
|
|
420
567
|
);
|
|
421
568
|
|
|
422
|
-
// save the
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
this.
|
|
426
|
-
this.promises.get(id)!.resolve(result);
|
|
569
|
+
// save the (small) rejection reason to the cache and notify clients of the job status. The reason
|
|
570
|
+
// is kept in memory, so status reads work even if the database write below fails.
|
|
571
|
+
this.resultsCache.set(id, { status: 'rejected', reason: String(err) });
|
|
572
|
+
this.promises.get(id)!.resolve();
|
|
427
573
|
this.completedJobNotifications.push(id);
|
|
428
574
|
|
|
429
575
|
this.instrumentation.incRejectedJobs(item.type);
|
|
@@ -445,20 +591,20 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
445
591
|
return this.#getProvingJob(filter);
|
|
446
592
|
}
|
|
447
593
|
|
|
448
|
-
#reportProvingJobProgress(
|
|
594
|
+
async #reportProvingJobProgress(
|
|
449
595
|
id: ProvingJobId,
|
|
450
596
|
startedAt: number,
|
|
451
597
|
filter?: ProvingJobFilter,
|
|
452
|
-
): { job: ProvingJob; time: number } | undefined {
|
|
598
|
+
): Promise<{ job: ProvingJob; time: number } | undefined> {
|
|
453
599
|
const job = this.jobsCache.get(id);
|
|
454
600
|
if (!job) {
|
|
455
601
|
this.logger.warn(`Proving job id=${id} does not exist`, { provingJobId: id });
|
|
456
|
-
return this.#getProvingJob(filter);
|
|
602
|
+
return await this.#getProvingJob(filter);
|
|
457
603
|
}
|
|
458
604
|
|
|
459
605
|
if (this.resultsCache.has(id)) {
|
|
460
606
|
this.logger.warn(`Proving job id=${id} has already been completed`, { provingJobId: id });
|
|
461
|
-
return this.#getProvingJob(filter);
|
|
607
|
+
return await this.#getProvingJob(filter);
|
|
462
608
|
}
|
|
463
609
|
|
|
464
610
|
const metadata = this.inProgress.get(id);
|
|
@@ -498,7 +644,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
498
644
|
{ provingJobId: id },
|
|
499
645
|
);
|
|
500
646
|
|
|
501
|
-
return this.#getProvingJob(filter);
|
|
647
|
+
return await this.#getProvingJob(filter);
|
|
502
648
|
}
|
|
503
649
|
|
|
504
650
|
async #reportProvingJobSuccess(
|
|
@@ -532,12 +678,12 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
532
678
|
{ provingJobId: id },
|
|
533
679
|
);
|
|
534
680
|
|
|
535
|
-
//
|
|
536
|
-
//
|
|
537
|
-
//
|
|
538
|
-
|
|
539
|
-
this.
|
|
540
|
-
this.promises.get(id)!.resolve(
|
|
681
|
+
// Mark settled synchronously (the gate that makes concurrent settles no-op, unchanged from before),
|
|
682
|
+
// holding only the status in resultsCache. The large proof value is kept transiently in
|
|
683
|
+
// pendingResults so status reads are read-your-writes until the database write below commits.
|
|
684
|
+
this.resultsCache.set(id, { status: 'fulfilled' });
|
|
685
|
+
this.pendingResults.set(id, { status: 'fulfilled', value });
|
|
686
|
+
this.promises.get(id)!.resolve();
|
|
541
687
|
this.completedJobNotifications.push(id);
|
|
542
688
|
|
|
543
689
|
this.instrumentation.incResolvedJobs(item.type);
|
|
@@ -548,7 +694,11 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
548
694
|
|
|
549
695
|
try {
|
|
550
696
|
await this.database.setProvingJobResult(id, value);
|
|
697
|
+
// The value is durable in the database now; drop the transient in-memory copy.
|
|
698
|
+
this.pendingResults.delete(id);
|
|
551
699
|
} catch (saveErr) {
|
|
700
|
+
// Keep the value in pendingResults so status/result reads still succeed despite the failed write —
|
|
701
|
+
// this preserves the previous "works even if the database breaks" behaviour for this edge.
|
|
552
702
|
this.logger.error(`Failed to save proving job result id=${id}`, saveErr, {
|
|
553
703
|
provingJobId: id,
|
|
554
704
|
});
|
|
@@ -560,21 +710,21 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
560
710
|
}
|
|
561
711
|
|
|
562
712
|
private async cleanupPass() {
|
|
563
|
-
this.cleanupStaleJobs();
|
|
564
713
|
this.reEnqueueExpiredJobs();
|
|
565
714
|
const oldestEpochToKeep = this.oldestEpochToKeep();
|
|
566
715
|
if (oldestEpochToKeep > 0) {
|
|
716
|
+
this.cleanupJobsOlderThanEpoch(EpochNumber(oldestEpochToKeep));
|
|
567
717
|
await this.database.deleteAllProvingJobsOlderThanEpoch(EpochNumber(oldestEpochToKeep));
|
|
568
718
|
this.logger.trace(`Deleted all epochs older than ${oldestEpochToKeep}`);
|
|
569
719
|
}
|
|
570
720
|
}
|
|
571
721
|
|
|
572
|
-
private
|
|
722
|
+
private cleanupJobsOlderThanEpoch(epochNumber: EpochNumber) {
|
|
573
723
|
const jobIds = Array.from(this.jobsCache.keys());
|
|
574
724
|
const jobsToClean: ProvingJobId[] = [];
|
|
575
725
|
for (const id of jobIds) {
|
|
576
726
|
const job = this.jobsCache.get(id)!;
|
|
577
|
-
if (
|
|
727
|
+
if (job.epochNumber < epochNumber) {
|
|
578
728
|
jobsToClean.push(id);
|
|
579
729
|
}
|
|
580
730
|
}
|
|
@@ -598,15 +748,30 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
598
748
|
const now = this.msTimeSource();
|
|
599
749
|
const msSinceLastUpdate = now - metadata.lastUpdatedAt;
|
|
600
750
|
if (msSinceLastUpdate >= this.jobTimeoutMs) {
|
|
601
|
-
this.logger.warn(`Proving job id=${id} timed out. Adding it back to the queue.`, { provingJobId: id });
|
|
602
751
|
this.inProgress.delete(id);
|
|
603
|
-
this.enqueueJobInternal(item);
|
|
604
752
|
this.instrumentation.incTimedOutJobs(item.type);
|
|
753
|
+
|
|
754
|
+
const retries = this.retries.get(id) ?? 0;
|
|
755
|
+
if (retries + 1 < this.maxRetries && !this.isJobStale(item)) {
|
|
756
|
+
this.logger.warn(`Proving job id=${id} timed out. Re-enqueueing (retry ${retries + 1}/${this.maxRetries}).`, {
|
|
757
|
+
provingJobId: id,
|
|
758
|
+
});
|
|
759
|
+
this.retries.set(id, retries + 1);
|
|
760
|
+
this.enqueueJobInternal(item);
|
|
761
|
+
} else {
|
|
762
|
+
this.logger.error(`Proving job id=${id} timed out after ${retries + 1} attempts. Marking as failed.`, {
|
|
763
|
+
provingJobId: id,
|
|
764
|
+
});
|
|
765
|
+
this.resultsCache.set(id, { status: 'rejected', reason: 'Timed out' });
|
|
766
|
+
this.promises.get(id)?.resolve();
|
|
767
|
+
this.completedJobNotifications.push(id);
|
|
768
|
+
this.instrumentation.incRejectedJobs(item.type);
|
|
769
|
+
}
|
|
605
770
|
}
|
|
606
771
|
}
|
|
607
772
|
}
|
|
608
773
|
|
|
609
|
-
private enqueueJobInternal(job:
|
|
774
|
+
private enqueueJobInternal(job: ProvingJobMetadata): void {
|
|
610
775
|
if (!this.promises.has(job.id)) {
|
|
611
776
|
this.promises.set(job.id, promiseWithResolvers());
|
|
612
777
|
}
|
|
@@ -618,7 +783,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Tr
|
|
|
618
783
|
this.epochHeight = Math.max(this.epochHeight, job.epochNumber);
|
|
619
784
|
}
|
|
620
785
|
|
|
621
|
-
private isJobStale(job:
|
|
786
|
+
private isJobStale(job: ProvingJobMetadata) {
|
|
622
787
|
return job.epochNumber < this.oldestEpochToKeep();
|
|
623
788
|
}
|
|
624
789
|
|
|
@@ -17,8 +17,12 @@ export class InMemoryBrokerDatabase implements ProvingBrokerDatabase {
|
|
|
17
17
|
return this.jobs.get(id);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
return this.
|
|
20
|
+
getProvingJobInputs(id: ProvingJobId): Promise<ProofUri | undefined> {
|
|
21
|
+
return Promise.resolve(this.jobs.get(id)?.inputsUri);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getProvingJobResult(id: ProvingJobId): Promise<ProvingJobSettledResult | undefined> {
|
|
25
|
+
return Promise.resolve(this.results.get(id));
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
addProvingJob(job: ProvingJob): Promise<void> {
|
|
@@ -36,6 +40,16 @@ export class InMemoryBrokerDatabase implements ProvingBrokerDatabase {
|
|
|
36
40
|
return Promise.resolve();
|
|
37
41
|
}
|
|
38
42
|
|
|
43
|
+
setProvingJobAborted(id: ProvingJobId): Promise<void> {
|
|
44
|
+
this.results.set(id, { status: 'aborted' });
|
|
45
|
+
return Promise.resolve();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
deleteProvingJobResult(id: ProvingJobId): Promise<void> {
|
|
49
|
+
this.results.delete(id);
|
|
50
|
+
return Promise.resolve();
|
|
51
|
+
}
|
|
52
|
+
|
|
39
53
|
deleteProvingJobs(ids: ProvingJobId[]): Promise<void> {
|
|
40
54
|
for (const id of ids) {
|
|
41
55
|
this.jobs.delete(id);
|