@aztec/sequencer-client 0.23.0 → 0.26.1

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 (88) hide show
  1. package/dest/block_builder/solo_block_builder.d.ts +9 -9
  2. package/dest/block_builder/solo_block_builder.d.ts.map +1 -1
  3. package/dest/block_builder/solo_block_builder.js +26 -48
  4. package/dest/global_variable_builder/viem-reader.js +2 -2
  5. package/dest/prover/empty.d.ts +2 -2
  6. package/dest/prover/empty.d.ts.map +1 -1
  7. package/dest/prover/empty.js +1 -1
  8. package/dest/prover/index.d.ts +2 -2
  9. package/dest/prover/index.d.ts.map +1 -1
  10. package/dest/publisher/l1-publisher.d.ts +0 -2
  11. package/dest/publisher/l1-publisher.d.ts.map +1 -1
  12. package/dest/publisher/l1-publisher.js +2 -3
  13. package/dest/publisher/viem-tx-sender.d.ts.map +1 -1
  14. package/dest/publisher/viem-tx-sender.js +6 -10
  15. package/dest/sequencer/abstract_phase_manager.d.ts +23 -30
  16. package/dest/sequencer/abstract_phase_manager.d.ts.map +1 -1
  17. package/dest/sequencer/abstract_phase_manager.js +151 -98
  18. package/dest/sequencer/{fee_distribution_phase_manager.d.ts → app_logic_phase_manager.d.ts} +9 -11
  19. package/dest/sequencer/app_logic_phase_manager.d.ts.map +1 -0
  20. package/dest/sequencer/app_logic_phase_manager.js +44 -0
  21. package/dest/sequencer/phase_manager_factory.d.ts +19 -0
  22. package/dest/sequencer/phase_manager_factory.d.ts.map +1 -0
  23. package/dest/sequencer/phase_manager_factory.js +51 -0
  24. package/dest/sequencer/processed_tx.d.ts +4 -4
  25. package/dest/sequencer/processed_tx.d.ts.map +1 -1
  26. package/dest/sequencer/processed_tx.js +8 -8
  27. package/dest/sequencer/public_processor.d.ts +1 -1
  28. package/dest/sequencer/public_processor.d.ts.map +1 -1
  29. package/dest/sequencer/public_processor.js +13 -11
  30. package/dest/sequencer/sequencer.d.ts +3 -3
  31. package/dest/sequencer/sequencer.d.ts.map +1 -1
  32. package/dest/sequencer/sequencer.js +23 -16
  33. package/dest/sequencer/{fee_preparation_phase_manager.d.ts → setup_phase_manager.d.ts} +9 -11
  34. package/dest/sequencer/setup_phase_manager.d.ts.map +1 -0
  35. package/dest/sequencer/setup_phase_manager.js +37 -0
  36. package/dest/sequencer/{application_logic_phase_manager.d.ts → teardown_phase_manager.d.ts} +9 -12
  37. package/dest/sequencer/teardown_phase_manager.d.ts.map +1 -0
  38. package/dest/sequencer/teardown_phase_manager.js +36 -0
  39. package/dest/simulator/index.d.ts +11 -5
  40. package/dest/simulator/index.d.ts.map +1 -1
  41. package/dest/simulator/public_executor.d.ts +8 -3
  42. package/dest/simulator/public_executor.d.ts.map +1 -1
  43. package/dest/simulator/public_executor.js +64 -11
  44. package/dest/simulator/public_kernel.d.ts +11 -5
  45. package/dest/simulator/public_kernel.d.ts.map +1 -1
  46. package/dest/simulator/public_kernel.js +35 -16
  47. package/dest/simulator/rollup.js +2 -2
  48. package/package.json +14 -24
  49. package/src/block_builder/index.ts +24 -0
  50. package/src/block_builder/solo_block_builder.ts +726 -0
  51. package/src/block_builder/types.ts +8 -0
  52. package/src/client/index.ts +1 -0
  53. package/src/client/sequencer-client.ts +97 -0
  54. package/src/config.ts +86 -0
  55. package/src/global_variable_builder/config.ts +20 -0
  56. package/src/global_variable_builder/global_builder.ts +95 -0
  57. package/src/global_variable_builder/index.ts +16 -0
  58. package/src/global_variable_builder/viem-reader.ts +61 -0
  59. package/src/index.ts +15 -0
  60. package/src/mocks/verification_keys.ts +36 -0
  61. package/src/prover/empty.ts +74 -0
  62. package/src/prover/index.ts +53 -0
  63. package/src/publisher/config.ts +41 -0
  64. package/src/publisher/index.ts +14 -0
  65. package/src/publisher/l1-publisher.ts +362 -0
  66. package/src/publisher/viem-tx-sender.ts +235 -0
  67. package/src/receiver.ts +13 -0
  68. package/src/sequencer/abstract_phase_manager.ts +489 -0
  69. package/src/sequencer/app_logic_phase_manager.ts +75 -0
  70. package/src/sequencer/config.ts +1 -0
  71. package/src/sequencer/index.ts +2 -0
  72. package/src/sequencer/phase_manager_factory.ts +122 -0
  73. package/src/sequencer/processed_tx.ts +94 -0
  74. package/src/sequencer/public_processor.ts +153 -0
  75. package/src/sequencer/sequencer.ts +469 -0
  76. package/src/sequencer/setup_phase_manager.ts +68 -0
  77. package/src/sequencer/teardown_phase_manager.ts +67 -0
  78. package/src/simulator/index.ts +57 -0
  79. package/src/simulator/public_executor.ts +267 -0
  80. package/src/simulator/public_kernel.ts +84 -0
  81. package/src/simulator/rollup.ts +76 -0
  82. package/src/utils.ts +16 -0
  83. package/dest/sequencer/application_logic_phase_manager.d.ts.map +0 -1
  84. package/dest/sequencer/application_logic_phase_manager.js +0 -64
  85. package/dest/sequencer/fee_distribution_phase_manager.d.ts.map +0 -1
  86. package/dest/sequencer/fee_distribution_phase_manager.js +0 -42
  87. package/dest/sequencer/fee_preparation_phase_manager.d.ts.map +0 -1
  88. package/dest/sequencer/fee_preparation_phase_manager.js +0 -43
@@ -0,0 +1,726 @@
1
+ import { Body, ContractData, L2Block, MerkleTreeId, PublicDataWrite, TxEffect, TxL2Logs } from '@aztec/circuit-types';
2
+ import {
3
+ ARCHIVE_HEIGHT,
4
+ AppendOnlyTreeSnapshot,
5
+ BaseOrMergeRollupPublicInputs,
6
+ BaseRollupInputs,
7
+ CONTRACT_SUBTREE_HEIGHT,
8
+ CONTRACT_SUBTREE_SIBLING_PATH_LENGTH,
9
+ CombinedAccumulatedData,
10
+ ConstantRollupData,
11
+ GlobalVariables,
12
+ L1_TO_L2_MSG_SUBTREE_HEIGHT,
13
+ L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH,
14
+ MAX_NEW_CONTRACTS_PER_TX,
15
+ MAX_NEW_NOTE_HASHES_PER_TX,
16
+ MAX_NEW_NULLIFIERS_PER_TX,
17
+ MAX_PUBLIC_DATA_READS_PER_TX,
18
+ MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
19
+ MembershipWitness,
20
+ MergeRollupInputs,
21
+ NOTE_HASH_SUBTREE_HEIGHT,
22
+ NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH,
23
+ NULLIFIER_SUBTREE_HEIGHT,
24
+ NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH,
25
+ NULLIFIER_TREE_HEIGHT,
26
+ NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
27
+ NullifierLeafPreimage,
28
+ PUBLIC_DATA_SUBTREE_HEIGHT,
29
+ PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH,
30
+ PUBLIC_DATA_TREE_HEIGHT,
31
+ PartialStateReference,
32
+ PreviousRollupData,
33
+ Proof,
34
+ PublicDataTreeLeaf,
35
+ PublicDataTreeLeafPreimage,
36
+ ROLLUP_VK_TREE_HEIGHT,
37
+ RollupKernelCircuitPublicInputs,
38
+ RollupKernelData,
39
+ RollupTypes,
40
+ RootRollupInputs,
41
+ RootRollupPublicInputs,
42
+ SideEffect,
43
+ SideEffectLinkedToNoteHash,
44
+ StateDiffHints,
45
+ StateReference,
46
+ VK_TREE_HEIGHT,
47
+ VerificationKey,
48
+ } from '@aztec/circuits.js';
49
+ import { assertPermutation, makeTuple } from '@aztec/foundation/array';
50
+ import { toBigIntBE } from '@aztec/foundation/bigint-buffer';
51
+ import { padArrayEnd } from '@aztec/foundation/collection';
52
+ import { Fr } from '@aztec/foundation/fields';
53
+ import { createDebugLogger } from '@aztec/foundation/log';
54
+ import { Tuple, assertLength, toFriendlyJSON } from '@aztec/foundation/serialize';
55
+ import { MerkleTreeOperations } from '@aztec/world-state';
56
+
57
+ import chunk from 'lodash.chunk';
58
+
59
+ import { VerificationKeys } from '../mocks/verification_keys.js';
60
+ import { RollupProver } from '../prover/index.js';
61
+ import { ProcessedTx } from '../sequencer/processed_tx.js';
62
+ import { RollupSimulator } from '../simulator/index.js';
63
+ import { BlockBuilder } from './index.js';
64
+ import { TreeNames } from './types.js';
65
+
66
+ const frToBigInt = (fr: Fr) => toBigIntBE(fr.toBuffer());
67
+
68
+ // Denotes fields that are not used now, but will be in the future
69
+ const FUTURE_FR = new Fr(0n);
70
+ const FUTURE_NUM = 0;
71
+
72
+ // Denotes fields that should be deleted
73
+ const DELETE_FR = new Fr(0n);
74
+
75
+ /**
76
+ * Builds an L2 block out of a set of ProcessedTx's,
77
+ * using the base, merge, and root rollup circuits.
78
+ */
79
+ export class SoloBlockBuilder implements BlockBuilder {
80
+ constructor(
81
+ protected db: MerkleTreeOperations,
82
+ protected vks: VerificationKeys,
83
+ protected simulator: RollupSimulator,
84
+ protected prover: RollupProver,
85
+ protected debug = createDebugLogger('aztec:sequencer:solo-block-builder'),
86
+ ) {}
87
+
88
+ /**
89
+ * Builds an L2 block with the given number containing the given txs, updating state trees.
90
+ * @param globalVariables - Global variables to be used in the block.
91
+ * @param txs - Processed transactions to include in the block.
92
+ * @param newL1ToL2Messages - L1 to L2 messages to be part of the block.
93
+ * @param timestamp - Timestamp of the block.
94
+ * @returns The new L2 block and a correctness proof as returned by the root rollup circuit.
95
+ */
96
+ public async buildL2Block(
97
+ globalVariables: GlobalVariables,
98
+ txs: ProcessedTx[],
99
+ newL1ToL2Messages: Fr[],
100
+ ): Promise<[L2Block, Proof]> {
101
+ // Check txs are good for processing by checking if all the tree snapshots in header are non-empty
102
+ this.validateTxs(txs);
103
+
104
+ // We fill the tx batch with empty txs, we process only one tx at a time for now
105
+ const [circuitsOutput, proof] = await this.runCircuits(globalVariables, txs, newL1ToL2Messages);
106
+
107
+ // Collect all new nullifiers, commitments, and contracts from all txs in this block
108
+ const txEffects: TxEffect[] = txs.map(
109
+ tx =>
110
+ new TxEffect(
111
+ tx.data.combinedData.newNoteHashes.map((c: SideEffect) => c.value) as Tuple<
112
+ Fr,
113
+ typeof MAX_NEW_NOTE_HASHES_PER_TX
114
+ >,
115
+ tx.data.combinedData.newNullifiers.map((n: SideEffectLinkedToNoteHash) => n.value) as Tuple<
116
+ Fr,
117
+ typeof MAX_NEW_NULLIFIERS_PER_TX
118
+ >,
119
+ tx.data.combinedData.newL2ToL1Msgs,
120
+ tx.data.combinedData.publicDataUpdateRequests.map(t => new PublicDataWrite(t.leafSlot, t.newValue)) as Tuple<
121
+ PublicDataWrite,
122
+ typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
123
+ >,
124
+ tx.data.combinedData.newContracts.map(cd => cd.hash()) as Tuple<Fr, typeof MAX_NEW_CONTRACTS_PER_TX>,
125
+ tx.data.combinedData.newContracts.map(
126
+ cd => new ContractData(cd.contractAddress, cd.portalContractAddress),
127
+ ) as Tuple<ContractData, typeof MAX_NEW_CONTRACTS_PER_TX>,
128
+ tx.encryptedLogs || new TxL2Logs([]),
129
+ tx.unencryptedLogs || new TxL2Logs([]),
130
+ ),
131
+ );
132
+
133
+ const blockBody = new Body(padArrayEnd(newL1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP), txEffects);
134
+
135
+ const l2Block = L2Block.fromFields({
136
+ archive: circuitsOutput.archive,
137
+ header: circuitsOutput.header,
138
+ body: blockBody,
139
+ });
140
+
141
+ if (!l2Block.body.getCalldataHash().equals(circuitsOutput.header.contentCommitment.txsHash)) {
142
+ throw new Error(
143
+ `Calldata hash mismatch, ${l2Block.body
144
+ .getCalldataHash()
145
+ .toString('hex')} == ${circuitsOutput.header.contentCommitment.txsHash.toString('hex')} `,
146
+ );
147
+ }
148
+
149
+ return [l2Block, proof];
150
+ }
151
+
152
+ protected validateTxs(txs: ProcessedTx[]) {
153
+ for (const tx of txs) {
154
+ const txHeader = tx.data.constants.historicalHeader;
155
+ if (txHeader.state.l1ToL2MessageTree.isZero()) {
156
+ throw new Error(`Empty L1 to L2 messages tree in tx: ${toFriendlyJSON(tx)}`);
157
+ }
158
+ if (txHeader.state.partial.noteHashTree.isZero()) {
159
+ throw new Error(`Empty note hash tree in tx: ${toFriendlyJSON(tx)}`);
160
+ }
161
+ if (txHeader.state.partial.nullifierTree.isZero()) {
162
+ throw new Error(`Empty nullifier tree in tx: ${toFriendlyJSON(tx)}`);
163
+ }
164
+ if (txHeader.state.partial.contractTree.isZero()) {
165
+ throw new Error(`Empty contract tree in tx: ${toFriendlyJSON(tx)}`);
166
+ }
167
+ if (txHeader.state.partial.publicDataTree.isZero()) {
168
+ throw new Error(`Empty public data tree in tx: ${toFriendlyJSON(tx)}`);
169
+ }
170
+ }
171
+ }
172
+
173
+ protected async getTreeSnapshot(id: MerkleTreeId): Promise<AppendOnlyTreeSnapshot> {
174
+ const treeInfo = await this.db.getTreeInfo(id);
175
+ return new AppendOnlyTreeSnapshot(Fr.fromBuffer(treeInfo.root), Number(treeInfo.size));
176
+ }
177
+
178
+ protected async runCircuits(
179
+ globalVariables: GlobalVariables,
180
+ txs: ProcessedTx[],
181
+ newL1ToL2Messages: Fr[],
182
+ ): Promise<[RootRollupPublicInputs, Proof]> {
183
+ // Check that the length of the array of txs is a power of two
184
+ // See https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
185
+ if (txs.length < 2 || (txs.length & (txs.length - 1)) !== 0) {
186
+ throw new Error(`Length of txs for the block should be a power of two and at least two (got ${txs.length})`);
187
+ }
188
+
189
+ // padArrayEnd throws if the array is already full. Otherwise it pads till we reach the required size
190
+ const newL1ToL2MessagesTuple = padArrayEnd(newL1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
191
+
192
+ // Run the base rollup circuits for the txs
193
+ const baseRollupOutputs: [BaseOrMergeRollupPublicInputs, Proof][] = [];
194
+ for (const tx of txs) {
195
+ baseRollupOutputs.push(await this.baseRollupCircuit(tx, globalVariables));
196
+ }
197
+
198
+ // Run merge rollups in layers until we have only two outputs
199
+ let mergeRollupInputs: [BaseOrMergeRollupPublicInputs, Proof][] = baseRollupOutputs;
200
+ let mergeRollupOutputs: [BaseOrMergeRollupPublicInputs, Proof][] = [];
201
+ while (mergeRollupInputs.length > 2) {
202
+ for (const pair of chunk(mergeRollupInputs, 2)) {
203
+ const [r1, r2] = pair;
204
+ mergeRollupOutputs.push(await this.mergeRollupCircuit(r1, r2));
205
+ }
206
+ mergeRollupInputs = mergeRollupOutputs;
207
+ mergeRollupOutputs = [];
208
+ }
209
+
210
+ // Run the root rollup with the last two merge rollups (or base, if no merge layers)
211
+ const [mergeOutputLeft, mergeOutputRight] = mergeRollupInputs;
212
+ return this.rootRollupCircuit(mergeOutputLeft, mergeOutputRight, newL1ToL2MessagesTuple);
213
+ }
214
+
215
+ protected async baseRollupCircuit(
216
+ tx: ProcessedTx,
217
+ globalVariables: GlobalVariables,
218
+ ): Promise<[BaseOrMergeRollupPublicInputs, Proof]> {
219
+ this.debug(`Running base rollup for ${tx.hash}`);
220
+ const rollupInput = await this.buildBaseRollupInput(tx, globalVariables);
221
+ const rollupOutput = await this.simulator.baseRollupCircuit(rollupInput);
222
+ await this.validatePartialState(rollupOutput.end);
223
+ const proof = await this.prover.getBaseRollupProof(rollupInput, rollupOutput);
224
+ return [rollupOutput, proof];
225
+ }
226
+
227
+ protected async mergeRollupCircuit(
228
+ left: [BaseOrMergeRollupPublicInputs, Proof],
229
+ right: [BaseOrMergeRollupPublicInputs, Proof],
230
+ ): Promise<[BaseOrMergeRollupPublicInputs, Proof]> {
231
+ const vk = this.getVerificationKey(left[0].rollupType);
232
+ const mergeInputs = new MergeRollupInputs([
233
+ this.getPreviousRollupDataFromPublicInputs(left[0], left[1], vk),
234
+ this.getPreviousRollupDataFromPublicInputs(right[0], right[1], vk),
235
+ ]);
236
+
237
+ this.debug(`Running merge rollup circuit`);
238
+ const output = await this.simulator.mergeRollupCircuit(mergeInputs);
239
+ const proof = await this.prover.getMergeRollupProof(mergeInputs, output);
240
+ return [output, proof];
241
+ }
242
+
243
+ protected getVerificationKey(type: RollupTypes) {
244
+ switch (type) {
245
+ case RollupTypes.Base:
246
+ return this.vks.baseRollupCircuit;
247
+ case RollupTypes.Merge:
248
+ return this.vks.mergeRollupCircuit;
249
+ default:
250
+ throw new Error(`No verification key available for ${type}`);
251
+ }
252
+ }
253
+
254
+ protected async rootRollupCircuit(
255
+ left: [BaseOrMergeRollupPublicInputs, Proof],
256
+ right: [BaseOrMergeRollupPublicInputs, Proof],
257
+ newL1ToL2Messages: Tuple<Fr, typeof NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP>,
258
+ ): Promise<[RootRollupPublicInputs, Proof]> {
259
+ this.debug(`Running root rollup circuit`);
260
+ const rootInput = await this.getRootRollupInput(...left, ...right, newL1ToL2Messages);
261
+
262
+ // Update the local trees to include the new l1 to l2 messages
263
+ await this.db.appendLeaves(
264
+ MerkleTreeId.L1_TO_L2_MESSAGE_TREE,
265
+ newL1ToL2Messages.map(m => m.toBuffer()),
266
+ );
267
+
268
+ // Simulate and get proof for the root circuit
269
+ const rootOutput = await this.simulator.rootRollupCircuit(rootInput);
270
+
271
+ const rootProof = await this.prover.getRootRollupProof(rootInput, rootOutput);
272
+
273
+ // Update the archive with the latest block header
274
+ this.debug(`Updating and validating root trees`);
275
+ await this.db.updateArchive(rootOutput.header);
276
+
277
+ await this.validateRootOutput(rootOutput);
278
+
279
+ return [rootOutput, rootProof];
280
+ }
281
+
282
+ protected async validatePartialState(partialState: PartialStateReference) {
283
+ await Promise.all([
284
+ this.validateSimulatedTree(
285
+ await this.getTreeSnapshot(MerkleTreeId.NOTE_HASH_TREE),
286
+ partialState.noteHashTree,
287
+ 'NoteHashTree',
288
+ ),
289
+ this.validateSimulatedTree(
290
+ await this.getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE),
291
+ partialState.nullifierTree,
292
+ 'NullifierTree',
293
+ ),
294
+ this.validateSimulatedTree(
295
+ await this.getTreeSnapshot(MerkleTreeId.CONTRACT_TREE),
296
+ partialState.contractTree,
297
+ 'ContractTree',
298
+ ),
299
+ this.validateSimulatedTree(
300
+ await this.getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE),
301
+ partialState.publicDataTree,
302
+ 'PublicDataTree',
303
+ ),
304
+ ]);
305
+ }
306
+
307
+ protected async validateState(state: StateReference) {
308
+ await Promise.all([
309
+ this.validateSimulatedTree(
310
+ await this.getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGE_TREE),
311
+ state.l1ToL2MessageTree,
312
+ 'L1ToL2MessageTree',
313
+ ),
314
+ this.validatePartialState(state.partial),
315
+ ]);
316
+ }
317
+
318
+ // Validate that the roots of all local trees match the output of the root circuit simulation
319
+ protected async validateRootOutput(rootOutput: RootRollupPublicInputs) {
320
+ await Promise.all([
321
+ this.validateState(rootOutput.header.state),
322
+ this.validateSimulatedTree(await this.getTreeSnapshot(MerkleTreeId.ARCHIVE), rootOutput.archive, 'Archive'),
323
+ ]);
324
+ }
325
+
326
+ // Helper for comparing two trees snapshots
327
+ protected validateSimulatedTree(
328
+ localTree: AppendOnlyTreeSnapshot,
329
+ simulatedTree: AppendOnlyTreeSnapshot,
330
+ name: TreeNames,
331
+ label?: string,
332
+ ) {
333
+ if (!simulatedTree.root.toBuffer().equals(localTree.root.toBuffer())) {
334
+ throw new Error(`${label ?? name} tree root mismatch (local ${localTree.root}, simulated ${simulatedTree.root})`);
335
+ }
336
+ if (simulatedTree.nextAvailableLeafIndex !== localTree.nextAvailableLeafIndex) {
337
+ throw new Error(
338
+ `${label ?? name} tree next available leaf index mismatch (local ${
339
+ localTree.nextAvailableLeafIndex
340
+ }, simulated ${simulatedTree.nextAvailableLeafIndex})`,
341
+ );
342
+ }
343
+ }
344
+
345
+ // Builds the inputs for the root rollup circuit, without making any changes to trees
346
+ protected async getRootRollupInput(
347
+ rollupOutputLeft: BaseOrMergeRollupPublicInputs,
348
+ rollupProofLeft: Proof,
349
+ rollupOutputRight: BaseOrMergeRollupPublicInputs,
350
+ rollupProofRight: Proof,
351
+ newL1ToL2Messages: Tuple<Fr, typeof NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP>,
352
+ ) {
353
+ const vk = this.getVerificationKey(rollupOutputLeft.rollupType);
354
+ const previousRollupData: RootRollupInputs['previousRollupData'] = [
355
+ this.getPreviousRollupDataFromPublicInputs(rollupOutputLeft, rollupProofLeft, vk),
356
+ this.getPreviousRollupDataFromPublicInputs(rollupOutputRight, rollupProofRight, vk),
357
+ ];
358
+
359
+ const getRootTreeSiblingPath = async (treeId: MerkleTreeId) => {
360
+ const { size } = await this.db.getTreeInfo(treeId);
361
+ const path = await this.db.getSiblingPath(treeId, size);
362
+ return path.toFields();
363
+ };
364
+
365
+ const newL1ToL2MessageTreeRootSiblingPathArray = await this.getSubtreeSiblingPath(
366
+ MerkleTreeId.L1_TO_L2_MESSAGE_TREE,
367
+ L1_TO_L2_MSG_SUBTREE_HEIGHT,
368
+ );
369
+
370
+ const newL1ToL2MessageTreeRootSiblingPath = makeTuple(
371
+ L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH,
372
+ i =>
373
+ i < newL1ToL2MessageTreeRootSiblingPathArray.length ? newL1ToL2MessageTreeRootSiblingPathArray[i] : Fr.ZERO,
374
+ 0,
375
+ );
376
+
377
+ // Get tree snapshots
378
+ const startL1ToL2MessageTreeSnapshot = await this.getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGE_TREE);
379
+
380
+ // Get blocks tree
381
+ const startArchiveSnapshot = await this.getTreeSnapshot(MerkleTreeId.ARCHIVE);
382
+ const newArchiveSiblingPathArray = await getRootTreeSiblingPath(MerkleTreeId.ARCHIVE);
383
+
384
+ const newArchiveSiblingPath = makeTuple(
385
+ ARCHIVE_HEIGHT,
386
+ i => (i < newArchiveSiblingPathArray.length ? newArchiveSiblingPathArray[i] : Fr.ZERO),
387
+ 0,
388
+ );
389
+
390
+ return RootRollupInputs.from({
391
+ previousRollupData,
392
+ newL1ToL2Messages,
393
+ newL1ToL2MessageTreeRootSiblingPath,
394
+ startL1ToL2MessageTreeSnapshot,
395
+ startArchiveSnapshot,
396
+ newArchiveSiblingPath,
397
+ });
398
+ }
399
+
400
+ protected getPreviousRollupDataFromPublicInputs(
401
+ rollupOutput: BaseOrMergeRollupPublicInputs,
402
+ rollupProof: Proof,
403
+ vk: VerificationKey,
404
+ ) {
405
+ return new PreviousRollupData(
406
+ rollupOutput,
407
+ rollupProof,
408
+ vk,
409
+
410
+ // MembershipWitness for a VK tree to be implemented in the future
411
+ FUTURE_NUM,
412
+ new MembershipWitness(
413
+ ROLLUP_VK_TREE_HEIGHT,
414
+ BigInt(FUTURE_NUM),
415
+ makeTuple(ROLLUP_VK_TREE_HEIGHT, () => FUTURE_FR),
416
+ ),
417
+ );
418
+ }
419
+
420
+ protected getKernelDataFor(tx: ProcessedTx): RollupKernelData {
421
+ const inputs = new RollupKernelCircuitPublicInputs(
422
+ tx.data.aggregationObject,
423
+ CombinedAccumulatedData.recombine(tx.data.endNonRevertibleData, tx.data.end),
424
+ tx.data.constants,
425
+ );
426
+ return new RollupKernelData(
427
+ inputs,
428
+ tx.proof,
429
+
430
+ // VK for the kernel circuit
431
+ this.vks.privateKernelCircuit,
432
+
433
+ // MembershipWitness for a VK tree to be implemented in the future
434
+ FUTURE_NUM,
435
+ assertLength(Array(VK_TREE_HEIGHT).fill(FUTURE_FR), VK_TREE_HEIGHT),
436
+ );
437
+ }
438
+
439
+ // Scan a tree searching for a specific value and return a membership witness proof for it
440
+ protected async getMembershipWitnessFor<N extends number>(
441
+ value: Fr,
442
+ treeId: MerkleTreeId,
443
+ height: N,
444
+ ): Promise<MembershipWitness<N>> {
445
+ // If this is an empty tx, then just return zeroes
446
+ if (value.isZero()) {
447
+ return this.makeEmptyMembershipWitness(height);
448
+ }
449
+
450
+ const index = await this.db.findLeafIndex(treeId, value.toBuffer());
451
+ if (index === undefined) {
452
+ throw new Error(`Leaf with value ${value} not found in tree ${MerkleTreeId[treeId]}`);
453
+ }
454
+ const path = await this.db.getSiblingPath(treeId, index);
455
+ return new MembershipWitness(height, index, assertLength(path.toFields(), height));
456
+ }
457
+
458
+ protected async getConstantRollupData(globalVariables: GlobalVariables): Promise<ConstantRollupData> {
459
+ return ConstantRollupData.from({
460
+ baseRollupVkHash: DELETE_FR,
461
+ mergeRollupVkHash: DELETE_FR,
462
+ privateKernelVkTreeRoot: FUTURE_FR,
463
+ publicKernelVkTreeRoot: FUTURE_FR,
464
+ lastArchive: await this.getTreeSnapshot(MerkleTreeId.ARCHIVE),
465
+ globalVariables,
466
+ });
467
+ }
468
+
469
+ protected async getLowNullifierInfo(nullifier: Fr) {
470
+ // Return empty nullifier info for an empty tx
471
+ if (nullifier.value === 0n) {
472
+ return {
473
+ index: 0,
474
+ leafPreimage: NullifierLeafPreimage.empty(),
475
+ witness: this.makeEmptyMembershipWitness(NULLIFIER_TREE_HEIGHT),
476
+ };
477
+ }
478
+
479
+ const tree = MerkleTreeId.NULLIFIER_TREE;
480
+ const prevValueIndex = await this.db.getPreviousValueIndex(tree, frToBigInt(nullifier));
481
+ if (!prevValueIndex) {
482
+ throw new Error(`Nullifier tree should have one initial leaf`);
483
+ }
484
+ const prevValuePreimage = (await this.db.getLeafPreimage(tree, prevValueIndex.index))!;
485
+
486
+ const prevValueSiblingPath = await this.db.getSiblingPath(tree, BigInt(prevValueIndex.index));
487
+
488
+ return {
489
+ index: prevValueIndex,
490
+ leafPreimage: prevValuePreimage,
491
+ witness: new MembershipWitness(
492
+ NULLIFIER_TREE_HEIGHT,
493
+ BigInt(prevValueIndex.index),
494
+ assertLength(prevValueSiblingPath.toFields(), NULLIFIER_TREE_HEIGHT),
495
+ ),
496
+ };
497
+ }
498
+
499
+ protected async getSubtreeSiblingPath(treeId: MerkleTreeId, subtreeHeight: number): Promise<Fr[]> {
500
+ const nextAvailableLeafIndex = await this.db.getTreeInfo(treeId).then(t => t.size);
501
+ const fullSiblingPath = await this.db.getSiblingPath(treeId, nextAvailableLeafIndex);
502
+
503
+ // Drop the first subtreeHeight items since we only care about the path to the subtree root
504
+ return fullSiblingPath.getSubtreeSiblingPath(subtreeHeight).toFields();
505
+ }
506
+
507
+ protected async processPublicDataUpdateRequests(tx: ProcessedTx) {
508
+ const combinedPublicDataUpdateRequests = tx.data.combinedData.publicDataUpdateRequests.map(updateRequest => {
509
+ return new PublicDataTreeLeaf(updateRequest.leafSlot, updateRequest.newValue);
510
+ });
511
+ const { lowLeavesWitnessData, newSubtreeSiblingPath, sortedNewLeaves, sortedNewLeavesIndexes } =
512
+ await this.db.batchInsert(
513
+ MerkleTreeId.PUBLIC_DATA_TREE,
514
+ combinedPublicDataUpdateRequests.map(x => x.toBuffer()),
515
+ // TODO(#3675) remove oldValue from update requests
516
+ PUBLIC_DATA_SUBTREE_HEIGHT,
517
+ );
518
+
519
+ if (lowLeavesWitnessData === undefined) {
520
+ throw new Error(`Could not craft public data batch insertion proofs`);
521
+ }
522
+
523
+ const sortedPublicDataWrites = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => {
524
+ return PublicDataTreeLeaf.fromBuffer(sortedNewLeaves[i]);
525
+ });
526
+
527
+ const sortedPublicDataWritesIndexes = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => {
528
+ return sortedNewLeavesIndexes[i];
529
+ });
530
+
531
+ const subtreeSiblingPathAsFields = newSubtreeSiblingPath.toFields();
532
+ const newPublicDataSubtreeSiblingPath = makeTuple(PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, i => {
533
+ return subtreeSiblingPathAsFields[i];
534
+ });
535
+
536
+ const lowPublicDataWritesMembershipWitnesses: Tuple<
537
+ MembershipWitness<typeof PUBLIC_DATA_TREE_HEIGHT>,
538
+ typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
539
+ > = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => {
540
+ const witness = lowLeavesWitnessData[i];
541
+ return MembershipWitness.fromBufferArray(
542
+ witness.index,
543
+ assertLength(witness.siblingPath.toBufferArray(), PUBLIC_DATA_TREE_HEIGHT),
544
+ );
545
+ });
546
+
547
+ const lowPublicDataWritesPreimages: Tuple<
548
+ PublicDataTreeLeafPreimage,
549
+ typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
550
+ > = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => {
551
+ return lowLeavesWitnessData[i].leafPreimage as PublicDataTreeLeafPreimage;
552
+ });
553
+
554
+ // validate that the sortedPublicDataWrites and sortedPublicDataWritesIndexes are in the correct order
555
+ // otherwise it will just fail in the circuit
556
+ assertPermutation(combinedPublicDataUpdateRequests, sortedPublicDataWrites, sortedPublicDataWritesIndexes, (a, b) =>
557
+ a.equals(b),
558
+ );
559
+
560
+ return {
561
+ lowPublicDataWritesPreimages,
562
+ lowPublicDataWritesMembershipWitnesses,
563
+ newPublicDataSubtreeSiblingPath,
564
+ sortedPublicDataWrites,
565
+ sortedPublicDataWritesIndexes,
566
+ };
567
+ }
568
+
569
+ protected async getPublicDataReadsInfo(tx: ProcessedTx) {
570
+ const newPublicDataReadsWitnesses: Tuple<
571
+ MembershipWitness<typeof PUBLIC_DATA_TREE_HEIGHT>,
572
+ typeof MAX_PUBLIC_DATA_READS_PER_TX
573
+ > = makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => MembershipWitness.empty(PUBLIC_DATA_TREE_HEIGHT, 0n));
574
+
575
+ const newPublicDataReadsPreimages: Tuple<PublicDataTreeLeafPreimage, typeof MAX_PUBLIC_DATA_READS_PER_TX> =
576
+ makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => PublicDataTreeLeafPreimage.empty());
577
+
578
+ for (const i in tx.data.combinedData.publicDataReads) {
579
+ const leafSlot = tx.data.combinedData.publicDataReads[i].leafSlot.value;
580
+ const lowLeafResult = await this.db.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot);
581
+ if (!lowLeafResult) {
582
+ throw new Error(`Public data tree should have one initial leaf`);
583
+ }
584
+ const preimage = await this.db.getLeafPreimage(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
585
+ const path = await this.db.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
586
+ newPublicDataReadsWitnesses[i] = new MembershipWitness(
587
+ PUBLIC_DATA_TREE_HEIGHT,
588
+ BigInt(lowLeafResult.index),
589
+ path.toTuple<typeof PUBLIC_DATA_TREE_HEIGHT>(),
590
+ );
591
+ newPublicDataReadsPreimages[i] = preimage! as PublicDataTreeLeafPreimage;
592
+ }
593
+ return {
594
+ newPublicDataReadsWitnesses,
595
+ newPublicDataReadsPreimages,
596
+ };
597
+ }
598
+
599
+ // Builds the base rollup inputs, updating the contract, nullifier, and data trees in the process
600
+ protected async buildBaseRollupInput(tx: ProcessedTx, globalVariables: GlobalVariables) {
601
+ // Get trees info before any changes hit
602
+ const constants = await this.getConstantRollupData(globalVariables);
603
+ const start = new PartialStateReference(
604
+ await this.getTreeSnapshot(MerkleTreeId.NOTE_HASH_TREE),
605
+ await this.getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE),
606
+ await this.getTreeSnapshot(MerkleTreeId.CONTRACT_TREE),
607
+ await this.getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE),
608
+ );
609
+
610
+ // Get the subtree sibling paths for the circuit
611
+ const noteHashSubtreeSiblingPathArray = await this.getSubtreeSiblingPath(
612
+ MerkleTreeId.NOTE_HASH_TREE,
613
+ NOTE_HASH_SUBTREE_HEIGHT,
614
+ );
615
+
616
+ const noteHashSubtreeSiblingPath = makeTuple(NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, i =>
617
+ i < noteHashSubtreeSiblingPathArray.length ? noteHashSubtreeSiblingPathArray[i] : Fr.ZERO,
618
+ );
619
+
620
+ const contractSubtreeSiblingPathArray = await this.getSubtreeSiblingPath(
621
+ MerkleTreeId.CONTRACT_TREE,
622
+ CONTRACT_SUBTREE_HEIGHT,
623
+ );
624
+
625
+ const contractSubtreeSiblingPath = makeTuple(CONTRACT_SUBTREE_SIBLING_PATH_LENGTH, i =>
626
+ i < contractSubtreeSiblingPathArray.length ? contractSubtreeSiblingPathArray[i] : Fr.ZERO,
627
+ );
628
+
629
+ // Update the contract and note hash trees with the new items being inserted to get the new roots
630
+ // that will be used by the next iteration of the base rollup circuit, skipping the empty ones
631
+ const newContracts = tx.data.combinedData.newContracts.map(cd => cd.hash());
632
+ const newNoteHashes = tx.data.combinedData.newNoteHashes.map(x => x.value.toBuffer());
633
+
634
+ await this.db.appendLeaves(
635
+ MerkleTreeId.CONTRACT_TREE,
636
+ newContracts.map(x => x.toBuffer()),
637
+ );
638
+
639
+ await this.db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, newNoteHashes);
640
+
641
+ // The read witnesses for a given TX should be generated before the writes of the same TX are applied.
642
+ // All reads that refer to writes in the same tx are transient and can be simplified out.
643
+ const txPublicDataReadsInfo = await this.getPublicDataReadsInfo(tx);
644
+ const txPublicDataUpdateRequestInfo = await this.processPublicDataUpdateRequests(tx);
645
+
646
+ // Update the nullifier tree, capturing the low nullifier info for each individual operation
647
+ const {
648
+ lowLeavesWitnessData: nullifierWitnessLeaves,
649
+ newSubtreeSiblingPath: newNullifiersSubtreeSiblingPath,
650
+ sortedNewLeaves: sortedNewNullifiers,
651
+ sortedNewLeavesIndexes,
652
+ } = await this.db.batchInsert(
653
+ MerkleTreeId.NULLIFIER_TREE,
654
+ tx.data.combinedData.newNullifiers.map(sideEffectLinkedToNoteHash => sideEffectLinkedToNoteHash.value.toBuffer()),
655
+ NULLIFIER_SUBTREE_HEIGHT,
656
+ );
657
+ if (nullifierWitnessLeaves === undefined) {
658
+ throw new Error(`Could not craft nullifier batch insertion proofs`);
659
+ }
660
+
661
+ // Extract witness objects from returned data
662
+ const nullifierPredecessorMembershipWitnessesWithoutPadding: MembershipWitness<typeof NULLIFIER_TREE_HEIGHT>[] =
663
+ nullifierWitnessLeaves.map(l =>
664
+ MembershipWitness.fromBufferArray(l.index, assertLength(l.siblingPath.toBufferArray(), NULLIFIER_TREE_HEIGHT)),
665
+ );
666
+
667
+ const nullifierSubtreeSiblingPathArray = newNullifiersSubtreeSiblingPath.toFields();
668
+
669
+ const nullifierSubtreeSiblingPath = makeTuple(NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, i =>
670
+ i < nullifierSubtreeSiblingPathArray.length ? nullifierSubtreeSiblingPathArray[i] : Fr.ZERO,
671
+ );
672
+
673
+ const publicDataSiblingPath = txPublicDataUpdateRequestInfo.newPublicDataSubtreeSiblingPath;
674
+
675
+ const stateDiffHints = StateDiffHints.from({
676
+ nullifierPredecessorPreimages: makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i =>
677
+ i < nullifierWitnessLeaves.length
678
+ ? (nullifierWitnessLeaves[i].leafPreimage as NullifierLeafPreimage)
679
+ : NullifierLeafPreimage.empty(),
680
+ ),
681
+ nullifierPredecessorMembershipWitnesses: makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i =>
682
+ i < nullifierPredecessorMembershipWitnessesWithoutPadding.length
683
+ ? nullifierPredecessorMembershipWitnessesWithoutPadding[i]
684
+ : this.makeEmptyMembershipWitness(NULLIFIER_TREE_HEIGHT),
685
+ ),
686
+ sortedNullifiers: makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i => Fr.fromBuffer(sortedNewNullifiers[i])),
687
+ sortedNullifierIndexes: makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i => sortedNewLeavesIndexes[i]),
688
+ noteHashSubtreeSiblingPath,
689
+ nullifierSubtreeSiblingPath,
690
+ contractSubtreeSiblingPath,
691
+ publicDataSiblingPath,
692
+ });
693
+
694
+ const blockHash = tx.data.constants.historicalHeader.hash();
695
+ const archiveRootMembershipWitness = await this.getMembershipWitnessFor(
696
+ blockHash,
697
+ MerkleTreeId.ARCHIVE,
698
+ ARCHIVE_HEIGHT,
699
+ );
700
+
701
+ return BaseRollupInputs.from({
702
+ kernelData: this.getKernelDataFor(tx),
703
+ start,
704
+ stateDiffHints,
705
+
706
+ sortedPublicDataWrites: txPublicDataUpdateRequestInfo.sortedPublicDataWrites,
707
+ sortedPublicDataWritesIndexes: txPublicDataUpdateRequestInfo.sortedPublicDataWritesIndexes,
708
+ lowPublicDataWritesPreimages: txPublicDataUpdateRequestInfo.lowPublicDataWritesPreimages,
709
+ lowPublicDataWritesMembershipWitnesses: txPublicDataUpdateRequestInfo.lowPublicDataWritesMembershipWitnesses,
710
+ publicDataReadsPreimages: txPublicDataReadsInfo.newPublicDataReadsPreimages,
711
+ publicDataReadsMembershipWitnesses: txPublicDataReadsInfo.newPublicDataReadsWitnesses,
712
+
713
+ archiveRootMembershipWitness,
714
+
715
+ constants,
716
+ });
717
+ }
718
+
719
+ protected makeEmptyMembershipWitness<N extends number>(height: N) {
720
+ return new MembershipWitness(
721
+ height,
722
+ 0n,
723
+ makeTuple(height, () => Fr.ZERO),
724
+ );
725
+ }
726
+ }