@aztec/prover-client 0.65.2 → 0.66.0

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.
Files changed (78) hide show
  1. package/dest/block_builder/index.d.ts +6 -0
  2. package/dest/block_builder/index.d.ts.map +1 -0
  3. package/dest/block_builder/index.js +2 -0
  4. package/dest/block_builder/light.d.ts +32 -0
  5. package/dest/block_builder/light.d.ts.map +1 -0
  6. package/dest/block_builder/light.js +71 -0
  7. package/dest/index.d.ts +1 -2
  8. package/dest/index.d.ts.map +1 -1
  9. package/dest/index.js +2 -3
  10. package/dest/mocks/fixtures.d.ts +1 -2
  11. package/dest/mocks/fixtures.d.ts.map +1 -1
  12. package/dest/mocks/fixtures.js +3 -7
  13. package/dest/mocks/test_context.d.ts +28 -10
  14. package/dest/mocks/test_context.d.ts.map +1 -1
  15. package/dest/mocks/test_context.js +58 -21
  16. package/dest/orchestrator/epoch-proving-state.d.ts +5 -6
  17. package/dest/orchestrator/epoch-proving-state.d.ts.map +1 -1
  18. package/dest/orchestrator/epoch-proving-state.js +10 -12
  19. package/dest/orchestrator/orchestrator.d.ts +8 -6
  20. package/dest/orchestrator/orchestrator.d.ts.map +1 -1
  21. package/dest/orchestrator/orchestrator.js +83 -72
  22. package/dest/orchestrator/tx-proving-state.d.ts +0 -1
  23. package/dest/orchestrator/tx-proving-state.d.ts.map +1 -1
  24. package/dest/orchestrator/tx-proving-state.js +2 -34
  25. package/dest/prover-client/factory.d.ts +6 -0
  26. package/dest/prover-client/factory.d.ts.map +1 -0
  27. package/dest/prover-client/factory.js +6 -0
  28. package/dest/prover-client/index.d.ts +3 -0
  29. package/dest/prover-client/index.d.ts.map +1 -0
  30. package/dest/prover-client/index.js +3 -0
  31. package/dest/{tx-prover/tx-prover.d.ts → prover-client/prover-client.d.ts} +8 -11
  32. package/dest/prover-client/prover-client.d.ts.map +1 -0
  33. package/dest/prover-client/prover-client.js +107 -0
  34. package/dest/proving_broker/factory.d.ts +2 -1
  35. package/dest/proving_broker/factory.d.ts.map +1 -1
  36. package/dest/proving_broker/factory.js +4 -4
  37. package/dest/proving_broker/proving_agent.d.ts +5 -0
  38. package/dest/proving_broker/proving_agent.d.ts.map +1 -1
  39. package/dest/proving_broker/proving_agent.js +13 -2
  40. package/dest/proving_broker/proving_agent_instrumentation.d.ts +8 -0
  41. package/dest/proving_broker/proving_agent_instrumentation.d.ts.map +1 -0
  42. package/dest/proving_broker/proving_agent_instrumentation.js +16 -0
  43. package/dest/proving_broker/proving_broker.d.ts +6 -1
  44. package/dest/proving_broker/proving_broker.d.ts.map +1 -1
  45. package/dest/proving_broker/proving_broker.js +37 -4
  46. package/dest/proving_broker/proving_broker_database/persisted.d.ts +3 -1
  47. package/dest/proving_broker/proving_broker_database/persisted.d.ts.map +1 -1
  48. package/dest/proving_broker/proving_broker_database/persisted.js +10 -2
  49. package/dest/proving_broker/proving_broker_instrumentation.d.ts +25 -0
  50. package/dest/proving_broker/proving_broker_instrumentation.d.ts.map +1 -0
  51. package/dest/proving_broker/proving_broker_instrumentation.js +91 -0
  52. package/dest/test/mock_prover.d.ts +1 -1
  53. package/dest/test/mock_prover.d.ts.map +1 -1
  54. package/dest/test/mock_prover.js +4 -3
  55. package/package.json +14 -13
  56. package/src/block_builder/index.ts +6 -0
  57. package/src/block_builder/light.ts +117 -0
  58. package/src/index.ts +1 -2
  59. package/src/mocks/fixtures.ts +2 -14
  60. package/src/mocks/test_context.ts +80 -24
  61. package/src/orchestrator/epoch-proving-state.ts +10 -13
  62. package/src/orchestrator/orchestrator.ts +97 -77
  63. package/src/orchestrator/tx-proving-state.ts +1 -56
  64. package/src/{tx-prover → prover-client}/factory.ts +4 -3
  65. package/src/prover-client/index.ts +2 -0
  66. package/src/{tx-prover/tx-prover.ts → prover-client/prover-client.ts} +23 -13
  67. package/src/proving_broker/factory.ts +7 -3
  68. package/src/proving_broker/proving_agent.ts +16 -1
  69. package/src/proving_broker/proving_agent_instrumentation.ts +21 -0
  70. package/src/proving_broker/proving_broker.ts +46 -3
  71. package/src/proving_broker/proving_broker_database/persisted.ts +17 -2
  72. package/src/proving_broker/proving_broker_instrumentation.ts +130 -0
  73. package/src/test/mock_prover.ts +3 -2
  74. package/dest/tx-prover/factory.d.ts +0 -6
  75. package/dest/tx-prover/factory.d.ts.map +0 -1
  76. package/dest/tx-prover/factory.js +0 -6
  77. package/dest/tx-prover/tx-prover.d.ts.map +0 -1
  78. package/dest/tx-prover/tx-prover.js +0 -110
@@ -1,6 +1,6 @@
1
1
  import { type BBProverConfig } from '@aztec/bb-prover';
2
2
  import {
3
- type MerkleTreeWriteOperations,
3
+ type L2Block,
4
4
  type ProcessedTx,
5
5
  type ProcessedTxHandler,
6
6
  type PublicExecutionRequest,
@@ -8,10 +8,13 @@ import {
8
8
  type Tx,
9
9
  type TxValidator,
10
10
  } from '@aztec/circuit-types';
11
- import { type Gas, type GlobalVariables, Header } from '@aztec/circuits.js';
11
+ import { makeBloatedProcessedTx } from '@aztec/circuit-types/test';
12
+ import { type AppendOnlyTreeSnapshot, type Gas, type GlobalVariables, Header } from '@aztec/circuits.js';
13
+ import { times } from '@aztec/foundation/collection';
12
14
  import { Fr } from '@aztec/foundation/fields';
13
15
  import { type DebugLogger } from '@aztec/foundation/log';
14
- import { openTmpStore } from '@aztec/kv-store/utils';
16
+ import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types';
17
+ import { protocolContractTreeRoot } from '@aztec/protocol-contracts';
15
18
  import {
16
19
  PublicProcessor,
17
20
  PublicTxSimulator,
@@ -20,32 +23,34 @@ import {
20
23
  type WorldStateDB,
21
24
  } from '@aztec/simulator';
22
25
  import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
23
- import { MerkleTrees } from '@aztec/world-state';
26
+ import { type MerkleTreeAdminDatabase } from '@aztec/world-state';
24
27
  import { NativeWorldStateService } from '@aztec/world-state/native';
25
28
 
26
29
  import { jest } from '@jest/globals';
27
30
  import * as fs from 'fs/promises';
28
- import { type MockProxy, mock } from 'jest-mock-extended';
31
+ import { mock } from 'jest-mock-extended';
29
32
 
30
33
  import { TestCircuitProver } from '../../../bb-prover/src/test/test_circuit_prover.js';
31
34
  import { AvmFinalizedCallResult } from '../../../simulator/src/avm/avm_contract_call_result.js';
32
35
  import { type AvmPersistableStateManager } from '../../../simulator/src/avm/journal/journal.js';
36
+ import { buildBlock } from '../block_builder/light.js';
33
37
  import { ProvingOrchestrator } from '../orchestrator/index.js';
34
38
  import { MemoryProvingQueue } from '../prover-agent/memory-proving-queue.js';
35
39
  import { ProverAgent } from '../prover-agent/prover-agent.js';
36
40
  import { getEnvironmentConfig, getSimulationProvider, makeGlobals } from './fixtures.js';
37
41
 
38
42
  export class TestContext {
43
+ private headers: Map<number, Header> = new Map();
44
+
39
45
  constructor(
40
46
  public publicTxSimulator: PublicTxSimulator,
41
- public worldStateDB: MockProxy<WorldStateDB>,
47
+ public worldState: MerkleTreeAdminDatabase,
42
48
  public publicProcessor: PublicProcessor,
43
49
  public simulationProvider: SimulationProvider,
44
50
  public globalVariables: GlobalVariables,
45
- public actualDb: MerkleTreeWriteOperations,
46
51
  public prover: ServerCircuitProver,
47
52
  public proverAgent: ProverAgent,
48
- public orchestrator: ProvingOrchestrator,
53
+ public orchestrator: TestProvingOrchestrator,
49
54
  public blockNumber: number,
50
55
  public directoriesToCleanup: string[],
51
56
  public logger: DebugLogger,
@@ -57,11 +62,10 @@ export class TestContext {
57
62
 
58
63
  static async new(
59
64
  logger: DebugLogger,
60
- worldState: 'native' | 'legacy' = 'native',
61
65
  proverCount = 4,
62
66
  createProver: (bbConfig: BBProverConfig) => Promise<ServerCircuitProver> = _ =>
63
67
  Promise.resolve(new TestCircuitProver(new NoopTelemetryClient(), new WASMSimulator())),
64
- blockNumber = 3,
68
+ blockNumber = 1,
65
69
  ) {
66
70
  const directoriesToCleanup: string[] = [];
67
71
  const globalVariables = makeGlobals(blockNumber);
@@ -70,18 +74,9 @@ export class TestContext {
70
74
  const telemetry = new NoopTelemetryClient();
71
75
 
72
76
  // Separated dbs for public processor and prover - see public_processor for context
73
- let publicDb: MerkleTreeWriteOperations;
74
- let proverDb: MerkleTreeWriteOperations;
77
+ const ws = await NativeWorldStateService.tmp();
78
+ const publicDb = await ws.fork();
75
79
 
76
- if (worldState === 'native') {
77
- const ws = await NativeWorldStateService.tmp();
78
- publicDb = await ws.fork();
79
- proverDb = await ws.fork();
80
- } else {
81
- const ws = await MerkleTrees.new(openTmpStore(), telemetry);
82
- publicDb = await ws.getLatest();
83
- proverDb = await ws.getLatest();
84
- }
85
80
  worldStateDB.getMerkleInterface.mockReturnValue(publicDb);
86
81
 
87
82
  const publicTxSimulator = new PublicTxSimulator(publicDb, worldStateDB, telemetry, globalVariables);
@@ -118,7 +113,7 @@ export class TestContext {
118
113
  }
119
114
 
120
115
  const queue = new MemoryProvingQueue(telemetry);
121
- const orchestrator = new ProvingOrchestrator(proverDb, queue, telemetry, Fr.ZERO);
116
+ const orchestrator = new TestProvingOrchestrator(ws, queue, telemetry, Fr.ZERO);
122
117
  const agent = new ProverAgent(localProver, proverCount);
123
118
 
124
119
  queue.start();
@@ -126,11 +121,10 @@ export class TestContext {
126
121
 
127
122
  return new this(
128
123
  publicTxSimulator,
129
- worldStateDB,
124
+ ws,
130
125
  processor,
131
126
  simulationProvider,
132
127
  globalVariables,
133
- proverDb,
134
128
  localProver,
135
129
  agent,
136
130
  orchestrator,
@@ -140,6 +134,16 @@ export class TestContext {
140
134
  );
141
135
  }
142
136
 
137
+ public getFork() {
138
+ return this.worldState.fork();
139
+ }
140
+
141
+ public getHeader(blockNumber: 0): Header;
142
+ public getHeader(blockNumber: number): Header | undefined;
143
+ public getHeader(blockNumber = 0) {
144
+ return blockNumber === 0 ? this.worldState.getCommitted().getInitialHeader() : this.headers.get(blockNumber);
145
+ }
146
+
143
147
  async cleanup() {
144
148
  await this.proverAgent.stop();
145
149
  for (const dir of this.directoriesToCleanup.filter(x => x !== '')) {
@@ -147,6 +151,42 @@ export class TestContext {
147
151
  }
148
152
  }
149
153
 
154
+ public makeProcessedTx(opts?: Parameters<typeof makeBloatedProcessedTx>[0]): ProcessedTx;
155
+ public makeProcessedTx(seed?: number): ProcessedTx;
156
+ public makeProcessedTx(seedOrOpts?: Parameters<typeof makeBloatedProcessedTx>[0] | number): ProcessedTx {
157
+ const opts = typeof seedOrOpts === 'number' ? { seed: seedOrOpts } : seedOrOpts;
158
+ const blockNum = (opts?.globalVariables ?? this.globalVariables).blockNumber.toNumber();
159
+ const header = this.getHeader(blockNum - 1);
160
+ return makeBloatedProcessedTx({
161
+ header,
162
+ vkTreeRoot: getVKTreeRoot(),
163
+ protocolContractTreeRoot,
164
+ globalVariables: this.globalVariables,
165
+ ...opts,
166
+ });
167
+ }
168
+
169
+ /** Creates a block with the given number of txs and adds it to world-state */
170
+ public async makePendingBlock(
171
+ numTxs: number,
172
+ numMsgs: number = 0,
173
+ blockNumOrGlobals: GlobalVariables | number = this.globalVariables,
174
+ makeProcessedTxOpts: (index: number) => Partial<Parameters<typeof makeBloatedProcessedTx>[0]> = () => ({}),
175
+ ) {
176
+ const globalVariables = typeof blockNumOrGlobals === 'number' ? makeGlobals(blockNumOrGlobals) : blockNumOrGlobals;
177
+ const blockNum = globalVariables.blockNumber.toNumber();
178
+ const db = await this.worldState.fork();
179
+ const msgs = times(numMsgs, i => new Fr(blockNum * 100 + i));
180
+ const txs = times(numTxs, i =>
181
+ this.makeProcessedTx({ seed: i + blockNum * 1000, globalVariables, ...makeProcessedTxOpts(i) }),
182
+ );
183
+
184
+ const block = await buildBlock(txs, globalVariables, msgs, db);
185
+ this.headers.set(blockNum, block.header);
186
+ await this.worldState.handleL2BlockAndMessages(block, msgs);
187
+ return { block, txs, msgs };
188
+ }
189
+
150
190
  public async processPublicFunctions(
151
191
  txs: Tx[],
152
192
  maxTransactions: number,
@@ -217,3 +257,19 @@ export class TestContext {
217
257
  return await this.publicProcessor.process(txs, maxTransactions, txHandler, txValidator);
218
258
  }
219
259
  }
260
+
261
+ class TestProvingOrchestrator extends ProvingOrchestrator {
262
+ public isVerifyBuiltBlockAgainstSyncedStateEnabled = false;
263
+
264
+ // Disable this check by default, since it requires seeding world state with the block being built
265
+ // This is only enabled in some tests with multiple blocks that populate the pending chain via makePendingBlock
266
+ protected override verifyBuiltBlockAgainstSyncedState(
267
+ l2Block: L2Block,
268
+ newArchive: AppendOnlyTreeSnapshot,
269
+ ): Promise<void> {
270
+ if (this.isVerifyBuiltBlockAgainstSyncedStateEnabled) {
271
+ return super.verifyBuiltBlockAgainstSyncedState(l2Block, newArchive);
272
+ }
273
+ return Promise.resolve();
274
+ }
275
+ }
@@ -50,20 +50,16 @@ export class EpochProvingState {
50
50
  private mergeRollupInputs: BlockMergeRollupInputData[] = [];
51
51
  public rootRollupPublicInputs: RootRollupPublicInputs | undefined;
52
52
  public finalProof: Proof | undefined;
53
- public blocks: BlockProvingState[] = [];
53
+ public blocks: (BlockProvingState | undefined)[] = [];
54
54
 
55
55
  constructor(
56
56
  public readonly epochNumber: number,
57
+ public readonly firstBlockNumber: number,
57
58
  public readonly totalNumBlocks: number,
58
59
  private completionCallback: (result: ProvingResult) => void,
59
60
  private rejectionCallback: (reason: string) => void,
60
61
  ) {}
61
62
 
62
- /** Returns the current block proving state */
63
- public get currentBlock(): BlockProvingState | undefined {
64
- return this.blocks.at(-1);
65
- }
66
-
67
63
  // Returns the number of levels of merge rollups
68
64
  public get numMergeLevels() {
69
65
  const totalLeaves = Math.max(2, this.totalNumBlocks);
@@ -110,9 +106,10 @@ export class EpochProvingState {
110
106
  archiveTreeSnapshot: AppendOnlyTreeSnapshot,
111
107
  archiveTreeRootSiblingPath: Tuple<Fr, typeof ARCHIVE_HEIGHT>,
112
108
  previousBlockHash: Fr,
113
- ) {
109
+ ): BlockProvingState {
110
+ const index = globalVariables.blockNumber.toNumber() - this.firstBlockNumber;
114
111
  const block = new BlockProvingState(
115
- this.blocks.length,
112
+ index,
116
113
  numTxs,
117
114
  globalVariables,
118
115
  padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP),
@@ -124,11 +121,11 @@ export class EpochProvingState {
124
121
  previousBlockHash,
125
122
  this,
126
123
  );
127
- this.blocks.push(block);
128
- if (this.blocks.length === this.totalNumBlocks) {
124
+ this.blocks[index] = block;
125
+ if (this.blocks.filter(b => !!b).length === this.totalNumBlocks) {
129
126
  this.provingStateLifecycle = PROVING_STATE_LIFECYCLE.PROVING_STATE_FULL;
130
127
  }
131
- return this.blocks.length - 1;
128
+ return block;
132
129
  }
133
130
 
134
131
  // Returns true if this proving state is still valid, false otherwise
@@ -180,8 +177,8 @@ export class EpochProvingState {
180
177
  }
181
178
 
182
179
  // Returns a specific transaction proving state
183
- public getBlockProvingState(index: number) {
184
- return this.blocks[index];
180
+ public getBlockProvingStateByBlockNumber(blockNumber: number) {
181
+ return this.blocks.find(block => block?.blockNumber === blockNumber);
185
182
  }
186
183
 
187
184
  // Returns a set of merge rollup inputs
@@ -7,6 +7,7 @@ import {
7
7
  } from '@aztec/circuit-types';
8
8
  import {
9
9
  type EpochProver,
10
+ type ForkMerkleTreeOperations,
10
11
  type MerkleTreeWriteOperations,
11
12
  type ProofAndVerificationKey,
12
13
  } from '@aztec/circuit-types/interfaces';
@@ -14,6 +15,7 @@ import { type CircuitName } from '@aztec/circuit-types/stats';
14
15
  import {
15
16
  AVM_PROOF_LENGTH_IN_FIELDS,
16
17
  AVM_VERIFICATION_KEY_LENGTH_IN_FIELDS,
18
+ type AppendOnlyTreeSnapshot,
17
19
  type BaseOrMergeRollupPublicInputs,
18
20
  BaseParityInputs,
19
21
  type BaseRollupHints,
@@ -38,7 +40,7 @@ import {
38
40
  makeEmptyRecursiveProof,
39
41
  } from '@aztec/circuits.js';
40
42
  import { makeTuple } from '@aztec/foundation/array';
41
- import { padArrayEnd } from '@aztec/foundation/collection';
43
+ import { maxBy, padArrayEnd } from '@aztec/foundation/collection';
42
44
  import { AbortError } from '@aztec/foundation/error';
43
45
  import { createDebugLogger } from '@aztec/foundation/log';
44
46
  import { promiseWithResolvers } from '@aztec/foundation/promise';
@@ -98,9 +100,10 @@ export class ProvingOrchestrator implements EpochProver {
98
100
 
99
101
  private provingPromise: Promise<ProvingResult> | undefined = undefined;
100
102
  private metrics: ProvingOrchestratorMetrics;
103
+ private dbs: Map<number, MerkleTreeWriteOperations> = new Map();
101
104
 
102
105
  constructor(
103
- private db: MerkleTreeWriteOperations,
106
+ private dbProvider: ForkMerkleTreeOperations,
104
107
  private prover: ServerCircuitProver,
105
108
  telemetryClient: TelemetryClient,
106
109
  private readonly proverId: Fr = Fr.ZERO,
@@ -123,14 +126,14 @@ export class ProvingOrchestrator implements EpochProver {
123
126
  this.paddingTxProof = undefined;
124
127
  }
125
128
 
126
- public startNewEpoch(epochNumber: number, totalNumBlocks: number) {
129
+ public startNewEpoch(epochNumber: number, firstBlockNumber: number, totalNumBlocks: number) {
127
130
  const { promise: _promise, resolve, reject } = promiseWithResolvers<ProvingResult>();
128
131
  const promise = _promise.catch((reason): ProvingResult => ({ status: 'failure', reason }));
129
132
  if (totalNumBlocks <= 0 || !Number.isInteger(totalNumBlocks)) {
130
133
  throw new Error(`Invalid number of blocks for epoch (got ${totalNumBlocks})`);
131
134
  }
132
135
  logger.info(`Starting epoch ${epochNumber} with ${totalNumBlocks} blocks`);
133
- this.provingState = new EpochProvingState(epochNumber, totalNumBlocks, resolve, reject);
136
+ this.provingState = new EpochProvingState(epochNumber, firstBlockNumber, totalNumBlocks, resolve, reject);
134
137
  this.provingPromise = promise;
135
138
  }
136
139
 
@@ -159,24 +162,14 @@ export class ProvingOrchestrator implements EpochProver {
159
162
  throw new Error(`Invalid number of txs for block (got ${numTxs})`);
160
163
  }
161
164
 
162
- if (this.provingState.currentBlock && !this.provingState.currentBlock.block) {
163
- throw new Error(`Must end previous block before starting a new one`);
164
- }
165
-
166
- // TODO(palla/prover): Store block number in the db itself to make this check more reliable,
167
- // and turn this warning into an exception that we throw.
168
- const { blockNumber } = globalVariables;
169
- const dbBlockNumber = (await this.db.getTreeInfo(MerkleTreeId.ARCHIVE)).size - 1n;
170
- if (dbBlockNumber !== blockNumber.toBigInt() - 1n) {
171
- logger.warn(
172
- `Database is at wrong block number (starting block ${blockNumber.toBigInt()} with db at ${dbBlockNumber})`,
173
- );
174
- }
175
-
176
165
  logger.info(
177
- `Starting block ${globalVariables.blockNumber} for slot ${globalVariables.slotNumber} with ${numTxs} transactions`,
166
+ `Starting block ${globalVariables.blockNumber.toNumber()} for slot ${globalVariables.slotNumber.toNumber()} with ${numTxs} transactions`,
178
167
  );
179
168
 
169
+ // Fork world state at the end of the immediately previous block
170
+ const db = await this.dbProvider.fork(globalVariables.blockNumber.toNumber() - 1);
171
+ this.dbs.set(globalVariables.blockNumber.toNumber(), db);
172
+
180
173
  // we start the block by enqueueing all of the base parity circuits
181
174
  let baseParityInputs: BaseParityInputs[] = [];
182
175
  let l1ToL2MessagesPadded: Tuple<Fr, typeof NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP>;
@@ -189,12 +182,12 @@ export class ProvingOrchestrator implements EpochProver {
189
182
  BaseParityInputs.fromSlice(l1ToL2MessagesPadded, i, getVKTreeRoot()),
190
183
  );
191
184
 
192
- const messageTreeSnapshot = await getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, this.db);
185
+ const messageTreeSnapshot = await getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, db);
193
186
 
194
187
  const newL1ToL2MessageTreeRootSiblingPathArray = await getSubtreeSiblingPath(
195
188
  MerkleTreeId.L1_TO_L2_MESSAGE_TREE,
196
189
  L1_TO_L2_MSG_SUBTREE_HEIGHT,
197
- this.db,
190
+ db,
198
191
  );
199
192
 
200
193
  const newL1ToL2MessageTreeRootSiblingPath = makeTuple(
@@ -205,18 +198,18 @@ export class ProvingOrchestrator implements EpochProver {
205
198
  );
206
199
 
207
200
  // Update the local trees to include the new l1 to l2 messages
208
- await this.db.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2MessagesPadded);
209
- const messageTreeSnapshotAfterInsertion = await getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, this.db);
201
+ await db.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2MessagesPadded);
202
+ const messageTreeSnapshotAfterInsertion = await getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, db);
210
203
 
211
204
  // Get archive snapshot before this block lands
212
- const startArchiveSnapshot = await getTreeSnapshot(MerkleTreeId.ARCHIVE, this.db);
213
- const newArchiveSiblingPath = await getRootTreeSiblingPath(MerkleTreeId.ARCHIVE, this.db);
214
- const previousBlockHash = await this.db.getLeafValue(
205
+ const startArchiveSnapshot = await getTreeSnapshot(MerkleTreeId.ARCHIVE, db);
206
+ const newArchiveSiblingPath = await getRootTreeSiblingPath(MerkleTreeId.ARCHIVE, db);
207
+ const previousBlockHash = await db.getLeafValue(
215
208
  MerkleTreeId.ARCHIVE,
216
209
  BigInt(startArchiveSnapshot.nextAvailableLeafIndex - 1),
217
210
  );
218
211
 
219
- this.provingState!.startNewBlock(
212
+ const blockProvingState = this.provingState!.startNewBlock(
220
213
  numTxs,
221
214
  globalVariables,
222
215
  l1ToL2MessagesPadded,
@@ -230,7 +223,7 @@ export class ProvingOrchestrator implements EpochProver {
230
223
 
231
224
  // Enqueue base parity circuits for the block
232
225
  for (let i = 0; i < baseParityInputs.length; i++) {
233
- this.enqueueBaseParityCircuit(this.provingState!.currentBlock!, baseParityInputs[i], i);
226
+ this.enqueueBaseParityCircuit(blockProvingState, baseParityInputs[i], i);
234
227
  }
235
228
  }
236
229
 
@@ -242,33 +235,40 @@ export class ProvingOrchestrator implements EpochProver {
242
235
  [Attributes.TX_HASH]: tx.hash.toString(),
243
236
  }))
244
237
  public async addNewTx(tx: ProcessedTx): Promise<void> {
245
- const provingState = this?.provingState?.currentBlock;
246
- if (!provingState) {
247
- throw new Error(`Invalid proving state, call startNewBlock before adding transactions`);
248
- }
238
+ const blockNumber = tx.constants.globalVariables.blockNumber.toNumber();
239
+ try {
240
+ const provingState = this.provingState?.getBlockProvingStateByBlockNumber(blockNumber);
241
+ if (!provingState) {
242
+ throw new Error(`Block proving state for ${blockNumber} not found`);
243
+ }
249
244
 
250
- if (!provingState.isAcceptingTransactions()) {
251
- throw new Error(`Rollup not accepting further transactions`);
252
- }
245
+ if (!provingState.isAcceptingTransactions()) {
246
+ throw new Error(`Rollup not accepting further transactions`);
247
+ }
253
248
 
254
- if (!provingState.verifyState()) {
255
- throw new Error(`Invalid proving state when adding a tx`);
256
- }
249
+ if (!provingState.verifyState()) {
250
+ throw new Error(`Invalid proving state when adding a tx`);
251
+ }
257
252
 
258
- validateTx(tx);
253
+ validateTx(tx);
259
254
 
260
- logger.info(`Received transaction: ${tx.hash}`);
255
+ logger.info(`Received transaction: ${tx.hash}`);
261
256
 
262
- if (tx.isEmpty) {
263
- logger.warn(`Ignoring empty transaction ${tx.hash} - it will not be added to this block`);
264
- return;
265
- }
257
+ if (tx.isEmpty) {
258
+ logger.warn(`Ignoring empty transaction ${tx.hash} - it will not be added to this block`);
259
+ return;
260
+ }
266
261
 
267
- const [hints, treeSnapshots] = await this.prepareTransaction(tx, provingState);
268
- this.enqueueFirstProofs(hints, treeSnapshots, tx, provingState);
262
+ const [hints, treeSnapshots] = await this.prepareTransaction(tx, provingState);
263
+ this.enqueueFirstProofs(hints, treeSnapshots, tx, provingState);
269
264
 
270
- if (provingState.transactionsReceived === provingState.totalNumTxs) {
271
- logger.verbose(`All transactions received for block ${provingState.globalVariables.blockNumber}.`);
265
+ if (provingState.transactionsReceived === provingState.totalNumTxs) {
266
+ logger.verbose(`All transactions received for block ${provingState.globalVariables.blockNumber}.`);
267
+ }
268
+ } catch (err: any) {
269
+ throw new Error(`Error adding transaction ${tx.hash.toString()} to block ${blockNumber}: ${err.message}`, {
270
+ cause: err,
271
+ });
272
272
  }
273
273
  }
274
274
 
@@ -276,21 +276,13 @@ export class ProvingOrchestrator implements EpochProver {
276
276
  * Marks the block as full and pads it if required, no more transactions will be accepted.
277
277
  * Computes the block header and updates the archive tree.
278
278
  */
279
- @trackSpan('ProvingOrchestrator.setBlockCompleted', function () {
280
- const block = this.provingState?.currentBlock;
281
- if (!block) {
282
- return {};
283
- }
284
- return {
285
- [Attributes.BLOCK_NUMBER]: block.globalVariables.blockNumber.toNumber(),
286
- [Attributes.BLOCK_SIZE]: block.totalNumTxs,
287
- [Attributes.BLOCK_TXS_COUNT]: block.transactionsReceived,
288
- };
289
- })
290
- public async setBlockCompleted(expectedHeader?: Header): Promise<L2Block> {
291
- const provingState = this.provingState?.currentBlock;
279
+ @trackSpan('ProvingOrchestrator.setBlockCompleted', (blockNumber: number) => ({
280
+ [Attributes.BLOCK_NUMBER]: blockNumber,
281
+ }))
282
+ public async setBlockCompleted(blockNumber: number, expectedHeader?: Header): Promise<L2Block> {
283
+ const provingState = this.provingState?.getBlockProvingStateByBlockNumber(blockNumber);
292
284
  if (!provingState) {
293
- throw new Error(`Invalid proving state, call startNewBlock before adding transactions or completing the block`);
285
+ throw new Error(`Block proving state for ${blockNumber} not found`);
294
286
  }
295
287
 
296
288
  if (!provingState.verifyState()) {
@@ -313,7 +305,7 @@ export class ProvingOrchestrator implements EpochProver {
313
305
  // base rollup inputs
314
306
  // Then enqueue the proving of all the transactions
315
307
  const unprovenPaddingTx = makeEmptyProcessedTx(
316
- this.db.getInitialHeader(),
308
+ this.dbs.get(blockNumber)!.getInitialHeader(),
317
309
  provingState.globalVariables.chainId,
318
310
  provingState.globalVariables.version,
319
311
  getVKTreeRoot(),
@@ -344,7 +336,7 @@ export class ProvingOrchestrator implements EpochProver {
344
336
 
345
337
  /** Returns the block as built for a given index. */
346
338
  public getBlock(index: number): L2Block {
347
- const block = this.provingState?.blocks[index].block;
339
+ const block = this.provingState?.blocks[index]?.block;
348
340
  if (!block) {
349
341
  throw new Error(`Block at index ${index} not available`);
350
342
  }
@@ -362,7 +354,10 @@ export class ProvingOrchestrator implements EpochProver {
362
354
  })
363
355
  private padEpoch(): Promise<void> {
364
356
  const provingState = this.provingState!;
365
- const lastBlock = provingState.currentBlock?.block;
357
+ const lastBlock = maxBy(
358
+ provingState.blocks.filter(b => !!b),
359
+ b => b!.blockNumber,
360
+ )?.block;
366
361
  if (!lastBlock) {
367
362
  return Promise.reject(new Error(`Epoch needs at least one completed block in order to be padded`));
368
363
  }
@@ -416,13 +411,16 @@ export class ProvingOrchestrator implements EpochProver {
416
411
  // Collect all new nullifiers, commitments, and contracts from all txs in this block to build body
417
412
  const txs = provingState!.allTxs.map(a => a.processedTx);
418
413
 
414
+ // Get db for this block
415
+ const db = this.dbs.get(provingState.blockNumber)!;
416
+
419
417
  // Given we've applied every change from this block, now assemble the block header
420
418
  // and update the archive tree, so we're ready to start processing the next block
421
419
  const { header, body } = await buildHeaderAndBodyFromTxs(
422
420
  txs,
423
421
  provingState.globalVariables,
424
422
  provingState.newL1ToL2Messages,
425
- this.db,
423
+ db,
426
424
  );
427
425
 
428
426
  if (expectedHeader && !header.equals(expectedHeader)) {
@@ -431,10 +429,10 @@ export class ProvingOrchestrator implements EpochProver {
431
429
  }
432
430
 
433
431
  logger.verbose(`Updating archive tree with block ${provingState.blockNumber} header ${header.hash().toString()}`);
434
- await this.db.updateArchive(header);
432
+ await db.updateArchive(header);
435
433
 
436
434
  // Assemble the L2 block
437
- const newArchive = await getTreeSnapshot(MerkleTreeId.ARCHIVE, this.db);
435
+ const newArchive = await getTreeSnapshot(MerkleTreeId.ARCHIVE, db);
438
436
  const l2Block = new L2Block(newArchive, header, body);
439
437
 
440
438
  if (!l2Block.body.getTxsEffectsHash().equals(header.contentCommitment.txsEffectsHash)) {
@@ -445,10 +443,24 @@ export class ProvingOrchestrator implements EpochProver {
445
443
  );
446
444
  }
447
445
 
446
+ await this.verifyBuiltBlockAgainstSyncedState(l2Block, newArchive);
447
+
448
448
  logger.verbose(`Orchestrator finalised block ${l2Block.number}`);
449
449
  provingState.block = l2Block;
450
450
  }
451
451
 
452
+ // Flagged as protected to disable in certain unit tests
453
+ protected async verifyBuiltBlockAgainstSyncedState(l2Block: L2Block, newArchive: AppendOnlyTreeSnapshot) {
454
+ const syncedArchive = await getTreeSnapshot(MerkleTreeId.ARCHIVE, this.dbProvider.getSnapshot(l2Block.number));
455
+ if (!syncedArchive.equals(newArchive)) {
456
+ throw new Error(
457
+ `Archive tree mismatch for block ${l2Block.number}: world state synced to ${inspect(
458
+ syncedArchive,
459
+ )} but built ${inspect(newArchive)}`,
460
+ );
461
+ }
462
+ }
463
+
452
464
  // Enqueues the proving of the required padding transactions
453
465
  // If the fully proven padding transaction is not available, this will first be proven
454
466
  private enqueuePaddingTxs(
@@ -602,13 +614,6 @@ export class ProvingOrchestrator implements EpochProver {
602
614
  provingState: BlockProvingState,
603
615
  ) {
604
616
  const txProvingState = new TxProvingState(tx, hints, treeSnapshots);
605
-
606
- const rejectReason = txProvingState.verifyStateOrReject();
607
- if (rejectReason) {
608
- provingState.reject(rejectReason);
609
- return;
610
- }
611
-
612
617
  const txIndex = provingState.addNewTx(txProvingState);
613
618
  this.enqueueTube(provingState, txIndex);
614
619
  if (txProvingState.requireAvmProof) {
@@ -692,9 +697,11 @@ export class ProvingOrchestrator implements EpochProver {
692
697
  return;
693
698
  }
694
699
 
700
+ const db = this.dbs.get(provingState.blockNumber)!;
701
+
695
702
  // We build the base rollup inputs using a mock proof and verification key.
696
703
  // These will be overwritten later once we have proven the tube circuit and any public kernels
697
- const [ms, hints] = await elapsed(buildBaseRollupHints(tx, provingState.globalVariables, this.db));
704
+ const [ms, hints] = await elapsed(buildBaseRollupHints(tx, provingState.globalVariables, db));
698
705
 
699
706
  if (!tx.isEmpty) {
700
707
  this.metrics.recordBaseRollupInputs(ms);
@@ -702,7 +709,7 @@ export class ProvingOrchestrator implements EpochProver {
702
709
 
703
710
  const promises = [MerkleTreeId.NOTE_HASH_TREE, MerkleTreeId.NULLIFIER_TREE, MerkleTreeId.PUBLIC_DATA_TREE].map(
704
711
  async (id: MerkleTreeId) => {
705
- return { key: id, value: await getTreeSnapshot(id, this.db) };
712
+ return { key: id, value: await getTreeSnapshot(id, db) };
706
713
  },
707
714
  );
708
715
  const treeSnapshots: TreeSnapshots = new Map((await Promise.all(promises)).map(obj => [obj.key, obj.value]));
@@ -1055,6 +1062,19 @@ export class ProvingOrchestrator implements EpochProver {
1055
1062
  logger.debug('Block root rollup already started');
1056
1063
  return;
1057
1064
  }
1065
+ const blockNumber = provingState.blockNumber;
1066
+
1067
+ // TODO(palla/prover): This closes the fork only on the happy path. If this epoch orchestrator
1068
+ // is aborted and never reaches this point, it will leak the fork. We need to add a global cleanup,
1069
+ // but have to make sure it only runs once all operations are completed, otherwise some function here
1070
+ // will attempt to access the fork after it was closed.
1071
+ logger.debug(`Cleaning up world state fork for ${blockNumber}`);
1072
+ void this.dbs
1073
+ .get(blockNumber)
1074
+ ?.close()
1075
+ .then(() => this.dbs.delete(blockNumber))
1076
+ .catch(err => logger.error(`Error closing db for block ${blockNumber}`, err));
1077
+
1058
1078
  this.enqueueBlockRootRollup(provingState);
1059
1079
  }
1060
1080