Skip to content

Commit f181313

Browse files
committed
feat(prover-node): checkpoint-driven optimistic proving
Drives the prover-node onto the split `CheckpointSubTreeOrchestrator` + `TopTreeOrchestrator` pair, with checkpoint-driven proving that pipelines sub-trees against tx-gathering and the top-tree against the in-flight sub-trees. ## What's new ### `prover-node` — `EpochProvingJob` job-model rewrite `EpochProvingJob` becomes an orchestrator over a `Map<string, CheckpointJob>` keyed by `${number}:${slot}`. Each `CheckpointJob` owns a single `CheckpointSubTreeOrchestrator` with its own per-checkpoint context (txs, attestations, previous-block header, l1ToL2 messages, archive sibling path). A `TopTreeJob` drives the epoch root rollup once all checkpoint sub-trees have started block-level proving. Public API: - `registerCheckpoint` — synchronous; sets up sub-tree, kicks off chonk-verifier cache fill, attaches the `blockProofs` promise to the eventual top-tree job. - `provideTxs` — supplies simulated txs, transitions the checkpoint job from registered → block-proving. - `removeCheckpoint(synchronous, idempotent)` — drops a single checkpoint by `(number, slot)`, fire-and-forget cancels its sub-tree. Tolerates re-add of the same checkpoint number under a different slot. - `removeCheckpointsAfter`, `getCheckpointCount`, `getCheckpointNumbers`, `cancelPendingCheckpoints`. ### `prover-node` — `L2BlockStream`-driven checkpoint pipeline The prover-node consumes `chain-checkpointed` / `chain-pruned` events from an `L2BlockStream` rooted at the first block of the first unproven epoch. On each `chain-checkpointed`: 1. Resolve the epoch via `getEpochAtSlot`. 2. Get-or-create the per-epoch `EpochProvingJob`. 3. Detached-task gather txs + register the checkpoint with the job. On `chain-pruned`: call `removeCheckpointsAfter(threshold)` on every job whose first checkpoint sits at or above the threshold. Pending gather tasks are cancelled via `AbortSignal`. Finalization is driven by the union of three signals: epoch-monitor sees the epoch close on L1, a checkpoint for a strictly later epoch arrives, or all expected checkpoints (per archiver) are registered while the epoch is already complete on L1. ### `prover-node` — reorg-after-finalization restart When the L2BlockStream emits a prune that retroactively invalidates an epoch already in finalize, the prover-node aborts the in-flight publish, clears the job, and restarts proving from the new tip. ### e2e - New `epochs_optimistic_proving.parallel.test.ts`: full e2e covering the pipelining, replacement-checkpoint reuse, and reorg-during-proving paths. - `epochs_proof_fails`, `epochs_upload_failed_proof`, `epochs_long_proving_time`, `epochs_multi_proof` updated to assert the new in-flight epoch behaviour. ## What's removed `EpochProver` interface and `ServerEpochProver` are removed: the prover-node no longer drives a single-class epoch prover, so the legacy API has no production callers. `ProvingOrchestrator` survives only as a base class for `CheckpointSubTreeOrchestrator` and as the single-class driver used by `prover-client`'s integration tests; it no longer implements `EpochProver`. ## Test plan - `yarn workspace @aztec/prover-client test` — 261 tests pass. - `yarn workspace @aztec/prover-node test` — 89 tests pass. - e2e tests covering optimistic proving, reorgs during proving, failed proof publish, and multi-checkpoint flows are included in this PR.
1 parent da67473 commit f181313

24 files changed

Lines changed: 3416 additions & 856 deletions

spartan/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ environments/*
1919
!environments/devnet.env
2020
!environments/block-capacity.env
2121
!environments/next-net.env
22+
!environments/next-net-clone.env
2223
!environments/next-scenario.env
2324
!environments/scenario.local.env
2425
!environments/source-env.sh
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
CREATE_ETH_DEVNET=false
2+
GCP_REGION=us-west1-a
3+
CLUSTER=aztec-gke-private
4+
NETWORK=next-net
5+
NAMESPACE=${NAMESPACE:-next-net-clone}
6+
DESTROY_NAMESPACE=true
7+
ETHEREUM_CHAIN_ID=11155111
8+
ETHEREUM_RPC_URLS=REPLACE_WITH_GCP_SECRET
9+
ETHEREUM_CONSENSUS_HOST_URLS=REPLACE_WITH_GCP_SECRET
10+
ETHEREUM_CONSENSUS_HOST_API_KEYS=REPLACE_WITH_GCP_SECRET
11+
ETHEREUM_CONSENSUS_HOST_API_KEY_HEADERS=REPLACE_WITH_GCP_SECRET
12+
FUNDING_PRIVATE_KEY=REPLACE_WITH_GCP_SECRET
13+
LABS_INFRA_MNEMONIC=REPLACE_WITH_GCP_SECRET
14+
ROLLUP_DEPLOYMENT_PRIVATE_KEY=REPLACE_WITH_GCP_SECRET
15+
OTEL_COLLECTOR_ENDPOINT=REPLACE_WITH_GCP_SECRET
16+
VERIFY_CONTRACTS=false
17+
ETHERSCAN_API_KEY=REPLACE_WITH_GCP_SECRET
18+
DEPLOY_INTERNAL_BOOTNODE=true
19+
STORE_SNAPSHOT_URL=
20+
#BLOB_BUCKET_DIRECTORY=${BLOB_BUCKET_DIRECTORY:-next-net/blobs}
21+
#BLOB_FILE_STORE_URLS=","
22+
#TX_FILE_STORE_ENABLED=true
23+
#TX_FILE_STORE_BUCKET_DIRECTORY=${TX_FILE_STORE_BUCKET_DIRECTORY:-next-net/txs}
24+
#TX_COLLECTION_FILE_STORE_URLS="https://aztec-labs-snapshots.com/${TX_FILE_STORE_BUCKET_DIRECTORY}"
25+
R2_ACCESS_KEY_ID=REPLACE_WITH_GCP_SECRET
26+
R2_SECRET_ACCESS_KEY=REPLACE_WITH_GCP_SECRET
27+
#PROVER_FAILED_PROOF_STORE=gs://aztec-develop/next-net/failed-proofs
28+
#L1_TX_FAILED_STORE=gs://aztec-develop/next-net/failed-l1-txs
29+
TEST_ACCOUNTS=true
30+
SPONSORED_FPC=true
31+
32+
SEQ_ENABLE_PROPOSER_PIPELINING=true
33+
SEQ_MIN_TX_PER_BLOCK=1
34+
SEQ_MAX_TX_PER_CHECKPOINT=12
35+
36+
# Build checkpoint even if block is empty.
37+
SEQ_BUILD_CHECKPOINT_IF_EMPTY=true
38+
SEQ_BLOCK_DURATION_MS=5500
39+
40+
AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET=2
41+
AZTEC_LAG_IN_EPOCHS_FOR_RANDAO=2
42+
AZTEC_INBOX_LAG=2
43+
44+
VALIDATOR_REPLICAS=4
45+
VALIDATORS_PER_NODE=12
46+
VALIDATOR_PUBLISHERS_PER_REPLICA=4
47+
VALIDATOR_PUBLISHER_MNEMONIC_START_INDEX=5000
48+
49+
PUBLISHERS_PER_PROVER=2
50+
PROVER_PUBLISHER_MNEMONIC_START_INDEX=8000
51+
52+
BOT_TRANSFERS_REPLICAS=1
53+
BOT_TRANSFERS_TX_INTERVAL_SECONDS=250
54+
BOT_TRANSFERS_FOLLOW_CHAIN=PENDING
55+
56+
BOT_SWAPS_REPLICAS=1
57+
BOT_SWAPS_FOLLOW_CHAIN=PENDING
58+
BOT_SWAPS_TX_INTERVAL_SECONDS=350
59+
60+
CREATE_ROLLUP_CONTRACTS=true
61+
62+
DEBUG_P2P_INSTRUMENT_MESSAGES=true
63+
64+
RPC_INGRESS_ENABLED=false
65+
#RPC_INGRESS_HOSTS='["nextnet.aztec-labs.com"]'
66+
#RPC_INGRESS_STATIC_IP_NAME=nextnet-rpc-ip
67+
#RPC_INGRESS_SSL_CERT_NAMES='["nextnet-rpc-cert"]'
68+
69+
VALIDATOR_HA_REPLICAS=1
70+
VALIDATOR_RESOURCE_PROFILE="prod-spot"
71+
72+
REAL_VERIFIER=true
73+
AZTEC_SLOT_DURATION=72
74+
AZTEC_EPOCH_DURATION=32
75+
AZTEC_TARGET_COMMITTEE_SIZE=48
76+
AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET=2
77+
AZTEC_LAG_IN_EPOCHS_FOR_RANDAO=2
78+
AZTEC_PROOF_SUBMISSION_EPOCHS=1
79+

yarn-project/end-to-end/bootstrap.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ function test_cmds {
3333
echo "$prefix:NAME=e2e_prover_full_fake FAKE_PROOFS=1 $run_test_script simple e2e_prover/full"
3434
fi
3535
echo "$prefix:TIMEOUT=15m:NAME=e2e_block_building $(set_dump_avm e2e_block_building) $run_test_script simple e2e_block_building"
36+
echo "$prefix:TIMEOUT=15m:NAME=e2e_epochs/epochs_long_proving_time $run_test_script simple src/e2e_epochs/epochs_long_proving_time.test.ts"
3637

3738
local tests=(
3839
# List all standalone and nested tests, except for the ones listed above.
39-
src/e2e_!(prover)/*.test.ts
40+
src/e2e_!(prover|epochs)/*.test.ts
41+
src/e2e_epochs/!(epochs_long_proving_time).test.ts
4042
src/e2e_p2p/reqresp/*.test.ts
4143
src/e2e_!(block_building).test.ts
4244
)

yarn-project/end-to-end/src/e2e_epochs/epochs_long_proving_time.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { jest } from '@jest/globals';
66

77
import { EpochsTestContext } from './epochs_test.js';
88

9-
jest.setTimeout(1000 * 60 * 10);
9+
jest.setTimeout(1000 * 60 * 15);
1010

1111
describe('e2e_epochs/epochs_long_proving_time', () => {
1212
let logger: Logger;
@@ -24,11 +24,14 @@ describe('e2e_epochs/epochs_long_proving_time', () => {
2424
const { aztecSlotDuration } = EpochsTestContext.getSlotDurations({ aztecEpochDuration });
2525
const epochDurationInSeconds = aztecSlotDuration * aztecEpochDuration;
2626
const proverTestDelayMs = (epochDurationInSeconds * 1000 * 3) / 4;
27+
// Each epoch takes ~3 epochs to prove, so the broker needs to keep results for
28+
// at least that many epochs to avoid rejecting jobs as stale.
2729
test = await EpochsTestContext.setup({
2830
aztecEpochDuration,
2931
aztecProofSubmissionEpochs: 1000, // Effectively don't re-org
3032
proverTestDelayMs,
31-
proverNodeMaxPendingJobs: 1, // We test for only a single job at once
33+
proverNodeMaxPendingJobs: 2,
34+
proverBrokerMaxEpochsToKeepResultsFor: 10,
3235
enableProposerPipelining: true,
3336
});
3437
({ logger, monitor, L1_BLOCK_TIME_IN_S } = test);
@@ -59,10 +62,7 @@ describe('e2e_epochs/epochs_long_proving_time', () => {
5962
// At least 3 epochs should have passed after the proven one (though we add a -1 just in case)
6063
expect(monitor.checkpointNumber).toBeGreaterThanOrEqual(targetProvenEpochs * test.epochDuration * 3 - 1);
6164

62-
// We expect maxJobCount to equal 1, since the prover node epoch monitor defines an epoch as ready to be proven
63-
// only if the previous one has already been proven. We can relax this check if we want to support multiple epochs
64-
// to be proven in parallel, in which case we should update the assertion below.
65-
expect(maxJobCount).toEqual(1);
65+
expect(maxJobCount).toBeLessThanOrEqual(2);
6666
logger.info(`Test succeeded`);
6767
});
6868
});

yarn-project/end-to-end/src/e2e_epochs/epochs_multi_proof.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,18 @@ describe('e2e_epochs/epochs_multi_proof', () => {
4646
// This prevents the race condition where multiple provers submit to L1 at the same time
4747
test.proverNodes.forEach((proverAztecNode, index) => {
4848
const proverManager = proverAztecNode.getProverNode()!.getProver();
49-
const origCreateEpochProver = proverManager.createEpochProver.bind(proverManager);
50-
proverManager.createEpochProver = () => {
51-
const epochProver = origCreateEpochProver();
52-
const origFinalizeEpoch = epochProver.finalizeEpoch.bind(epochProver);
53-
epochProver.finalizeEpoch = async () => {
54-
const result = await origFinalizeEpoch();
49+
const origCreateTopTree = proverManager.createTopTreeOrchestrator.bind(proverManager);
50+
proverManager.createTopTreeOrchestrator = () => {
51+
const topTree = origCreateTopTree();
52+
const origProve = topTree.prove.bind(topTree);
53+
topTree.prove = async (...args: Parameters<typeof origProve>) => {
54+
const result = await origProve(...args);
5555
const sleepTime = index * 1000 * test.constants.ethereumSlotDuration;
56-
logger.warn(`Delaying finalizeEpoch for prover node ${index} by ${sleepTime}ms`);
56+
logger.warn(`Delaying top-tree prove for prover node ${index} by ${sleepTime}ms`);
5757
await sleep(sleepTime);
5858
return result;
5959
};
60-
return epochProver;
60+
return topTree;
6161
};
6262
});
6363

0 commit comments

Comments
 (0)