@aztec/prover-client 0.0.1-commit.3100065 → 0.0.1-commit.330febf
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/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 +1 -1
- package/dest/mocks/test_context.d.ts.map +1 -1
- package/dest/mocks/test_context.js +2 -1
- package/dest/orchestrator/block-building-helpers.d.ts +1 -1
- package/dest/proving_broker/broker_prover_facade.d.ts +1 -1
- package/dest/proving_broker/broker_prover_facade.d.ts.map +1 -1
- package/dest/proving_broker/broker_prover_facade.js +8 -1
- package/dest/proving_broker/proving_broker.d.ts +2 -1
- package/dest/proving_broker/proving_broker.d.ts.map +1 -1
- package/dest/proving_broker/proving_broker.js +145 -70
- package/dest/proving_broker/proving_broker_database/memory.d.ts +3 -2
- package/dest/proving_broker/proving_broker_database/memory.d.ts.map +1 -1
- package/dest/proving_broker/proving_broker_database/memory.js +4 -1
- package/dest/proving_broker/proving_broker_database/persisted.d.ts +3 -1
- package/dest/proving_broker/proving_broker_database/persisted.d.ts.map +1 -1
- package/dest/proving_broker/proving_broker_database/persisted.js +25 -1
- package/dest/proving_broker/proving_broker_database.d.ts +13 -1
- package/dest/proving_broker/proving_broker_database.d.ts.map +1 -1
- package/dest/proving_broker/rpc.d.ts +13 -4
- package/dest/proving_broker/rpc.d.ts.map +1 -1
- package/dest/proving_broker/rpc.js +14 -3
- package/dest/test/mock_prover.d.ts +1 -1
- package/package.json +15 -15
- package/src/mocks/fixtures.ts +2 -4
- package/src/mocks/test_context.ts +2 -1
- package/src/proving_broker/broker_prover_facade.ts +8 -1
- package/src/proving_broker/proving_broker.ts +138 -69
- package/src/proving_broker/proving_broker_database/memory.ts +6 -2
- package/src/proving_broker/proving_broker_database/persisted.ts +29 -1
- package/src/proving_broker/proving_broker_database.ts +14 -0
- package/src/proving_broker/rpc.ts +16 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../../src/proving_broker/rpc.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EAEvB,KAAK,kBAAkB,EAExB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,KAAK,YAAY,EAAY,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,KAAK,kBAAkB,EAAgC,MAAM,0BAA0B,CAAC;AAKjG,qFAAqF;AACrF,wBAAiB,mBAAmB,qCAMnC;AAWD,eAAO,MAAM,wBAAwB,EAAE,YAAY,CAAC,kBAAkB,CAKrE,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,YAAY,CAAC,kBAAkB,CAiBrE,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,YAAY,CAAC,gBAAgB,CAGjE,CAAC;AAEF,eAAO,MAAM,2BAA2B,EAAE,YAAY,CAAC,qBAAqB,CAK3E,CAAC;AAEF,eAAO,MAAM,+BAA+B,EAAE,YAAY,CAAC,gBAAgB,GAAG,qBAAqB,CAGlG,CAAC;AAEF,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,OAAO,CAAC,kBAAkB,CAAC,EACrC,KAAK;;;;;EAA8C,
|
|
1
|
+
{"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../../src/proving_broker/rpc.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EAEvB,KAAK,kBAAkB,EAExB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,KAAK,YAAY,EAAY,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,KAAK,kBAAkB,EAAgC,MAAM,0BAA0B,CAAC;AAKjG;;;;;;;GAOG;AACH,eAAO,MAAM,mCAAmC,QAAmB,CAAC;AAEpE,qFAAqF;AACrF,wBAAiB,mBAAmB,qCAMnC;AAWD,eAAO,MAAM,wBAAwB,EAAE,YAAY,CAAC,kBAAkB,CAKrE,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,YAAY,CAAC,kBAAkB,CAiBrE,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,YAAY,CAAC,gBAAgB,CAGjE,CAAC;AAEF,eAAO,MAAM,2BAA2B,EAAE,YAAY,CAAC,qBAAqB,CAK3E,CAAC;AAEF,eAAO,MAAM,+BAA+B,EAAE,YAAY,CAAC,gBAAgB,GAAG,qBAAqB,CAGlG,CAAC;AAEF,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,OAAO,CAAC,kBAAkB,CAAC,EACrC,KAAK;;;;;EAA8C,EACnD,kBAAkB,SAAsC,GACvD,gBAAgB,CAOlB;AAED,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,OAAO,CAAC,kBAAkB,CAAC,EACrC,KAAK;;;;;EAA8C,EACnD,kBAAkB,SAAsC,GACvD,kBAAkB,CAOpB;AAED,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,OAAO,CAAC,kBAAkB,CAAC,EACrC,KAAK;;;;;EAA8C,EACnD,kBAAkB,SAAsC,GACvD,kBAAkB,CAOpB"}
|
|
@@ -6,6 +6,14 @@ import { optional } from '@aztec/stdlib/schemas';
|
|
|
6
6
|
import { getVersioningResponseHandler } from '@aztec/stdlib/versioning';
|
|
7
7
|
import { makeTracedFetch } from '@aztec/telemetry-client';
|
|
8
8
|
import { z } from 'zod';
|
|
9
|
+
/**
|
|
10
|
+
* Max JSON-RPC request body the broker clients will send.
|
|
11
|
+
*
|
|
12
|
+
* Job inputs and proof results travel inline as `data:` URIs on this path, so a single call is routinely larger than
|
|
13
|
+
* the 1 mb the generic JSON-RPC client allows — that default sizes batches of small node requests against the node
|
|
14
|
+
* server's own default. An AVM (`PUBLIC_VM`) result is around 1.3 mb today. This value must stay at or below the
|
|
15
|
+
* broker server's `RPC_MAX_BODY_SIZE`, which deployments set to 50mb.
|
|
16
|
+
*/ export const PROVER_BROKER_MAX_REQUEST_BODY_SIZE = 50 * 1024 * 1024;
|
|
9
17
|
/** Indefinite backoff for broker communication: 1, 1, 1, 2, 4, 4, 4, ... seconds. */ export function* proverBrokerBackoff() {
|
|
10
18
|
const v = [
|
|
11
19
|
1,
|
|
@@ -104,24 +112,27 @@ export const ProvingJobBrokerSchemaWithDebug = {
|
|
|
104
112
|
...ProvingJobBrokerSchema,
|
|
105
113
|
...ProvingJobBrokerDebugSchema
|
|
106
114
|
};
|
|
107
|
-
export function createProvingJobBrokerClient(url, versions, fetch = makeTracedFetch(proverBrokerBackoff, false)) {
|
|
115
|
+
export function createProvingJobBrokerClient(url, versions, fetch = makeTracedFetch(proverBrokerBackoff, false), maxRequestBodySize = PROVER_BROKER_MAX_REQUEST_BODY_SIZE) {
|
|
108
116
|
return createSafeJsonRpcClient(url, ProvingJobBrokerSchema, {
|
|
109
117
|
namespaceMethods: 'proverBroker',
|
|
110
118
|
fetch,
|
|
119
|
+
maxRequestBodySize,
|
|
111
120
|
onResponse: getVersioningResponseHandler(versions)
|
|
112
121
|
});
|
|
113
122
|
}
|
|
114
|
-
export function createProvingJobProducerClient(url, versions, fetch = makeTracedFetch(proverBrokerBackoff, false)) {
|
|
123
|
+
export function createProvingJobProducerClient(url, versions, fetch = makeTracedFetch(proverBrokerBackoff, false), maxRequestBodySize = PROVER_BROKER_MAX_REQUEST_BODY_SIZE) {
|
|
115
124
|
return createSafeJsonRpcClient(url, ProvingJobProducerSchema, {
|
|
116
125
|
namespaceMethods: 'provingJobProducer',
|
|
117
126
|
fetch,
|
|
127
|
+
maxRequestBodySize,
|
|
118
128
|
onResponse: getVersioningResponseHandler(versions)
|
|
119
129
|
});
|
|
120
130
|
}
|
|
121
|
-
export function createProvingJobConsumerClient(url, versions, fetch = makeTracedFetch(proverBrokerBackoff, false)) {
|
|
131
|
+
export function createProvingJobConsumerClient(url, versions, fetch = makeTracedFetch(proverBrokerBackoff, false), maxRequestBodySize = PROVER_BROKER_MAX_REQUEST_BODY_SIZE) {
|
|
122
132
|
return createSafeJsonRpcClient(url, ProvingJobConsumerSchema, {
|
|
123
133
|
namespaceMethods: 'provingJobConsumer',
|
|
124
134
|
fetch,
|
|
135
|
+
maxRequestBodySize,
|
|
125
136
|
onResponse: getVersioningResponseHandler(versions)
|
|
126
137
|
});
|
|
127
138
|
}
|
|
@@ -19,7 +19,7 @@ export declare class TestBroker implements ProvingJobProducer {
|
|
|
19
19
|
}
|
|
20
20
|
export declare class MockProver implements ServerCircuitProver {
|
|
21
21
|
constructor();
|
|
22
|
-
getAvmProof(_inputs: AvmCircuitInputs, _signal?: AbortSignal, _epochNumber?: number): Promise<import("@aztec/stdlib/proofs").RecursiveProof<
|
|
22
|
+
getAvmProof(_inputs: AvmCircuitInputs, _signal?: AbortSignal, _epochNumber?: number): Promise<import("@aztec/stdlib/proofs").RecursiveProof<14532>>;
|
|
23
23
|
getBaseParityProof(_inputs: ParityBasePrivateInputs, _signal?: AbortSignal, _epochNumber?: number): Promise<PublicInputsAndRecursiveProof<import("@aztec/stdlib/parity").ParityPublicInputs, 410>>;
|
|
24
24
|
getRootParityProof(_inputs: ParityRootPrivateInputs, _signal?: AbortSignal, _epochNumber?: number): Promise<PublicInputsAndRecursiveProof<import("@aztec/stdlib/parity").ParityPublicInputs, 410>>;
|
|
25
25
|
getPublicChonkVerifierProof(_inputs: PublicChonkVerifierPrivateInputs, _signal?: AbortSignal, _epochNumber?: number): Promise<PublicInputsAndRecursiveProof<PublicChonkVerifierPublicInputs, typeof NESTED_RECURSIVE_ROLLUP_HONK_PROOF_LENGTH>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/prover-client",
|
|
3
|
-
"version": "0.0.1-commit.
|
|
3
|
+
"version": "0.0.1-commit.330febf",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -69,19 +69,19 @@
|
|
|
69
69
|
]
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@aztec/bb-prover": "0.0.1-commit.
|
|
73
|
-
"@aztec/blob-lib": "0.0.1-commit.
|
|
74
|
-
"@aztec/constants": "0.0.1-commit.
|
|
75
|
-
"@aztec/ethereum": "0.0.1-commit.
|
|
76
|
-
"@aztec/foundation": "0.0.1-commit.
|
|
77
|
-
"@aztec/kv-store": "0.0.1-commit.
|
|
78
|
-
"@aztec/noir-protocol-circuits-types": "0.0.1-commit.
|
|
79
|
-
"@aztec/noir-types": "0.0
|
|
80
|
-
"@aztec/protocol-contracts": "0.0.1-commit.
|
|
81
|
-
"@aztec/simulator": "0.0.1-commit.
|
|
82
|
-
"@aztec/stdlib": "0.0.1-commit.
|
|
83
|
-
"@aztec/telemetry-client": "0.0.1-commit.
|
|
84
|
-
"@aztec/world-state": "0.0.1-commit.
|
|
72
|
+
"@aztec/bb-prover": "0.0.1-commit.330febf",
|
|
73
|
+
"@aztec/blob-lib": "0.0.1-commit.330febf",
|
|
74
|
+
"@aztec/constants": "0.0.1-commit.330febf",
|
|
75
|
+
"@aztec/ethereum": "0.0.1-commit.330febf",
|
|
76
|
+
"@aztec/foundation": "0.0.1-commit.330febf",
|
|
77
|
+
"@aztec/kv-store": "0.0.1-commit.330febf",
|
|
78
|
+
"@aztec/noir-protocol-circuits-types": "0.0.1-commit.330febf",
|
|
79
|
+
"@aztec/noir-types": "6.0.0-nightly.20260807",
|
|
80
|
+
"@aztec/protocol-contracts": "0.0.1-commit.330febf",
|
|
81
|
+
"@aztec/simulator": "0.0.1-commit.330febf",
|
|
82
|
+
"@aztec/stdlib": "0.0.1-commit.330febf",
|
|
83
|
+
"@aztec/telemetry-client": "0.0.1-commit.330febf",
|
|
84
|
+
"@aztec/world-state": "0.0.1-commit.330febf",
|
|
85
85
|
"@iarna/toml": "^2.2.5",
|
|
86
86
|
"commander": "^12.1.0",
|
|
87
87
|
"lodash.chunk": "^4.2.0",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"zod": "^4"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
|
-
"@aztec/noir-contracts.js": "0.0.1-commit.
|
|
93
|
+
"@aztec/noir-contracts.js": "0.0.1-commit.330febf",
|
|
94
94
|
"@jest/globals": "^30.0.0",
|
|
95
95
|
"@types/jest": "^30.0.0",
|
|
96
96
|
"@types/node": "^22.15.17",
|
package/src/mocks/fixtures.ts
CHANGED
|
@@ -17,12 +17,10 @@ import { promises as fs } from 'fs';
|
|
|
17
17
|
import path from 'path';
|
|
18
18
|
|
|
19
19
|
const {
|
|
20
|
-
BB_RELEASE_DIR = 'cpp/build/bin',
|
|
21
20
|
TEMP_DIR = '/tmp',
|
|
22
21
|
BB_BINARY_PATH = '',
|
|
23
22
|
BB_WORKING_DIRECTORY = '',
|
|
24
23
|
BB_SKIP_CLEANUP = '',
|
|
25
|
-
NOIR_RELEASE_DIR = 'noir-repo/target/release',
|
|
26
24
|
ACVM_BINARY_PATH = '',
|
|
27
25
|
ACVM_WORKING_DIRECTORY = '',
|
|
28
26
|
} = process.env;
|
|
@@ -32,7 +30,7 @@ export const getEnvironmentConfig = async (logger: Logger) => {
|
|
|
32
30
|
try {
|
|
33
31
|
const expectedBBPath = BB_BINARY_PATH
|
|
34
32
|
? BB_BINARY_PATH
|
|
35
|
-
: `${path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../
|
|
33
|
+
: `${path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../labs-aztec-toolchain/bin')}/bb-avm`;
|
|
36
34
|
await fs.access(expectedBBPath, fs.constants.R_OK);
|
|
37
35
|
const tempWorkingDirectory = `${TEMP_DIR}/${randomBytes(4).toString('hex')}`;
|
|
38
36
|
const bbWorkingDirectory = BB_WORKING_DIRECTORY ? BB_WORKING_DIRECTORY : `${tempWorkingDirectory}/bb`;
|
|
@@ -41,7 +39,7 @@ export const getEnvironmentConfig = async (logger: Logger) => {
|
|
|
41
39
|
|
|
42
40
|
const expectedAcvmPath = ACVM_BINARY_PATH
|
|
43
41
|
? ACVM_BINARY_PATH
|
|
44
|
-
: `${path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../
|
|
42
|
+
: `${path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../labs-aztec-toolchain/bin')}/acvm`;
|
|
45
43
|
await fs.access(expectedAcvmPath, fs.constants.R_OK);
|
|
46
44
|
const acvmWorkingDirectory = ACVM_WORKING_DIRECTORY ? ACVM_WORKING_DIRECTORY : `${tempWorkingDirectory}/acvm`;
|
|
47
45
|
await fs.mkdir(acvmWorkingDirectory, { recursive: true });
|
|
@@ -95,7 +95,7 @@ export class TestContext {
|
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
// Separated dbs for public processor and prover - see public_processor for context
|
|
98
|
-
const ws = await NativeWorldStateService.tmp(/*
|
|
98
|
+
const ws = await NativeWorldStateService.tmp(/*cleanupTmpDir=*/ true, genesis);
|
|
99
99
|
|
|
100
100
|
let localProver: ServerCircuitProver;
|
|
101
101
|
const config = await getEnvironmentConfig(logger);
|
|
@@ -136,6 +136,7 @@ export class TestContext {
|
|
|
136
136
|
async cleanup() {
|
|
137
137
|
await this.brokerProverFacade.stop();
|
|
138
138
|
await this.broker.stop();
|
|
139
|
+
await this.worldState.close();
|
|
139
140
|
for (const dir of this.directoriesToCleanup.filter(x => x !== '')) {
|
|
140
141
|
try {
|
|
141
142
|
await fs.rm(dir, { recursive: true, force: true, maxRetries: 3 });
|
|
@@ -145,7 +145,14 @@ export class BrokerCircuitProverFacade implements ServerCircuitProver {
|
|
|
145
145
|
|
|
146
146
|
try {
|
|
147
147
|
const inputsUri = await this.proofStore.saveProofInput(id, type, inputs);
|
|
148
|
-
job
|
|
148
|
+
// Only retain the inputs URI on the long-lived job when a failed-proof store is configured: it is
|
|
149
|
+
// read solely by `backupFailedProofInputs` on failure. With the default `InlineProofStore` the URI
|
|
150
|
+
// embeds the full circuit inputs, so retaining it for every in-flight job would pin those inputs in
|
|
151
|
+
// memory until the job settles for no purpose when no failed-proof store exists. The broker still
|
|
152
|
+
// receives the URI below regardless; this only governs what the facade holds onto.
|
|
153
|
+
if (this.failedProofStore) {
|
|
154
|
+
job.inputsUri = inputsUri;
|
|
155
|
+
}
|
|
149
156
|
|
|
150
157
|
// Send the job to the broker
|
|
151
158
|
const jobStatus = await this.broker.enqueueProvingJob({ id, type, inputsUri, epochNumber });
|
|
@@ -33,6 +33,23 @@ type InProgressMetadata = {
|
|
|
33
33
|
|
|
34
34
|
type EnqueuedProvingJob = Pick<ProvingJob, 'id' | 'epochNumber'>;
|
|
35
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
|
+
|
|
36
53
|
/**
|
|
37
54
|
* A broker that manages proof requests and distributes them to workers based on their priority.
|
|
38
55
|
* It takes a backend that is responsible for storing and retrieving proof requests and results.
|
|
@@ -69,11 +86,17 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
69
86
|
[ProvingRequestType.PARITY_ROOT]: new PriorityMemoryQueue<EnqueuedProvingJob>(provingJobComparator),
|
|
70
87
|
};
|
|
71
88
|
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
private jobsCache = new Map<ProvingJobId,
|
|
75
|
-
//
|
|
76
|
-
|
|
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>();
|
|
77
100
|
|
|
78
101
|
// tracks when each job was enqueued
|
|
79
102
|
private enqueuedAt = new Map<ProvingJobId, Timer>();
|
|
@@ -87,8 +110,9 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
87
110
|
// keep track of which proving job has been retried
|
|
88
111
|
private retries = new Map<ProvingJobId, number>();
|
|
89
112
|
|
|
90
|
-
// a map of promises that
|
|
91
|
-
|
|
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>>();
|
|
92
116
|
|
|
93
117
|
private cleanupPromise: RunningPromise;
|
|
94
118
|
private msTimeSource = () => Date.now();
|
|
@@ -175,12 +199,12 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
175
199
|
status: result ? result.status : 'pending',
|
|
176
200
|
});
|
|
177
201
|
|
|
178
|
-
this.jobsCache.set(item.id, item);
|
|
202
|
+
this.jobsCache.set(item.id, toJobMetadata(item));
|
|
179
203
|
this.promises.set(item.id, promiseWithResolvers());
|
|
180
204
|
|
|
181
205
|
if (result) {
|
|
182
|
-
this.promises.get(item.id)!.resolve(
|
|
183
|
-
this.resultsCache.set(item.id, result);
|
|
206
|
+
this.promises.get(item.id)!.resolve();
|
|
207
|
+
this.resultsCache.set(item.id, toSettledRecord(result));
|
|
184
208
|
} else {
|
|
185
209
|
this.enqueueJobInternal(item);
|
|
186
210
|
}
|
|
@@ -211,7 +235,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
211
235
|
}
|
|
212
236
|
|
|
213
237
|
public getProvingJobStatus(id: ProvingJobId): Promise<ProvingJobStatus> {
|
|
214
|
-
return
|
|
238
|
+
return this.#getProvingJobStatus(id);
|
|
215
239
|
}
|
|
216
240
|
|
|
217
241
|
public getCompletedJobs(ids: ProvingJobId[]): Promise<ProvingJobId[]> {
|
|
@@ -219,7 +243,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
219
243
|
}
|
|
220
244
|
|
|
221
245
|
public getProvingJob(filter?: ProvingJobFilter): Promise<GetProvingJobResponse | undefined> {
|
|
222
|
-
return
|
|
246
|
+
return this.#getProvingJob(filter);
|
|
223
247
|
}
|
|
224
248
|
|
|
225
249
|
public reportProvingJobSuccess(
|
|
@@ -244,7 +268,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
244
268
|
startedAt: number,
|
|
245
269
|
filter?: ProvingJobFilter,
|
|
246
270
|
): Promise<{ job: ProvingJob; time: number } | undefined> {
|
|
247
|
-
return
|
|
271
|
+
return this.#reportProvingJobProgress(id, startedAt, filter);
|
|
248
272
|
}
|
|
249
273
|
|
|
250
274
|
public async replayProvingJob(
|
|
@@ -263,7 +287,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
263
287
|
this.cleanUpProvingJobState([jobId]);
|
|
264
288
|
|
|
265
289
|
const job: ProvingJob = { id: jobId, type, epochNumber, inputsUri };
|
|
266
|
-
this.jobsCache.set(jobId, job);
|
|
290
|
+
this.jobsCache.set(jobId, toJobMetadata(job));
|
|
267
291
|
await this.database.addProvingJob(job);
|
|
268
292
|
this.enqueueJobInternal(job);
|
|
269
293
|
|
|
@@ -271,11 +295,17 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
271
295
|
}
|
|
272
296
|
|
|
273
297
|
async #enqueueProvingJob(job: ProvingJob): Promise<ProvingJobStatus> {
|
|
274
|
-
//
|
|
275
|
-
|
|
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' };
|
|
276
304
|
if (this.jobsCache.has(job.id)) {
|
|
277
305
|
const existing = this.jobsCache.get(job.id);
|
|
278
|
-
|
|
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');
|
|
279
309
|
|
|
280
310
|
if (this.resultsCache.get(job.id)?.status === 'aborted') {
|
|
281
311
|
// The producer is re-requesting a job it previously cancelled: revive it rather than
|
|
@@ -290,21 +320,26 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
290
320
|
// single synchronous span (no await in between), and only then await the database. Because a
|
|
291
321
|
// concurrent re-request can only interleave at that await — by which point `jobsCache` is
|
|
292
322
|
// populated again and the aborted result is gone — it falls into the cached branch and no-ops,
|
|
293
|
-
// so the job is enqueued exactly once. (`cleanUpProvingJobState` also drops the promise,
|
|
294
|
-
//
|
|
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.
|
|
295
327
|
this.logger.info(`Reviving aborted proving job id=${job.id} epochNumber=${job.epochNumber}`, {
|
|
296
328
|
provingJobId: job.id,
|
|
297
329
|
});
|
|
298
330
|
this.cleanUpProvingJobState([job.id]);
|
|
299
|
-
this.jobsCache.set(job.id, job);
|
|
331
|
+
this.jobsCache.set(job.id, toJobMetadata(job));
|
|
300
332
|
await this.database.deleteProvingJobResult(job.id);
|
|
301
|
-
|
|
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' };
|
|
302
335
|
} else {
|
|
303
336
|
this.logger.warn(`Cached proving job id=${job.id} epochNumber=${job.epochNumber}. Not enqueuing again`, {
|
|
304
337
|
provingJobId: job.id,
|
|
305
338
|
});
|
|
306
339
|
this.instrumentation.incCachedJobs(job.type);
|
|
307
|
-
|
|
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);
|
|
308
343
|
}
|
|
309
344
|
}
|
|
310
345
|
|
|
@@ -318,7 +353,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
318
353
|
this.logger.info(`New proving job id=${job.id} epochNumber=${job.epochNumber}`, { provingJobId: job.id });
|
|
319
354
|
try {
|
|
320
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.
|
|
321
|
-
this.jobsCache.set(job.id, job);
|
|
356
|
+
this.jobsCache.set(job.id, toJobMetadata(job));
|
|
322
357
|
await this.database.addProvingJob(job);
|
|
323
358
|
this.enqueueJobInternal(job);
|
|
324
359
|
this.instrumentation.incTotalJobs(job.type);
|
|
@@ -349,9 +384,8 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
349
384
|
// notifies the current waiter. Unlike a completion or failure this is not terminal: re-enqueuing
|
|
350
385
|
// the same job id revives it (see #enqueueProvingJob), so the abort never permanently blocks the
|
|
351
386
|
// proof.
|
|
352
|
-
|
|
353
|
-
this.
|
|
354
|
-
this.promises.get(id)?.resolve(result);
|
|
387
|
+
this.resultsCache.set(id, { status: 'aborted' });
|
|
388
|
+
this.promises.get(id)?.resolve();
|
|
355
389
|
this.completedJobNotifications.push(id);
|
|
356
390
|
this.instrumentation.incAbortedJobs(job.type);
|
|
357
391
|
|
|
@@ -369,10 +403,11 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
369
403
|
this.jobsCache.delete(id);
|
|
370
404
|
const deferred = this.promises.get(id);
|
|
371
405
|
if (deferred) {
|
|
372
|
-
deferred.resolve(
|
|
406
|
+
deferred.resolve();
|
|
373
407
|
}
|
|
374
408
|
this.promises.delete(id);
|
|
375
409
|
this.resultsCache.delete(id);
|
|
410
|
+
this.pendingResults.delete(id);
|
|
376
411
|
this.inProgress.delete(id);
|
|
377
412
|
this.retries.delete(id);
|
|
378
413
|
this.enqueuedAt.delete(id);
|
|
@@ -380,20 +415,31 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
380
415
|
this.completedJobNotifications = this.completedJobNotifications.filter(id => !idsToClean.has(id));
|
|
381
416
|
}
|
|
382
417
|
|
|
383
|
-
#getProvingJobStatus(id: ProvingJobId): ProvingJobStatus {
|
|
384
|
-
const
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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;
|
|
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;
|
|
393
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
|
+
}
|
|
394
436
|
|
|
395
|
-
|
|
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' };
|
|
396
441
|
}
|
|
442
|
+
return { status: this.inProgress.has(id) ? 'in-progress' : 'in-queue' };
|
|
397
443
|
}
|
|
398
444
|
|
|
399
445
|
#getCompletedJobs(ids: ProvingJobId[]): Promise<ProvingJobId[]> {
|
|
@@ -403,7 +449,9 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
403
449
|
return Promise.resolve(notifications.concat(completedJobs));
|
|
404
450
|
}
|
|
405
451
|
|
|
406
|
-
#getProvingJob(
|
|
452
|
+
async #getProvingJob(
|
|
453
|
+
filter: ProvingJobFilter = { allowList: [] },
|
|
454
|
+
): Promise<{ job: ProvingJob; time: number } | undefined> {
|
|
407
455
|
const allowedProofs: ProvingRequestType[] =
|
|
408
456
|
Array.isArray(filter.allowList) && filter.allowList.length > 0
|
|
409
457
|
? [...filter.allowList]
|
|
@@ -418,21 +466,40 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
418
466
|
// this can happen if the broker crashes and restarts
|
|
419
467
|
// it's possible agents will report progress or results for jobs that are in the queue (after the restart)
|
|
420
468
|
while ((enqueuedJob = queue.getImmediate())) {
|
|
421
|
-
const
|
|
422
|
-
if (
|
|
469
|
+
const meta = this.jobsCache.get(enqueuedJob.id);
|
|
470
|
+
if (meta && !this.inProgress.has(enqueuedJob.id) && !this.resultsCache.has(enqueuedJob.id)) {
|
|
423
471
|
const time = this.msTimeSource();
|
|
424
|
-
|
|
425
|
-
|
|
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,
|
|
426
478
|
startedAt: time,
|
|
427
479
|
lastUpdatedAt: time,
|
|
428
480
|
});
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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;
|
|
434
494
|
}
|
|
435
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 };
|
|
436
503
|
return { job, time };
|
|
437
504
|
}
|
|
438
505
|
}
|
|
@@ -499,11 +566,10 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
499
566
|
},
|
|
500
567
|
);
|
|
501
568
|
|
|
502
|
-
// save the
|
|
503
|
-
//
|
|
504
|
-
|
|
505
|
-
this.
|
|
506
|
-
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();
|
|
507
573
|
this.completedJobNotifications.push(id);
|
|
508
574
|
|
|
509
575
|
this.instrumentation.incRejectedJobs(item.type);
|
|
@@ -525,20 +591,20 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
525
591
|
return this.#getProvingJob(filter);
|
|
526
592
|
}
|
|
527
593
|
|
|
528
|
-
#reportProvingJobProgress(
|
|
594
|
+
async #reportProvingJobProgress(
|
|
529
595
|
id: ProvingJobId,
|
|
530
596
|
startedAt: number,
|
|
531
597
|
filter?: ProvingJobFilter,
|
|
532
|
-
): { job: ProvingJob; time: number } | undefined {
|
|
598
|
+
): Promise<{ job: ProvingJob; time: number } | undefined> {
|
|
533
599
|
const job = this.jobsCache.get(id);
|
|
534
600
|
if (!job) {
|
|
535
601
|
this.logger.warn(`Proving job id=${id} does not exist`, { provingJobId: id });
|
|
536
|
-
return this.#getProvingJob(filter);
|
|
602
|
+
return await this.#getProvingJob(filter);
|
|
537
603
|
}
|
|
538
604
|
|
|
539
605
|
if (this.resultsCache.has(id)) {
|
|
540
606
|
this.logger.warn(`Proving job id=${id} has already been completed`, { provingJobId: id });
|
|
541
|
-
return this.#getProvingJob(filter);
|
|
607
|
+
return await this.#getProvingJob(filter);
|
|
542
608
|
}
|
|
543
609
|
|
|
544
610
|
const metadata = this.inProgress.get(id);
|
|
@@ -578,7 +644,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
578
644
|
{ provingJobId: id },
|
|
579
645
|
);
|
|
580
646
|
|
|
581
|
-
return this.#getProvingJob(filter);
|
|
647
|
+
return await this.#getProvingJob(filter);
|
|
582
648
|
}
|
|
583
649
|
|
|
584
650
|
async #reportProvingJobSuccess(
|
|
@@ -612,12 +678,12 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
612
678
|
{ provingJobId: id },
|
|
613
679
|
);
|
|
614
680
|
|
|
615
|
-
//
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
|
|
619
|
-
this.
|
|
620
|
-
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();
|
|
621
687
|
this.completedJobNotifications.push(id);
|
|
622
688
|
|
|
623
689
|
this.instrumentation.incResolvedJobs(item.type);
|
|
@@ -628,7 +694,11 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
628
694
|
|
|
629
695
|
try {
|
|
630
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);
|
|
631
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.
|
|
632
702
|
this.logger.error(`Failed to save proving job result id=${id}`, saveErr, {
|
|
633
703
|
provingJobId: id,
|
|
634
704
|
});
|
|
@@ -692,9 +762,8 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
692
762
|
this.logger.error(`Proving job id=${id} timed out after ${retries + 1} attempts. Marking as failed.`, {
|
|
693
763
|
provingJobId: id,
|
|
694
764
|
});
|
|
695
|
-
|
|
696
|
-
this.
|
|
697
|
-
this.promises.get(id)?.resolve(result);
|
|
765
|
+
this.resultsCache.set(id, { status: 'rejected', reason: 'Timed out' });
|
|
766
|
+
this.promises.get(id)?.resolve();
|
|
698
767
|
this.completedJobNotifications.push(id);
|
|
699
768
|
this.instrumentation.incRejectedJobs(item.type);
|
|
700
769
|
}
|
|
@@ -702,7 +771,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
702
771
|
}
|
|
703
772
|
}
|
|
704
773
|
|
|
705
|
-
private enqueueJobInternal(job:
|
|
774
|
+
private enqueueJobInternal(job: ProvingJobMetadata): void {
|
|
706
775
|
if (!this.promises.has(job.id)) {
|
|
707
776
|
this.promises.set(job.id, promiseWithResolvers());
|
|
708
777
|
}
|
|
@@ -714,7 +783,7 @@ export class ProvingBroker implements ProvingJobProducer, ProvingJobConsumer, Pr
|
|
|
714
783
|
this.epochHeight = Math.max(this.epochHeight, job.epochNumber);
|
|
715
784
|
}
|
|
716
785
|
|
|
717
|
-
private isJobStale(job:
|
|
786
|
+
private isJobStale(job: ProvingJobMetadata) {
|
|
718
787
|
return job.epochNumber < this.oldestEpochToKeep();
|
|
719
788
|
}
|
|
720
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> {
|