@aztec/prover-node 0.0.1-commit.d3ec352c → 0.0.1-commit.d58ff9d0
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/README.md +518 -0
- package/dest/actions/download-epoch-proving-job.d.ts +1 -1
- package/dest/actions/download-epoch-proving-job.js +1 -1
- package/dest/actions/rerun-epoch-proving-job.d.ts +6 -4
- package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -1
- package/dest/actions/rerun-epoch-proving-job.js +191 -28
- package/dest/actions/upload-epoch-proof-failure.d.ts +2 -2
- package/dest/actions/upload-epoch-proof-failure.d.ts.map +1 -1
- package/dest/bin/run-failed-epoch.js +6 -5
- package/dest/checkpoint-store.d.ts +87 -0
- package/dest/checkpoint-store.d.ts.map +1 -0
- package/dest/checkpoint-store.js +186 -0
- package/dest/config.d.ts +10 -10
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +27 -22
- package/dest/factory.d.ts +23 -16
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +49 -68
- package/dest/index.d.ts +3 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +2 -0
- package/dest/job/checkpoint-prover.d.ts +134 -0
- package/dest/job/checkpoint-prover.d.ts.map +1 -0
- package/dest/job/checkpoint-prover.js +353 -0
- package/dest/job/epoch-proving-job-data.d.ts +2 -2
- package/dest/job/epoch-proving-job-data.d.ts.map +1 -1
- package/dest/job/epoch-proving-job-data.js +1 -1
- package/dest/job/epoch-session.d.ts +146 -0
- package/dest/job/epoch-session.d.ts.map +1 -0
- package/dest/job/epoch-session.js +720 -0
- package/dest/job/top-tree-job.d.ts +82 -0
- package/dest/job/top-tree-job.d.ts.map +1 -0
- package/dest/job/top-tree-job.js +152 -0
- package/dest/metrics.d.ts +41 -4
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +125 -102
- package/dest/monitors/epoch-monitor.d.ts +1 -1
- package/dest/monitors/epoch-monitor.d.ts.map +1 -1
- package/dest/monitors/epoch-monitor.js +12 -19
- package/dest/proof-publishing-service.d.ts +161 -0
- package/dest/proof-publishing-service.d.ts.map +1 -0
- package/dest/proof-publishing-service.js +335 -0
- package/dest/prover-node-publisher.d.ts +31 -19
- package/dest/prover-node-publisher.d.ts.map +1 -1
- package/dest/prover-node-publisher.js +222 -78
- package/dest/prover-node.d.ts +122 -72
- package/dest/prover-node.d.ts.map +1 -1
- package/dest/prover-node.js +872 -229
- package/dest/prover-publisher-factory.d.ts +12 -6
- package/dest/prover-publisher-factory.d.ts.map +1 -1
- package/dest/prover-publisher-factory.js +8 -5
- package/dest/session-manager.d.ts +158 -0
- package/dest/session-manager.d.ts.map +1 -0
- package/dest/session-manager.js +452 -0
- package/dest/test/index.d.ts +7 -6
- package/dest/test/index.d.ts.map +1 -1
- package/package.json +25 -23
- package/src/actions/download-epoch-proving-job.ts +1 -1
- package/src/actions/rerun-epoch-proving-job.ts +120 -30
- package/src/actions/upload-epoch-proof-failure.ts +1 -1
- package/src/bin/run-failed-epoch.ts +5 -3
- package/src/checkpoint-store.ts +218 -0
- package/src/config.ts +39 -34
- package/src/factory.ts +86 -113
- package/src/index.ts +2 -0
- package/src/job/checkpoint-prover.ts +468 -0
- package/src/job/epoch-proving-job-data.ts +1 -1
- package/src/job/epoch-session.ts +436 -0
- package/src/job/top-tree-job.ts +227 -0
- package/src/metrics.ts +143 -93
- package/src/monitors/epoch-monitor.ts +6 -14
- package/src/proof-publishing-service.ts +427 -0
- package/src/prover-node-publisher.ts +264 -98
- package/src/prover-node.ts +568 -259
- package/src/prover-publisher-factory.ts +22 -11
- package/src/session-manager.ts +552 -0
- package/src/test/index.ts +6 -6
- package/dest/job/epoch-proving-job.d.ts +0 -62
- package/dest/job/epoch-proving-job.d.ts.map +0 -1
- package/dest/job/epoch-proving-job.js +0 -363
- package/src/job/epoch-proving-job.ts +0 -426
|
@@ -1,61 +1,151 @@
|
|
|
1
|
-
import { createArchiverStore } from '@aztec/archiver';
|
|
1
|
+
import { createArchiverStore, createContractDataSource } from '@aztec/archiver';
|
|
2
|
+
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
3
|
+
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
2
4
|
import type { Logger } from '@aztec/foundation/log';
|
|
3
|
-
import
|
|
5
|
+
import { DateProvider } from '@aztec/foundation/timer';
|
|
4
6
|
import { type ProverClientConfig, createProverClient } from '@aztec/prover-client';
|
|
5
7
|
import { ProverBrokerConfig, createAndStartProvingBroker } from '@aztec/prover-client/broker';
|
|
6
|
-
import {
|
|
8
|
+
import { getLastSiblingPath } from '@aztec/prover-client/helpers';
|
|
9
|
+
import { ChonkCache } from '@aztec/prover-client/orchestrator';
|
|
10
|
+
import { AvmSimulatorPool, PublicProcessorFactory } from '@aztec/simulator/server';
|
|
11
|
+
import type { L2Block } from '@aztec/stdlib/block';
|
|
12
|
+
import { getEpochAtSlot, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
|
|
13
|
+
import type { ITxProvider } from '@aztec/stdlib/interfaces/server';
|
|
14
|
+
import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
|
|
15
|
+
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
16
|
+
import type { Tx, TxHash } from '@aztec/stdlib/tx';
|
|
17
|
+
import type { GenesisData } from '@aztec/stdlib/world-state';
|
|
7
18
|
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
8
19
|
import { createWorldState } from '@aztec/world-state';
|
|
9
20
|
|
|
10
21
|
import { readFileSync } from 'fs';
|
|
11
22
|
|
|
23
|
+
import { CheckpointProver } from '../job/checkpoint-prover.js';
|
|
12
24
|
import { deserializeEpochProvingJobData } from '../job/epoch-proving-job-data.js';
|
|
13
|
-
import {
|
|
25
|
+
import { EpochSession, type SessionSpec } from '../job/epoch-session.js';
|
|
14
26
|
import { ProverNodeJobMetrics } from '../metrics.js';
|
|
15
27
|
|
|
16
28
|
/**
|
|
17
29
|
* Given a local folder where `downloadEpochProvingJob` was called, creates a new archiver and world state
|
|
18
|
-
* using the state snapshots, and creates a new epoch proving
|
|
30
|
+
* using the state snapshots, and creates a new epoch proving session to prove the downloaded proving job.
|
|
19
31
|
* Proving is done with a local proving broker and agents as specified by the config.
|
|
20
32
|
*/
|
|
21
33
|
export async function rerunEpochProvingJob(
|
|
22
34
|
localPath: string,
|
|
23
35
|
log: Logger,
|
|
24
|
-
config: DataStoreConfig & ProverBrokerConfig & ProverClientConfig,
|
|
36
|
+
config: DataStoreConfig & ProverBrokerConfig & ProverClientConfig & Pick<L1ContractsConfig, 'aztecEpochDuration'>,
|
|
37
|
+
genesis?: GenesisData,
|
|
25
38
|
) {
|
|
26
39
|
const jobData = deserializeEpochProvingJobData(readFileSync(localPath));
|
|
27
40
|
log.info(`Loaded proving job data for epoch ${jobData.epochNumber}`);
|
|
28
41
|
|
|
29
42
|
const telemetry = getTelemetryClient();
|
|
30
43
|
const metrics = new ProverNodeJobMetrics(telemetry.getMeter('prover-job'), telemetry.getTracer('prover-job'));
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
await using worldState = await createWorldState(config, genesis);
|
|
45
|
+
const initialBlockHash = await worldState.getInitialHeader().hash();
|
|
46
|
+
const archiver = await createArchiverStore(config, initialBlockHash);
|
|
47
|
+
await using avmSimulator = await AvmSimulatorPool.spawn({ wsdbIpcPath: worldState.getIpcPath() });
|
|
48
|
+
const publicProcessorFactory = new PublicProcessorFactory(
|
|
49
|
+
createContractDataSource(archiver),
|
|
50
|
+
avmSimulator,
|
|
51
|
+
undefined,
|
|
52
|
+
undefined,
|
|
53
|
+
log.getBindings(),
|
|
54
|
+
);
|
|
38
55
|
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
56
|
+
// Local rerun never publishes — stub the service so submit() always resolves 'published'
|
|
57
|
+
// and withdraw is a no-op.
|
|
58
|
+
const publishingService = {
|
|
59
|
+
submit: () => Promise.resolve('published' as const),
|
|
60
|
+
withdraw: () => {},
|
|
61
|
+
};
|
|
42
62
|
const broker = await createAndStartProvingBroker(config, telemetry);
|
|
43
63
|
const prover = await createProverClient(config, worldState, broker, telemetry);
|
|
64
|
+
const chonkCache = new ChonkCache(log.getBindings());
|
|
65
|
+
|
|
66
|
+
const txProvider = makeReplayingTxProvider(jobData.txs);
|
|
67
|
+
|
|
68
|
+
log.info(`Rerunning epoch proving for epoch ${jobData.epochNumber}`);
|
|
69
|
+
|
|
70
|
+
const provers: CheckpointProver[] = [];
|
|
71
|
+
for (let i = 0; i < jobData.checkpoints.length; i++) {
|
|
72
|
+
const checkpoint = jobData.checkpoints[i];
|
|
73
|
+
const previousBlockHeader =
|
|
74
|
+
i === 0 ? jobData.previousBlockHeader : jobData.checkpoints[i - 1].blocks.at(-1)!.header;
|
|
75
|
+
const l1ToL2Messages = jobData.l1ToL2Messages[checkpoint.number] ?? [];
|
|
76
|
+
const previousArchiveSiblingPath = await getLastSiblingPath(
|
|
77
|
+
MerkleTreeId.ARCHIVE,
|
|
78
|
+
worldState.getSnapshot(BlockNumber(checkpoint.blocks[0].number - 1)),
|
|
79
|
+
);
|
|
80
|
+
const attestations = checkpoint.number === jobData.checkpoints.at(-1)!.number ? jobData.attestations : [];
|
|
81
|
+
provers.push(
|
|
82
|
+
new CheckpointProver(
|
|
83
|
+
{
|
|
84
|
+
checkpoint,
|
|
85
|
+
epochNumber: jobData.epochNumber,
|
|
86
|
+
attestations,
|
|
87
|
+
previousBlockHeader,
|
|
88
|
+
l1ToL2Messages,
|
|
89
|
+
previousArchiveSiblingPath,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
proverFactory: prover,
|
|
93
|
+
chonkCache,
|
|
94
|
+
publicProcessorFactory,
|
|
95
|
+
dbProvider: worldState,
|
|
96
|
+
txProvider,
|
|
97
|
+
dateProvider: new DateProvider(),
|
|
98
|
+
proverId: prover.getProverId(),
|
|
99
|
+
metrics,
|
|
100
|
+
txGatheringTimeoutMs: 120_000,
|
|
101
|
+
deadline: undefined,
|
|
102
|
+
log,
|
|
103
|
+
},
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
44
107
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
108
|
+
const l1Constants = { epochDuration: config.aztecEpochDuration };
|
|
109
|
+
const [fromSlot, toSlot] = getSlotRangeForEpoch(jobData.epochNumber, l1Constants);
|
|
110
|
+
const spec: SessionSpec = { kind: 'full', epochNumber: jobData.epochNumber, fromSlot, toSlot };
|
|
111
|
+
|
|
112
|
+
const session = new EpochSession(spec, provers, {
|
|
113
|
+
proverFactory: prover,
|
|
114
|
+
proverId: prover.getProverId(),
|
|
115
|
+
publishingService,
|
|
52
116
|
metrics,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
117
|
+
dateProvider: new DateProvider(),
|
|
118
|
+
deadline: undefined,
|
|
119
|
+
config: {},
|
|
120
|
+
bindings: log.getBindings(),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const finalState = await session.start();
|
|
124
|
+
log.info(`Completed proving for epoch ${jobData.epochNumber} with status ${finalState}`, {
|
|
125
|
+
derivedEpoch: getEpochAtSlot(provers[0].slotNumber, l1Constants),
|
|
126
|
+
});
|
|
127
|
+
return finalState;
|
|
128
|
+
}
|
|
56
129
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
130
|
+
/** Build a synthetic ITxProvider that returns the supplied txs map by lookup. */
|
|
131
|
+
function makeReplayingTxProvider(txs: Map<string, Tx>): ITxProvider {
|
|
132
|
+
const lookup = (hashes: TxHash[]) => {
|
|
133
|
+
const found: Tx[] = [];
|
|
134
|
+
const missing: TxHash[] = [];
|
|
135
|
+
for (const hash of hashes) {
|
|
136
|
+
const tx = txs.get(hash.toString());
|
|
137
|
+
if (tx) {
|
|
138
|
+
found.push(tx);
|
|
139
|
+
} else {
|
|
140
|
+
missing.push(hash);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return { txs: found, missingTxs: missing };
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
getAvailableTxs: hashes => Promise.resolve(lookup(hashes)),
|
|
147
|
+
hasTxs: hashes => Promise.resolve(hashes.map(h => txs.has(h.toString()))),
|
|
148
|
+
getTxsForBlockProposal: () => Promise.resolve({ txs: [], missingTxs: [] }),
|
|
149
|
+
getTxsForBlock: (block: L2Block) => Promise.resolve(lookup(block.body.txEffects.map(e => e.txHash))),
|
|
150
|
+
};
|
|
61
151
|
}
|
|
@@ -3,11 +3,11 @@ import { tryRmDir } from '@aztec/foundation/fs';
|
|
|
3
3
|
import { jsonStringify } from '@aztec/foundation/json-rpc';
|
|
4
4
|
import type { Logger } from '@aztec/foundation/log';
|
|
5
5
|
import { isoDate } from '@aztec/foundation/string';
|
|
6
|
-
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
7
6
|
import { buildSnapshotMetadata, createBackups } from '@aztec/node-lib/actions';
|
|
8
7
|
import type { ChainConfig } from '@aztec/stdlib/config';
|
|
9
8
|
import { type FileStore, createFileStore } from '@aztec/stdlib/file-store';
|
|
10
9
|
import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
10
|
+
import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
|
|
11
11
|
import { type UploadSnapshotMetadata, getBasePath, uploadSnapshotData } from '@aztec/stdlib/snapshots';
|
|
12
12
|
import { WORLD_STATE_DB_VERSION } from '@aztec/world-state';
|
|
13
13
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
-
import
|
|
2
|
+
import { getL1ContractsConfigEnvVars } from '@aztec/ethereum/config';
|
|
3
3
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
4
|
import { jsonParseWithSchema, jsonStringify } from '@aztec/foundation/json-rpc';
|
|
5
5
|
import { createLogger } from '@aztec/foundation/log';
|
|
@@ -22,8 +22,10 @@ async function rerunFailedEpoch(provingJobUrl: string, baseLocalDir: string) {
|
|
|
22
22
|
const dataDir = join(localDir, 'state');
|
|
23
23
|
|
|
24
24
|
const env = getProverNodeConfigFromEnv();
|
|
25
|
+
const l1Config = getL1ContractsConfigEnvVars();
|
|
25
26
|
const config = {
|
|
26
|
-
...
|
|
27
|
+
...env,
|
|
28
|
+
...l1Config,
|
|
27
29
|
dataDirectory: dataDir,
|
|
28
30
|
dataStoreMapSizeKb: env.dataStoreMapSizeKb ?? 1024 * 1024,
|
|
29
31
|
proverId: env.proverId ?? EthAddress.random(),
|
|
@@ -47,7 +49,7 @@ async function rerunFailedEpoch(provingJobUrl: string, baseLocalDir: string) {
|
|
|
47
49
|
logger.info(`Rerunning proving job from ${jobPath} with state from ${dataDir}`, metadata);
|
|
48
50
|
const result = await rerunEpochProvingJob(jobPath, logger, {
|
|
49
51
|
...config,
|
|
50
|
-
|
|
52
|
+
rollupAddress: metadata.rollupAddress,
|
|
51
53
|
rollupVersion: metadata.rollupVersion,
|
|
52
54
|
});
|
|
53
55
|
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
3
|
+
import { RunningPromise } from '@aztec/foundation/promise';
|
|
4
|
+
import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
5
|
+
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
6
|
+
import { type L1RollupConstants, getEpochAtSlot, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
|
|
7
|
+
|
|
8
|
+
import { CheckpointProver, type CheckpointProverArgs, type CheckpointProverDeps } from './job/checkpoint-prover.js';
|
|
9
|
+
|
|
10
|
+
/** Register-time data needed to construct a `CheckpointProver` (everything except the checkpoint + epoch). */
|
|
11
|
+
export type RegisterCheckpointData = Omit<CheckpointProverArgs, 'checkpoint' | 'epochNumber'>;
|
|
12
|
+
|
|
13
|
+
/** Factory used by the store to construct new provers. Tests can inject a stub. */
|
|
14
|
+
export type CheckpointProverFactory = (args: CheckpointProverArgs, deps: CheckpointProverDeps) => CheckpointProver;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Prover-node-wide registry of `CheckpointProver` instances, content-addressed by
|
|
18
|
+
* `(checkpoint number, slot, checkpoint archive root)`.
|
|
19
|
+
*
|
|
20
|
+
* The store survives every epoch / session boundary. A prover lives from its first
|
|
21
|
+
* `addOrUpdate` call until either:
|
|
22
|
+
* - it has been pruned and the L2 chain has moved past its slot (no re-add possible), or
|
|
23
|
+
* - its epoch's proof-submission window has closed (`reapExpired`), so the proof could no
|
|
24
|
+
* longer be accepted on L1 even if produced.
|
|
25
|
+
*
|
|
26
|
+
* A re-add of a checkpoint that matches an existing prover's content key reuses the
|
|
27
|
+
* existing prover (and flips it back to canonical); the in-flight sub-tree work never
|
|
28
|
+
* stops, so a prune-then-re-add of the same content avoids re-proving entirely.
|
|
29
|
+
*/
|
|
30
|
+
export class CheckpointStore {
|
|
31
|
+
private readonly provers = new Map<string, CheckpointProver>();
|
|
32
|
+
private readonly slotWatcher: RunningPromise;
|
|
33
|
+
private readonly log: Logger;
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
private readonly l2BlockSource: Pick<L2BlockSource, 'getSyncedL2SlotNumber' | 'getL1Constants'>,
|
|
37
|
+
private readonly proverDeps: Omit<CheckpointProverDeps, 'log'>,
|
|
38
|
+
private readonly options: { slotWatcherPollIntervalMs: number },
|
|
39
|
+
bindings?: LoggerBindings,
|
|
40
|
+
private readonly proverFactoryFn: CheckpointProverFactory = (args, deps) => new CheckpointProver(args, deps),
|
|
41
|
+
) {
|
|
42
|
+
this.log = createLogger('prover-node:checkpoint-store', bindings);
|
|
43
|
+
this.slotWatcher = new RunningPromise(
|
|
44
|
+
() => this.reapPrunedPastSlot(),
|
|
45
|
+
this.log,
|
|
46
|
+
this.options.slotWatcherPollIntervalMs,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public start(): Promise<void> {
|
|
51
|
+
this.slotWatcher.start();
|
|
52
|
+
return Promise.resolve();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public async stop(): Promise<void> {
|
|
56
|
+
await this.slotWatcher.stop();
|
|
57
|
+
// Cancel every live prover; await teardown.
|
|
58
|
+
const provers = Array.from(this.provers.values());
|
|
59
|
+
this.provers.clear();
|
|
60
|
+
for (const prover of provers) {
|
|
61
|
+
prover.cancel();
|
|
62
|
+
}
|
|
63
|
+
await Promise.allSettled(provers.map(p => p.whenDone()));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Registers a checkpoint with the store. If a prover already exists for the
|
|
68
|
+
* `(number, slot, archive root)` content key, it is reused and marked canonical;
|
|
69
|
+
* otherwise a new prover is constructed.
|
|
70
|
+
*/
|
|
71
|
+
public async addOrUpdate(checkpoint: Checkpoint, data: RegisterCheckpointData): Promise<CheckpointProver> {
|
|
72
|
+
const l1Constants = await this.l2BlockSource.getL1Constants();
|
|
73
|
+
const epochNumber = getEpochAtSlot(checkpoint.header.slotNumber, l1Constants);
|
|
74
|
+
const id = CheckpointProver.idFor(checkpoint);
|
|
75
|
+
|
|
76
|
+
const existing = this.provers.get(id);
|
|
77
|
+
if (existing) {
|
|
78
|
+
existing.markCanonical();
|
|
79
|
+
return existing;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// At most one canonical checkpoint per slot. A different canonical checkpoint at the
|
|
83
|
+
// same slot means the caller forgot to prune the old chain before adding the replacement
|
|
84
|
+
// — surface it rather than silently creating a parallel canonical chain.
|
|
85
|
+
for (const prover of this.provers.values()) {
|
|
86
|
+
if (prover.slotNumber === checkpoint.header.slotNumber && !prover.isPruned()) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Cannot add checkpoint ${checkpoint.number} (archive ${checkpoint.archive.root}) at slot ${checkpoint.header.slotNumber}: ` +
|
|
89
|
+
`a different canonical checkpoint already occupies this slot. Prune it first.`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const prover = this.proverFactoryFn({ ...data, checkpoint, epochNumber }, { ...this.proverDeps, log: this.log });
|
|
95
|
+
this.provers.set(id, prover);
|
|
96
|
+
return prover;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Marks every canonical prover that holds a block above the prune target as pruned. A checkpoint is orphaned by a
|
|
101
|
+
* prune to block `targetBlockNumber` iff its last block sits above the target — including a checkpoint whose range
|
|
102
|
+
* straddles the target (partially orphaned), which block-range marking catches without boundary ambiguity. Keying
|
|
103
|
+
* off the surviving block number (rather than a checkpoint number) is correct even when the source has already
|
|
104
|
+
* re-checkpointed past the divergence: the prune event reports the highest surviving block, which by construction
|
|
105
|
+
* survives on the source, whereas the source's current checkpointed tip can sit above the prune target.
|
|
106
|
+
* Sub-tree work keeps running so a re-add of the same content can pick it up. Returns the affected provers.
|
|
107
|
+
*/
|
|
108
|
+
public markPrunedAboveBlock(targetBlockNumber: BlockNumber): CheckpointProver[] {
|
|
109
|
+
const affected: CheckpointProver[] = [];
|
|
110
|
+
for (const prover of this.provers.values()) {
|
|
111
|
+
const lastBlockNumber = prover.checkpoint.blocks.at(-1)!.number;
|
|
112
|
+
if (lastBlockNumber > targetBlockNumber && !prover.isPruned()) {
|
|
113
|
+
prover.markPruned();
|
|
114
|
+
affected.push(prover);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return affected;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Drops canonical (non-pruned) provers whose epoch is at or below the supplied expired
|
|
122
|
+
* epoch. Once an epoch's proof-submission window has closed, its proof can no longer be
|
|
123
|
+
* accepted on L1, so the prover is no longer needed.
|
|
124
|
+
*/
|
|
125
|
+
public reapExpired(expiredEpoch: EpochNumber): void {
|
|
126
|
+
const reaped: { id: string; checkpointNumber: CheckpointNumber; epochNumber: EpochNumber }[] = [];
|
|
127
|
+
for (const [id, prover] of Array.from(this.provers.entries())) {
|
|
128
|
+
if (prover.isPruned()) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (prover.epochNumber <= expiredEpoch) {
|
|
132
|
+
reaped.push({ id, checkpointNumber: prover.checkpoint.number, epochNumber: prover.epochNumber });
|
|
133
|
+
prover.cancel({ routine: true });
|
|
134
|
+
void prover.whenDone();
|
|
135
|
+
this.provers.delete(id);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (reaped.length > 0) {
|
|
139
|
+
this.log.info(`Reaped ${reaped.length} expired CheckpointProver(s) for expiredEpoch ${expiredEpoch}`, {
|
|
140
|
+
expiredEpoch,
|
|
141
|
+
reapedCount: reaped.length,
|
|
142
|
+
reaped,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Returns the prover with the supplied id, or undefined. */
|
|
148
|
+
public get(id: string): CheckpointProver | undefined {
|
|
149
|
+
return this.provers.get(id);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Returns the prover for the supplied checkpoint (by its content-addressed id), or undefined. */
|
|
153
|
+
public getByCheckpoint(checkpoint: Checkpoint): CheckpointProver | undefined {
|
|
154
|
+
return this.provers.get(CheckpointProver.idFor(checkpoint));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Every prover currently in the store (canonical and pruned), in insertion order. */
|
|
158
|
+
public listAll(): CheckpointProver[] {
|
|
159
|
+
return Array.from(this.provers.values());
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Canonical (non-pruned) provers in the store, sorted by checkpoint number. */
|
|
163
|
+
public listCanonical(): CheckpointProver[] {
|
|
164
|
+
return Array.from(this.provers.values())
|
|
165
|
+
.filter(p => !p.isPruned())
|
|
166
|
+
.sort((a, b) => a.checkpoint.number - b.checkpoint.number);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Canonical provers whose slot is in the supplied epoch's slot range, sorted by
|
|
171
|
+
* checkpoint number.
|
|
172
|
+
*/
|
|
173
|
+
public async listCanonicalForEpoch(epoch: EpochNumber): Promise<CheckpointProver[]> {
|
|
174
|
+
const l1Constants = await this.l2BlockSource.getL1Constants();
|
|
175
|
+
const [fromSlot, toSlot] = getSlotRangeForEpoch(epoch, l1Constants);
|
|
176
|
+
return this.listCanonicalInSlotRange(fromSlot, toSlot);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Canonical provers whose slot falls within `[fromSlot, toSlot]`, sorted by checkpoint number. */
|
|
180
|
+
public listCanonicalInSlotRange(fromSlot: SlotNumber, toSlot: SlotNumber): CheckpointProver[] {
|
|
181
|
+
return this.listCanonical().filter(p => p.slotNumber >= fromSlot && p.slotNumber <= toSlot);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* SlotWatcher tick: reap pruned provers whose slot has passed the chain's synced
|
|
186
|
+
* slot. Once the chain has moved past, no re-add can revive the prover and its
|
|
187
|
+
* content key is unique enough that an actual re-add would create a new entry.
|
|
188
|
+
*
|
|
189
|
+
* Protected so unit tests can drive a single tick without spinning up the
|
|
190
|
+
* `RunningPromise` and waiting on its interval.
|
|
191
|
+
*/
|
|
192
|
+
protected async reapPrunedPastSlot(): Promise<void> {
|
|
193
|
+
let syncedSlot: SlotNumber | undefined;
|
|
194
|
+
try {
|
|
195
|
+
syncedSlot = await this.l2BlockSource.getSyncedL2SlotNumber();
|
|
196
|
+
} catch (err) {
|
|
197
|
+
this.log.debug(`SlotWatcher could not read synced slot`, { error: `${err}` });
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (syncedSlot === undefined) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
for (const [id, prover] of Array.from(this.provers.entries())) {
|
|
204
|
+
if (prover.isPruned() && prover.slotNumber < syncedSlot) {
|
|
205
|
+
this.log.info(`Reaping pruned CheckpointProver ${id}: slot ${prover.slotNumber} < synced ${syncedSlot}`, {
|
|
206
|
+
checkpointNumber: prover.checkpoint.number,
|
|
207
|
+
slotNumber: prover.slotNumber,
|
|
208
|
+
});
|
|
209
|
+
prover.cancel();
|
|
210
|
+
void prover.whenDone();
|
|
211
|
+
this.provers.delete(id);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Sub-set of `L1RollupConstants` actually consumed by the store's slot helpers. */
|
|
218
|
+
export type CheckpointStoreL1Constants = Pick<L1RollupConstants, 'epochDuration'>;
|
package/src/config.ts
CHANGED
|
@@ -1,42 +1,38 @@
|
|
|
1
|
-
import { type ArchiverConfig, archiverConfigMappings } from '@aztec/archiver/config';
|
|
2
1
|
import type { ACVMConfig, BBConfig } from '@aztec/bb-prover/config';
|
|
3
|
-
import { type GenesisStateConfig, genesisStateConfigMappings } from '@aztec/ethereum';
|
|
4
2
|
import {
|
|
5
3
|
type ConfigMappingsType,
|
|
6
4
|
booleanConfigHelper,
|
|
7
5
|
getConfigFromMappings,
|
|
8
6
|
numberConfigHelper,
|
|
7
|
+
pickConfigMappings,
|
|
9
8
|
} from '@aztec/foundation/config';
|
|
10
|
-
import {
|
|
11
|
-
import { type
|
|
9
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
10
|
+
import { type KeyStoreConfig, keyStoreConfigMappings } from '@aztec/node-keystore/config';
|
|
11
|
+
import { ethPrivateKeySchema } from '@aztec/node-keystore/schemas';
|
|
12
|
+
import type { KeyStore } from '@aztec/node-keystore/types';
|
|
12
13
|
import { type SharedNodeConfig, sharedNodeConfigMappings } from '@aztec/node-lib/config';
|
|
13
|
-
import { type P2PConfig, p2pConfigMappings } from '@aztec/p2p/config';
|
|
14
14
|
import {
|
|
15
15
|
type ProverAgentConfig,
|
|
16
16
|
type ProverBrokerConfig,
|
|
17
17
|
proverAgentConfigMappings,
|
|
18
18
|
proverBrokerConfigMappings,
|
|
19
|
-
} from '@aztec/prover-client/broker';
|
|
19
|
+
} from '@aztec/prover-client/broker/config';
|
|
20
20
|
import { type ProverClientUserConfig, bbConfigMappings, proverClientConfigMappings } from '@aztec/prover-client/config';
|
|
21
21
|
import {
|
|
22
|
-
type
|
|
23
|
-
type
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
type ProverPublisherConfig,
|
|
23
|
+
type ProverTxSenderConfig,
|
|
24
|
+
proverPublisherConfigMappings,
|
|
25
|
+
proverTxSenderConfigMappings,
|
|
26
26
|
} from '@aztec/sequencer-client/config';
|
|
27
|
-
import { type
|
|
28
|
-
|
|
29
|
-
export type ProverNodeConfig =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
WorldStateConfig &
|
|
33
|
-
PublisherConfig &
|
|
34
|
-
TxSenderConfig &
|
|
27
|
+
import { type DataStoreConfig, dataConfigMappings } from '@aztec/stdlib/kv-store';
|
|
28
|
+
|
|
29
|
+
export type ProverNodeConfig = ProverClientUserConfig &
|
|
30
|
+
ProverPublisherConfig &
|
|
31
|
+
ProverTxSenderConfig &
|
|
35
32
|
DataStoreConfig &
|
|
36
33
|
KeyStoreConfig &
|
|
37
|
-
SharedNodeConfig &
|
|
38
34
|
SpecificProverNodeConfig &
|
|
39
|
-
|
|
35
|
+
Pick<SharedNodeConfig, 'web3SignerUrl'>;
|
|
40
36
|
|
|
41
37
|
export type SpecificProverNodeConfig = {
|
|
42
38
|
proverNodeMaxPendingJobs: number;
|
|
@@ -49,9 +45,10 @@ export type SpecificProverNodeConfig = {
|
|
|
49
45
|
txGatheringIntervalMs: number;
|
|
50
46
|
txGatheringBatchSize: number;
|
|
51
47
|
txGatheringMaxParallelRequestsPerNode: number;
|
|
48
|
+
proofSubmissionTargetAddress?: EthAddress;
|
|
52
49
|
};
|
|
53
50
|
|
|
54
|
-
const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProverNodeConfig> = {
|
|
51
|
+
export const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProverNodeConfig> = {
|
|
55
52
|
proverNodeMaxPendingJobs: {
|
|
56
53
|
env: 'PROVER_NODE_MAX_PENDING_JOBS',
|
|
57
54
|
description: 'The maximum number of pending jobs for the prover node',
|
|
@@ -65,7 +62,7 @@ const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProverNodeCon
|
|
|
65
62
|
proverNodeMaxParallelBlocksPerEpoch: {
|
|
66
63
|
env: 'PROVER_NODE_MAX_PARALLEL_BLOCKS_PER_EPOCH',
|
|
67
64
|
description: 'The Maximum number of blocks to process in parallel while proving an epoch',
|
|
68
|
-
...numberConfigHelper(
|
|
65
|
+
...numberConfigHelper(0),
|
|
69
66
|
},
|
|
70
67
|
proverNodeFailedEpochStore: {
|
|
71
68
|
env: 'PROVER_NODE_FAILED_EPOCH_STORE',
|
|
@@ -73,7 +70,8 @@ const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProverNodeCon
|
|
|
73
70
|
defaultValue: undefined,
|
|
74
71
|
},
|
|
75
72
|
proverNodeEpochProvingDelayMs: {
|
|
76
|
-
description:
|
|
73
|
+
description:
|
|
74
|
+
'Optional delay in milliseconds to wait for late-arriving events (e.g. reorgs) to settle before starting top-tree proving for an epoch',
|
|
77
75
|
defaultValue: undefined,
|
|
78
76
|
},
|
|
79
77
|
txGatheringIntervalMs: {
|
|
@@ -101,20 +99,24 @@ const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProverNodeCon
|
|
|
101
99
|
description: 'Whether the prover node skips publishing proofs to L1',
|
|
102
100
|
...booleanConfigHelper(false),
|
|
103
101
|
},
|
|
102
|
+
proofSubmissionTargetAddress: {
|
|
103
|
+
env: 'PROVER_NODE_PROOF_SUBMISSION_TARGET_ADDRESS',
|
|
104
|
+
description:
|
|
105
|
+
'Optional L1 address the submitEpochRootProof tx is sent to. Must expose the identical submitEpochRootProof ABI ' +
|
|
106
|
+
'and forward to the rollup. Defaults to the rollup address.',
|
|
107
|
+
parseEnv: (val: string) => EthAddress.fromString(val),
|
|
108
|
+
defaultValue: undefined,
|
|
109
|
+
},
|
|
104
110
|
};
|
|
105
111
|
|
|
106
112
|
export const proverNodeConfigMappings: ConfigMappingsType<ProverNodeConfig> = {
|
|
107
113
|
...dataConfigMappings,
|
|
108
114
|
...keyStoreConfigMappings,
|
|
109
|
-
...archiverConfigMappings,
|
|
110
115
|
...proverClientConfigMappings,
|
|
111
|
-
...
|
|
112
|
-
...
|
|
113
|
-
...getPublisherConfigMappings('PROVER'),
|
|
114
|
-
...getTxSenderConfigMappings('PROVER'),
|
|
116
|
+
...proverPublisherConfigMappings,
|
|
117
|
+
...proverTxSenderConfigMappings,
|
|
115
118
|
...specificProverNodeConfigMappings,
|
|
116
|
-
...
|
|
117
|
-
...sharedNodeConfigMappings,
|
|
119
|
+
...pickConfigMappings(sharedNodeConfigMappings, ['web3SignerUrl']),
|
|
118
120
|
};
|
|
119
121
|
|
|
120
122
|
export function getProverNodeConfigFromEnv(): ProverNodeConfig {
|
|
@@ -141,7 +143,7 @@ function createKeyStoreFromWeb3Signer(config: ProverNodeConfig): KeyStore | unde
|
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
// Also, we need at least one publisher address.
|
|
144
|
-
const publishers = config.
|
|
146
|
+
const publishers = config.proverPublisherAddresses ?? [];
|
|
145
147
|
|
|
146
148
|
if (publishers.length === 0) {
|
|
147
149
|
return undefined;
|
|
@@ -162,8 +164,8 @@ function createKeyStoreFromWeb3Signer(config: ProverNodeConfig): KeyStore | unde
|
|
|
162
164
|
|
|
163
165
|
function createKeyStoreFromPublisherKeys(config: ProverNodeConfig): KeyStore | undefined {
|
|
164
166
|
// Extract the publisher keys from the provided config.
|
|
165
|
-
const publisherKeys = config.
|
|
166
|
-
? config.
|
|
167
|
+
const publisherKeys = config.proverPublisherPrivateKeys
|
|
168
|
+
? config.proverPublisherPrivateKeys.map((k: { getValue: () => string }) => ethPrivateKeySchema.parse(k.getValue()))
|
|
167
169
|
: [];
|
|
168
170
|
|
|
169
171
|
// There must be at least 1.
|
|
@@ -192,7 +194,10 @@ function createKeyStoreFromPublisherKeys(config: ProverNodeConfig): KeyStore | u
|
|
|
192
194
|
|
|
193
195
|
export function createKeyStoreForProver(config: ProverNodeConfig): KeyStore | undefined {
|
|
194
196
|
if (config.web3SignerUrl !== undefined && config.web3SignerUrl.length > 0) {
|
|
195
|
-
|
|
197
|
+
const keyStore = createKeyStoreFromWeb3Signer(config);
|
|
198
|
+
if (keyStore) {
|
|
199
|
+
return keyStore;
|
|
200
|
+
}
|
|
196
201
|
}
|
|
197
202
|
|
|
198
203
|
return createKeyStoreFromPublisherKeys(config);
|