Skip to content

Commit fd4eda7

Browse files
committed
fix(fast-inbox): schedule proving jobs oldest-epoch-first across proof types (A-1427)
The legacy L1-to-L2 tree gated block roots on parity outputs, so the broker's type-major priority still progressed one epoch at a time. The streaming inbox parity only gates the checkpoint root: under sustained block production the type-major order kept serving younger epochs' block roots and never scheduled INBOX_PARITY, stalling every epoch at awaiting-root. Select the oldest-epoch job across the allowed queues and tie-break by proof-type priority.
1 parent 9a9c5f3 commit fd4eda7

2 files changed

Lines changed: 62 additions & 15 deletions

File tree

yarn-project/prover-client/src/proving_broker/proving_broker.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,29 @@ describe.each([
535535
);
536536
});
537537

538+
it('prefers an older epoch over a higher-priority proof type from a younger epoch', async () => {
539+
const baseRollup2 = makeRandomProvingJobId();
540+
await broker.enqueueProvingJob({
541+
id: baseRollup2,
542+
type: ProvingRequestType.PRIVATE_TX_BASE_ROLLUP,
543+
epochNumber: EpochNumber(2),
544+
inputsUri: makeInputsUri(),
545+
});
546+
547+
const publicVm1 = makeRandomProvingJobId();
548+
await broker.enqueueProvingJob({
549+
id: publicVm1,
550+
type: ProvingRequestType.PUBLIC_VM,
551+
epochNumber: EpochNumber(1),
552+
inputsUri: makeInputsUri(),
553+
});
554+
555+
// A lower-priority type from epoch 1 wins over the higher-priority type from epoch 2: the oldest
556+
// epoch's remaining jobs must complete rather than starve behind newer epochs' work.
557+
await getAndAssertNextJobId(publicVm1, ProvingRequestType.PUBLIC_VM, ProvingRequestType.PRIVATE_TX_BASE_ROLLUP);
558+
await getAndAssertNextJobId(baseRollup2, ProvingRequestType.PUBLIC_VM, ProvingRequestType.PRIVATE_TX_BASE_ROLLUP);
559+
});
560+
538561
it('returns any job if filter is empty', async () => {
539562
const baseParity1 = makeRandomProvingJobId();
540563
await broker.enqueueProvingJob({

yarn-project/prover-client/src/proving_broker/proving_broker.ts

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -410,35 +410,59 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
410410
: Object.values(ProvingRequestType).filter((x): x is ProvingRequestType => typeof x === 'number');
411411
allowedProofs.sort(proofTypeComparator);
412412

413+
// Select the oldest-epoch job across the allowed queues, tie-breaking by proof-type priority: an epoch's
414+
// remaining jobs always outrank younger epochs' work, so the oldest epoch completes instead of starving
415+
// behind the continuous arrival of new higher-priority-type jobs. The legacy L1-to-L2 tree got this ordering
416+
// for free (block roots waited on parity outputs); the streaming inbox parity only gates the checkpoint
417+
// root, so a purely type-major order would leave it unscheduled under sustained block production.
418+
let selected: { proofType: ProvingRequestType; enqueuedJob: EnqueuedProvingJob; job: ProvingJob } | undefined;
413419
for (const proofType of allowedProofs) {
414420
const queue = this.queues[proofType];
415421
let enqueuedJob: EnqueuedProvingJob | undefined;
422+
let candidate: { enqueuedJob: EnqueuedProvingJob; job: ProvingJob } | undefined;
416423
// exhaust the queue and make sure we're not sending a job that's already in progress
417424
// or has already been completed
418425
// this can happen if the broker crashes and restarts
419426
// it's possible agents will report progress or results for jobs that are in the queue (after the restart)
420427
while ((enqueuedJob = queue.getImmediate())) {
421428
const job = this.jobsCache.get(enqueuedJob.id);
422429
if (job && !this.inProgress.has(enqueuedJob.id) && !this.resultsCache.has(enqueuedJob.id)) {
423-
const time = this.msTimeSource();
424-
this.inProgress.set(job.id, {
425-
id: job.id,
426-
startedAt: time,
427-
lastUpdatedAt: time,
428-
});
429-
const enqueuedAt = this.enqueuedAt.get(job.id);
430-
if (enqueuedAt) {
431-
this.instrumentation.recordJobWait(job.type, enqueuedAt);
432-
// we can clear this flag now.
433-
this.enqueuedAt.delete(job.id);
434-
}
435-
436-
return { job, time };
430+
candidate = { enqueuedJob, job };
431+
break;
437432
}
438433
}
434+
if (!candidate) {
435+
continue;
436+
}
437+
if (selected === undefined || candidate.enqueuedJob.epochNumber < selected.enqueuedJob.epochNumber) {
438+
if (selected) {
439+
this.queues[selected.proofType].put(selected.enqueuedJob);
440+
}
441+
selected = { proofType, ...candidate };
442+
} else {
443+
queue.put(candidate.enqueuedJob);
444+
}
445+
}
446+
447+
if (!selected) {
448+
return undefined;
449+
}
450+
451+
const { job } = selected;
452+
const time = this.msTimeSource();
453+
this.inProgress.set(job.id, {
454+
id: job.id,
455+
startedAt: time,
456+
lastUpdatedAt: time,
457+
});
458+
const enqueuedAt = this.enqueuedAt.get(job.id);
459+
if (enqueuedAt) {
460+
this.instrumentation.recordJobWait(job.type, enqueuedAt);
461+
// we can clear this flag now.
462+
this.enqueuedAt.delete(job.id);
439463
}
440464

441-
return undefined;
465+
return { job, time };
442466
}
443467

444468
async #reportProvingJobError(

0 commit comments

Comments
 (0)