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