@aztec/sequencer-client 0.1.0-alpha11

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