@aztec/sequencer-client 0.32.1 → 0.33.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 (53) hide show
  1. package/dest/sequencer/abstract_phase_manager.d.ts +16 -4
  2. package/dest/sequencer/abstract_phase_manager.d.ts.map +1 -1
  3. package/dest/sequencer/abstract_phase_manager.js +38 -39
  4. package/dest/sequencer/app_logic_phase_manager.d.ts +1 -0
  5. package/dest/sequencer/app_logic_phase_manager.d.ts.map +1 -1
  6. package/dest/sequencer/app_logic_phase_manager.js +3 -3
  7. package/dest/sequencer/hints_builder.d.ts +7 -3
  8. package/dest/sequencer/hints_builder.d.ts.map +1 -1
  9. package/dest/sequencer/hints_builder.js +24 -4
  10. package/dest/sequencer/phase_manager_factory.d.ts.map +1 -1
  11. package/dest/sequencer/phase_manager_factory.js +5 -4
  12. package/dest/sequencer/public_processor.d.ts +5 -2
  13. package/dest/sequencer/public_processor.d.ts.map +1 -1
  14. package/dest/sequencer/public_processor.js +65 -27
  15. package/dest/sequencer/sequencer.d.ts +1 -11
  16. package/dest/sequencer/sequencer.d.ts.map +1 -1
  17. package/dest/sequencer/sequencer.js +33 -38
  18. package/dest/sequencer/setup_phase_manager.d.ts +1 -0
  19. package/dest/sequencer/setup_phase_manager.d.ts.map +1 -1
  20. package/dest/sequencer/setup_phase_manager.js +2 -2
  21. package/dest/sequencer/tail_phase_manager.d.ts +6 -1
  22. package/dest/sequencer/tail_phase_manager.d.ts.map +1 -1
  23. package/dest/sequencer/tail_phase_manager.js +28 -3
  24. package/dest/sequencer/teardown_phase_manager.d.ts +1 -0
  25. package/dest/sequencer/teardown_phase_manager.d.ts.map +1 -1
  26. package/dest/sequencer/teardown_phase_manager.js +2 -2
  27. package/dest/sequencer/tx_validator.d.ts.map +1 -1
  28. package/dest/sequencer/tx_validator.js +8 -7
  29. package/dest/sequencer/utils.d.ts.map +1 -1
  30. package/dest/sequencer/utils.js +8 -7
  31. package/dest/simulator/index.d.ts +2 -2
  32. package/dest/simulator/index.d.ts.map +1 -1
  33. package/dest/simulator/public_kernel.d.ts +2 -2
  34. package/dest/simulator/public_kernel.d.ts.map +1 -1
  35. package/dest/simulator/public_kernel.js +1 -1
  36. package/package.json +32 -15
  37. package/src/sequencer/abstract_phase_manager.ts +74 -63
  38. package/src/sequencer/app_logic_phase_manager.ts +2 -2
  39. package/src/sequencer/hints_builder.ts +42 -9
  40. package/src/sequencer/phase_manager_factory.ts +4 -3
  41. package/src/sequencer/public_processor.ts +97 -51
  42. package/src/sequencer/sequencer.ts +41 -50
  43. package/src/sequencer/setup_phase_manager.ts +1 -1
  44. package/src/sequencer/tail_phase_manager.ts +66 -2
  45. package/src/sequencer/teardown_phase_manager.ts +1 -1
  46. package/src/sequencer/tx_validator.ts +8 -6
  47. package/src/sequencer/utils.ts +7 -6
  48. package/src/simulator/index.ts +2 -1
  49. package/src/simulator/public_kernel.ts +2 -1
  50. package/dest/utils.d.ts +0 -12
  51. package/dest/utils.d.ts.map +0 -1
  52. package/dest/utils.js +0 -16
  53. package/src/utils.ts +0 -16
@@ -2,18 +2,21 @@ import { MerkleTreeId } from '@aztec/circuit-types';
2
2
  import {
3
3
  type Fr,
4
4
  MAX_NEW_NULLIFIERS_PER_TX,
5
- type MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX,
6
5
  type MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX,
7
6
  type MAX_NULLIFIER_READ_REQUESTS_PER_TX,
8
- type MAX_REVERTIBLE_NULLIFIERS_PER_TX,
7
+ MAX_PUBLIC_DATA_READS_PER_TX,
9
8
  MembershipWitness,
10
9
  NULLIFIER_TREE_HEIGHT,
10
+ PUBLIC_DATA_TREE_HEIGHT,
11
+ type PublicDataRead,
12
+ PublicDataTreeLeafPreimage,
11
13
  type ReadRequestContext,
12
14
  type SideEffectLinkedToNoteHash,
13
15
  buildNullifierNonExistentReadRequestHints,
14
16
  buildNullifierReadRequestHints,
15
- concatAccumulatedData,
17
+ mergeAccumulatedData,
16
18
  } from '@aztec/circuits.js';
19
+ import { makeTuple } from '@aztec/foundation/array';
17
20
  import { type Tuple } from '@aztec/foundation/serialize';
18
21
  import { type MerkleTreeOperations } from '@aztec/world-state';
19
22
 
@@ -22,22 +25,22 @@ export class HintsBuilder {
22
25
 
23
26
  getNullifierReadRequestHints(
24
27
  nullifierReadRequests: Tuple<ReadRequestContext, typeof MAX_NULLIFIER_READ_REQUESTS_PER_TX>,
25
- nullifiersNonRevertible: Tuple<SideEffectLinkedToNoteHash, typeof MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX>,
26
- nullifiersRevertible: Tuple<SideEffectLinkedToNoteHash, typeof MAX_REVERTIBLE_NULLIFIERS_PER_TX>,
28
+ nullifiersNonRevertible: Tuple<SideEffectLinkedToNoteHash, typeof MAX_NEW_NULLIFIERS_PER_TX>,
29
+ nullifiersRevertible: Tuple<SideEffectLinkedToNoteHash, typeof MAX_NEW_NULLIFIERS_PER_TX>,
27
30
  ) {
28
31
  return buildNullifierReadRequestHints(
29
32
  this,
30
33
  nullifierReadRequests,
31
- concatAccumulatedData(MAX_NEW_NULLIFIERS_PER_TX, nullifiersNonRevertible, nullifiersRevertible),
34
+ mergeAccumulatedData(MAX_NEW_NULLIFIERS_PER_TX, nullifiersNonRevertible, nullifiersRevertible),
32
35
  );
33
36
  }
34
37
 
35
38
  getNullifierNonExistentReadRequestHints(
36
39
  nullifierNonExistentReadRequests: Tuple<ReadRequestContext, typeof MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX>,
37
- nullifiersNonRevertible: Tuple<SideEffectLinkedToNoteHash, typeof MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX>,
38
- nullifiersRevertible: Tuple<SideEffectLinkedToNoteHash, typeof MAX_REVERTIBLE_NULLIFIERS_PER_TX>,
40
+ nullifiersNonRevertible: Tuple<SideEffectLinkedToNoteHash, typeof MAX_NEW_NULLIFIERS_PER_TX>,
41
+ nullifiersRevertible: Tuple<SideEffectLinkedToNoteHash, typeof MAX_NEW_NULLIFIERS_PER_TX>,
39
42
  ) {
40
- const pendingNullifiers = concatAccumulatedData(
43
+ const pendingNullifiers = mergeAccumulatedData(
41
44
  MAX_NEW_NULLIFIERS_PER_TX,
42
45
  nullifiersNonRevertible,
43
46
  nullifiersRevertible,
@@ -83,4 +86,34 @@ export class HintsBuilder {
83
86
 
84
87
  return { membershipWitness, leafPreimage };
85
88
  }
89
+
90
+ async getPublicDataReadsInfo(publicDataReads: PublicDataRead[]) {
91
+ const newPublicDataReadsWitnesses: Tuple<
92
+ MembershipWitness<typeof PUBLIC_DATA_TREE_HEIGHT>,
93
+ typeof MAX_PUBLIC_DATA_READS_PER_TX
94
+ > = makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => MembershipWitness.empty(PUBLIC_DATA_TREE_HEIGHT, 0n));
95
+
96
+ const newPublicDataReadsPreimages: Tuple<PublicDataTreeLeafPreimage, typeof MAX_PUBLIC_DATA_READS_PER_TX> =
97
+ makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => PublicDataTreeLeafPreimage.empty());
98
+
99
+ for (const i in publicDataReads) {
100
+ const leafSlot = publicDataReads[i].leafSlot.value;
101
+ const lowLeafResult = await this.db.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot);
102
+ if (!lowLeafResult) {
103
+ throw new Error(`Public data tree should have one initial leaf`);
104
+ }
105
+ const preimage = await this.db.getLeafPreimage(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
106
+ const path = await this.db.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
107
+ newPublicDataReadsWitnesses[i] = new MembershipWitness(
108
+ PUBLIC_DATA_TREE_HEIGHT,
109
+ BigInt(lowLeafResult.index),
110
+ path.toTuple<typeof PUBLIC_DATA_TREE_HEIGHT>(),
111
+ );
112
+ newPublicDataReadsPreimages[i] = preimage! as PublicDataTreeLeafPreimage;
113
+ }
114
+ return {
115
+ newPublicDataReadsWitnesses,
116
+ newPublicDataReadsPreimages,
117
+ };
118
+ }
86
119
  }
@@ -34,7 +34,8 @@ export class PhaseManagerFactory {
34
34
  publicContractsDB: ContractsDataSourcePublicDB,
35
35
  publicStateDB: PublicStateDB,
36
36
  ): AbstractPhaseManager | undefined {
37
- if (tx.data.needsSetup) {
37
+ const data = tx.data.forPublic!;
38
+ if (data.needsSetup) {
38
39
  return new SetupPhaseManager(
39
40
  db,
40
41
  publicExecutor,
@@ -44,7 +45,7 @@ export class PhaseManagerFactory {
44
45
  publicContractsDB,
45
46
  publicStateDB,
46
47
  );
47
- } else if (tx.data.needsAppLogic) {
48
+ } else if (data.needsAppLogic) {
48
49
  return new AppLogicPhaseManager(
49
50
  db,
50
51
  publicExecutor,
@@ -54,7 +55,7 @@ export class PhaseManagerFactory {
54
55
  publicContractsDB,
55
56
  publicStateDB,
56
57
  );
57
- } else if (tx.data.needsTeardown) {
58
+ } else if (data.needsTeardown) {
58
59
  return new TeardownPhaseManager(
59
60
  db,
60
61
  publicExecutor,
@@ -1,16 +1,17 @@
1
1
  import {
2
+ type BlockProver,
2
3
  type FailedTx,
3
4
  type ProcessedTx,
4
5
  type SimulationError,
5
6
  Tx,
6
- getPreviousOutputAndProof,
7
7
  makeEmptyProcessedTx,
8
8
  makeProcessedTx,
9
9
  toTxEffect,
10
10
  validateProcessedTx,
11
11
  } from '@aztec/circuit-types';
12
12
  import { type TxSequencerProcessingStats } from '@aztec/circuit-types/stats';
13
- import { type GlobalVariables, type Header } from '@aztec/circuits.js';
13
+ import { type GlobalVariables, type Header, type KernelCircuitPublicInputs } from '@aztec/circuits.js';
14
+ import { type ProcessReturnValues } from '@aztec/foundation/abi';
14
15
  import { createDebugLogger } from '@aztec/foundation/log';
15
16
  import { Timer } from '@aztec/foundation/timer';
16
17
  import { PublicExecutor, type PublicStateDB, type SimulationProvider } from '@aztec/simulator';
@@ -20,8 +21,9 @@ import { type MerkleTreeOperations } from '@aztec/world-state';
20
21
  import { type PublicKernelCircuitSimulator } from '../simulator/index.js';
21
22
  import { ContractsDataSourcePublicDB, WorldStateDB, WorldStatePublicDB } from '../simulator/public_executor.js';
22
23
  import { RealPublicKernelCircuitSimulator } from '../simulator/public_kernel.js';
23
- import { type AbstractPhaseManager } from './abstract_phase_manager.js';
24
+ import { type AbstractPhaseManager, PublicKernelPhase } from './abstract_phase_manager.js';
24
25
  import { PhaseManagerFactory } from './phase_manager_factory.js';
26
+ import { type TxValidator } from './tx_validator.js';
25
27
 
26
28
  /**
27
29
  * Creates new instances of PublicProcessor given the provided merkle tree db and contract data source.
@@ -84,60 +86,45 @@ export class PublicProcessor {
84
86
  * @param txs - Txs to process.
85
87
  * @returns The list of processed txs with their circuit simulation outputs.
86
88
  */
87
- public async process(txs: Tx[]): Promise<[ProcessedTx[], FailedTx[]]> {
89
+ public async process(
90
+ txs: Tx[],
91
+ maxTransactions = txs.length,
92
+ blockProver?: BlockProver,
93
+ txValidator?: TxValidator,
94
+ ): Promise<[ProcessedTx[], FailedTx[], ProcessReturnValues[]]> {
88
95
  // The processor modifies the tx objects in place, so we need to clone them.
89
96
  txs = txs.map(tx => Tx.clone(tx));
90
97
  const result: ProcessedTx[] = [];
91
98
  const failed: FailedTx[] = [];
99
+ const returns: ProcessReturnValues[] = [];
92
100
 
93
101
  for (const tx of txs) {
94
- let phase: AbstractPhaseManager | undefined = PhaseManagerFactory.phaseFromTx(
95
- tx,
96
- this.db,
97
- this.publicExecutor,
98
- this.publicKernel,
99
- this.globalVariables,
100
- this.historicalHeader,
101
- this.publicContractsDB,
102
- this.publicStateDB,
103
- );
104
- this.log(`Beginning processing in phase ${phase?.phase} for tx ${tx.getTxHash()}`);
105
- let { publicKernelPublicInput, previousProof: proof } = getPreviousOutputAndProof(tx, undefined, undefined);
106
- let revertReason: SimulationError | undefined;
107
- const timer = new Timer();
102
+ // only process up to the limit of the block
103
+ if (result.length >= maxTransactions) {
104
+ break;
105
+ }
108
106
  try {
109
- while (phase) {
110
- const output = await phase.handle(tx, publicKernelPublicInput, proof);
111
- publicKernelPublicInput = output.publicKernelOutput;
112
- proof = output.publicKernelProof;
113
- revertReason ??= output.revertReason;
114
- phase = PhaseManagerFactory.phaseFromOutput(
115
- publicKernelPublicInput,
116
- phase,
117
- this.db,
118
- this.publicExecutor,
119
- this.publicKernel,
120
- this.globalVariables,
121
- this.historicalHeader,
122
- this.publicContractsDB,
123
- this.publicStateDB,
124
- );
107
+ const [processedTx, returnValues] = !tx.hasPublicCalls()
108
+ ? [makeProcessedTx(tx, tx.data.toKernelCircuitPublicInputs(), tx.proof)]
109
+ : await this.processTxWithPublicCalls(tx);
110
+ validateProcessedTx(processedTx);
111
+ // Re-validate the transaction
112
+ if (txValidator) {
113
+ // Only accept processed transactions that are not double-spends,
114
+ // public functions emitting nullifiers would pass earlier check but fail here.
115
+ // Note that we're checking all nullifiers generated in the private execution twice,
116
+ // we could store the ones already checked and skip them here as an optimization.
117
+ const [_, invalid] = await txValidator.validateTxs([processedTx]);
118
+ if (invalid.length) {
119
+ throw new Error(`Transaction ${invalid[0].hash} invalid after processing public functions`);
120
+ }
125
121
  }
126
-
127
- const processedTransaction = makeProcessedTx(tx, publicKernelPublicInput, proof, revertReason);
128
- validateProcessedTx(processedTransaction);
129
-
130
- result.push(processedTransaction);
131
-
132
- this.log(`Processed public part of ${tx.data.endNonRevertibleData.newNullifiers[0].value}`, {
133
- eventName: 'tx-sequencer-processing',
134
- duration: timer.ms(),
135
- effectsSize: toTxEffect(processedTransaction).toBuffer().length,
136
- publicDataUpdateRequests:
137
- processedTransaction.data.combinedData.publicDataUpdateRequests.filter(x => !x.leafSlot.isZero()).length ??
138
- 0,
139
- ...tx.getStats(),
140
- } satisfies TxSequencerProcessingStats);
122
+ // if we were given a prover then send the transaction to it for proving
123
+ if (blockProver) {
124
+ await blockProver.addNewTx(processedTx);
125
+ }
126
+ result.push(processedTx);
127
+ returns.push(returnValues);
141
128
  } catch (err: any) {
142
129
  const errorMessage = err instanceof Error ? err.message : 'Unknown error';
143
130
  this.log.warn(`Failed to process tx ${tx.getTxHash()}: ${errorMessage}`);
@@ -146,10 +133,11 @@ export class PublicProcessor {
146
133
  tx,
147
134
  error: err instanceof Error ? err : new Error(errorMessage),
148
135
  });
136
+ returns.push([]);
149
137
  }
150
138
  }
151
139
 
152
- return [result, failed];
140
+ return [result, failed, returns];
153
141
  }
154
142
 
155
143
  /**
@@ -158,6 +146,64 @@ export class PublicProcessor {
158
146
  */
159
147
  public makeEmptyProcessedTx(): ProcessedTx {
160
148
  const { chainId, version } = this.globalVariables;
161
- return makeEmptyProcessedTx(this.historicalHeader, chainId, version);
149
+ return makeEmptyProcessedTx(this.historicalHeader.clone(), chainId, version);
150
+ }
151
+
152
+ private async processTxWithPublicCalls(tx: Tx): Promise<[ProcessedTx, ProcessReturnValues | undefined]> {
153
+ let returnValues: ProcessReturnValues = undefined;
154
+ let phase: AbstractPhaseManager | undefined = PhaseManagerFactory.phaseFromTx(
155
+ tx,
156
+ this.db,
157
+ this.publicExecutor,
158
+ this.publicKernel,
159
+ this.globalVariables,
160
+ this.historicalHeader,
161
+ this.publicContractsDB,
162
+ this.publicStateDB,
163
+ );
164
+ this.log(`Beginning processing in phase ${phase?.phase} for tx ${tx.getTxHash()}`);
165
+ let proof = tx.proof;
166
+ let publicKernelPublicInput = tx.data.toPublicKernelCircuitPublicInputs();
167
+ let finalKernelOutput: KernelCircuitPublicInputs | undefined;
168
+ let revertReason: SimulationError | undefined;
169
+ const timer = new Timer();
170
+ while (phase) {
171
+ const output = await phase.handle(tx, publicKernelPublicInput, proof);
172
+ if (phase.phase === PublicKernelPhase.APP_LOGIC) {
173
+ returnValues = output.returnValues;
174
+ }
175
+ publicKernelPublicInput = output.publicKernelOutput;
176
+ finalKernelOutput = output.finalKernelOutput;
177
+ proof = output.publicKernelProof;
178
+ revertReason ??= output.revertReason;
179
+ phase = PhaseManagerFactory.phaseFromOutput(
180
+ publicKernelPublicInput,
181
+ phase,
182
+ this.db,
183
+ this.publicExecutor,
184
+ this.publicKernel,
185
+ this.globalVariables,
186
+ this.historicalHeader,
187
+ this.publicContractsDB,
188
+ this.publicStateDB,
189
+ );
190
+ }
191
+
192
+ if (!finalKernelOutput) {
193
+ throw new Error('Final public kernel was not executed.');
194
+ }
195
+
196
+ const processedTx = makeProcessedTx(tx, finalKernelOutput, proof, revertReason);
197
+
198
+ this.log(`Processed public part of ${tx.getTxHash()}`, {
199
+ eventName: 'tx-sequencer-processing',
200
+ duration: timer.ms(),
201
+ effectsSize: toTxEffect(processedTx).toBuffer().length,
202
+ publicDataUpdateRequests:
203
+ processedTx.data.end.publicDataUpdateRequests.filter(x => !x.leafSlot.isZero()).length ?? 0,
204
+ ...tx.getStats(),
205
+ } satisfies TxSequencerProcessingStats);
206
+
207
+ return [processedTx, returnValues];
162
208
  }
163
209
  }
@@ -1,7 +1,7 @@
1
1
  import { type L1ToL2MessageSource, type L2Block, type L2BlockSource, type ProcessedTx, Tx } from '@aztec/circuit-types';
2
2
  import { type BlockProver, PROVING_STATUS } from '@aztec/circuit-types/interfaces';
3
3
  import { type L2BlockBuiltStats } from '@aztec/circuit-types/stats';
4
- import { AztecAddress, EthAddress, type GlobalVariables } from '@aztec/circuits.js';
4
+ import { AztecAddress, EthAddress } from '@aztec/circuits.js';
5
5
  import { Fr } from '@aztec/foundation/fields';
6
6
  import { createDebugLogger } from '@aztec/foundation/log';
7
7
  import { RunningPromise } from '@aztec/foundation/running-promise';
@@ -193,57 +193,75 @@ export class Sequencer {
193
193
  this.log.info(`Building block ${newBlockNumber} with ${validTxs.length} transactions`);
194
194
  this.state = SequencerState.CREATING_BLOCK;
195
195
 
196
+ // Get l1 to l2 messages from the contract
197
+ this.log('Requesting L1 to L2 messages from contract');
198
+ const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(newBlockNumber));
199
+ this.log(`Retrieved ${l1ToL2Messages.length} L1 to L2 messages for block ${newBlockNumber}`);
200
+
196
201
  // We create a fresh processor each time to reset any cached state (eg storage writes)
197
202
  const processor = await this.publicProcessorFactory.create(historicalHeader, newGlobalVariables);
198
- const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() => processor.process(validTxs));
203
+
204
+ const emptyTx = processor.makeEmptyProcessedTx();
205
+
206
+ const blockBuildingTimer = new Timer();
207
+
208
+ // We must initialise the block to be a power of 2 in size
209
+ const numRealTxs = validTxs.length;
210
+ const pow2 = Math.log2(numRealTxs);
211
+ const totalTxs = 2 ** Math.ceil(pow2);
212
+ const blockSize = Math.max(2, totalTxs);
213
+ const blockTicket = await this.prover.startNewBlock(blockSize, newGlobalVariables, l1ToL2Messages, emptyTx);
214
+
215
+ const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
216
+ processor.process(validTxs, blockSize, this.prover, txValidator),
217
+ );
199
218
  if (failedTxs.length > 0) {
200
219
  const failedTxData = failedTxs.map(fail => fail.tx);
201
220
  this.log(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
202
221
  await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
203
222
  }
204
223
 
205
- // Only accept processed transactions that are not double-spends,
206
- // public functions emitting nullifiers would pass earlier check but fail here.
207
- // Note that we're checking all nullifiers generated in the private execution twice,
208
- // we could store the ones already checked and skip them here as an optimization.
209
- const processedValidTxs = await this.takeValidTxs(processedTxs, txValidator);
210
-
211
- if (processedValidTxs.length === 0) {
224
+ if (processedTxs.length === 0) {
212
225
  this.log('No txs processed correctly to build block. Exiting');
226
+ this.prover.cancelBlock();
213
227
  return;
214
228
  }
215
229
 
216
230
  await assertBlockHeight();
217
231
 
218
- // Get l1 to l2 messages from the contract
219
- this.log('Requesting L1 to L2 messages from contract');
220
- const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(newBlockNumber));
221
- this.log(`Retrieved ${l1ToL2Messages.length} L1 to L2 messages for block ${newBlockNumber}`);
232
+ // All real transactions have been added, set the block as full and complete the proving.
233
+ await this.prover.setBlockCompleted();
222
234
 
223
- // Build the new block by running the rollup circuits
224
- this.log(`Assembling block with txs ${processedValidTxs.map(tx => tx.hash).join(', ')}`);
235
+ // Here we are now waiting for the block to be proven.
236
+ // TODO(@PhilWindle) We should probably periodically check for things like another
237
+ // block being published before ours instead of just waiting on our block
238
+ const result = await blockTicket.provingPromise;
239
+ if (result.status === PROVING_STATUS.FAILURE) {
240
+ throw new Error(`Block proving failed, reason: ${result.reason}`);
241
+ }
225
242
 
226
243
  await assertBlockHeight();
227
244
 
228
- const emptyTx = processor.makeEmptyProcessedTx();
229
- const [rollupCircuitsDuration, block] = await elapsed(() =>
230
- this.buildBlock(processedValidTxs, l1ToL2Messages, emptyTx, newGlobalVariables),
231
- );
245
+ // Block is proven, now finalise and publish!
246
+ const blockResult = await this.prover.finaliseBlock();
247
+ const block = blockResult.block;
248
+
249
+ await assertBlockHeight();
232
250
 
233
251
  this.log(`Assembled block ${block.number}`, {
234
252
  eventName: 'l2-block-built',
235
253
  duration: workTimer.ms(),
236
254
  publicProcessDuration: publicProcessorDuration,
237
- rollupCircuitsDuration: rollupCircuitsDuration,
255
+ rollupCircuitsDuration: blockBuildingTimer.ms(),
238
256
  ...block.getStats(),
239
257
  } satisfies L2BlockBuiltStats);
240
258
 
241
- await assertBlockHeight();
242
-
243
259
  await this.publishL2Block(block);
244
- this.log.info(`Submitted rollup block ${block.number} with ${processedValidTxs.length} transactions`);
260
+ this.log.info(`Submitted rollup block ${block.number} with ${processedTxs.length} transactions`);
245
261
  } catch (err) {
246
262
  this.log.error(`Rolling back world state DB due to error assembling block`, (err as any).stack);
263
+ // Cancel any further proving on the block
264
+ this.prover?.cancelBlock();
247
265
  await this.worldState.getLatest().rollback();
248
266
  }
249
267
  }
@@ -289,33 +307,6 @@ export class Sequencer {
289
307
  return min >= this.lastPublishedBlock;
290
308
  }
291
309
 
292
- /**
293
- * Pads the set of txs to a power of two and assembles a block by calling the block builder.
294
- * @param txs - Processed txs to include in the next block.
295
- * @param l1ToL2Messages - L1 to L2 messages to be part of the block.
296
- * @param emptyTx - Empty tx to repeat at the end of the block to pad to a power of two.
297
- * @param globalVariables - Global variables to use in the block.
298
- * @returns The new block.
299
- */
300
- protected async buildBlock(
301
- txs: ProcessedTx[],
302
- l1ToL2Messages: Fr[],
303
- emptyTx: ProcessedTx,
304
- globalVariables: GlobalVariables,
305
- ) {
306
- const blockTicket = await this.prover.startNewBlock(txs.length, globalVariables, l1ToL2Messages, emptyTx);
307
-
308
- for (const tx of txs) {
309
- await this.prover.addNewTx(tx);
310
- }
311
-
312
- const result = await blockTicket.provingPromise;
313
- if (result.status === PROVING_STATUS.FAILURE) {
314
- throw new Error(`Block proving failed, reason: ${result.reason}`);
315
- }
316
- return result.block;
317
- }
318
-
319
310
  get coinbase(): EthAddress {
320
311
  return this._coinbase;
321
312
  }
@@ -45,6 +45,6 @@ export class SetupPhaseManager extends AbstractPhaseManager {
45
45
  );
46
46
  tx.unencryptedLogs.addFunctionLogs(newUnencryptedFunctionLogs);
47
47
  await this.publicStateDB.checkpoint();
48
- return { publicKernelOutput, publicKernelProof, revertReason };
48
+ return { publicKernelOutput, publicKernelProof, revertReason, returnValues: undefined };
49
49
  }
50
50
  }
@@ -1,10 +1,19 @@
1
1
  import { type Tx } from '@aztec/circuit-types';
2
2
  import {
3
+ type Fr,
3
4
  type GlobalVariables,
4
5
  type Header,
6
+ type KernelCircuitPublicInputs,
7
+ MAX_NEW_NOTE_HASHES_PER_TX,
5
8
  type Proof,
6
9
  type PublicKernelCircuitPublicInputs,
10
+ PublicKernelTailCircuitPrivateInputs,
11
+ type SideEffect,
12
+ makeEmptyProof,
13
+ mergeAccumulatedData,
14
+ sortByCounter,
7
15
  } from '@aztec/circuits.js';
16
+ import { type Tuple } from '@aztec/foundation/serialize';
8
17
  import { type PublicExecutor, type PublicStateDB } from '@aztec/simulator';
9
18
  import { type MerkleTreeOperations } from '@aztec/world-state';
10
19
 
@@ -28,7 +37,7 @@ export class TailPhaseManager extends AbstractPhaseManager {
28
37
 
29
38
  async handle(tx: Tx, previousPublicKernelOutput: PublicKernelCircuitPublicInputs, previousPublicKernelProof: Proof) {
30
39
  this.log(`Processing tx ${tx.getTxHash()}`);
31
- const [publicKernelOutput, publicKernelProof] = await this.runKernelCircuit(
40
+ const [finalKernelOutput, publicKernelProof] = await this.runTailKernelCircuit(
32
41
  previousPublicKernelOutput,
33
42
  previousPublicKernelProof,
34
43
  ).catch(
@@ -42,6 +51,61 @@ export class TailPhaseManager extends AbstractPhaseManager {
42
51
  // commit the state updates from this transaction
43
52
  await this.publicStateDB.commit();
44
53
 
45
- return { publicKernelOutput, publicKernelProof, revertReason: undefined };
54
+ return {
55
+ publicKernelOutput: previousPublicKernelOutput,
56
+ finalKernelOutput,
57
+ publicKernelProof,
58
+ revertReason: undefined,
59
+ returnValues: undefined,
60
+ };
61
+ }
62
+
63
+ private async runTailKernelCircuit(
64
+ previousOutput: PublicKernelCircuitPublicInputs,
65
+ previousProof: Proof,
66
+ ): Promise<[KernelCircuitPublicInputs, Proof]> {
67
+ const output = await this.simulate(previousOutput, previousProof);
68
+
69
+ // Temporary hack. Should sort them in the tail circuit.
70
+ const noteHashes = mergeAccumulatedData(
71
+ MAX_NEW_NOTE_HASHES_PER_TX,
72
+ previousOutput.endNonRevertibleData.newNoteHashes,
73
+ previousOutput.end.newNoteHashes,
74
+ );
75
+ output.end.newNoteHashes = this.sortNoteHashes<typeof MAX_NEW_NOTE_HASHES_PER_TX>(noteHashes);
76
+
77
+ return [output, makeEmptyProof()];
78
+ }
79
+
80
+ private async simulate(
81
+ previousOutput: PublicKernelCircuitPublicInputs,
82
+ previousProof: Proof,
83
+ ): Promise<KernelCircuitPublicInputs> {
84
+ const previousKernel = this.getPreviousKernelData(previousOutput, previousProof);
85
+
86
+ const { validationRequests, endNonRevertibleData, end } = previousOutput;
87
+ const nullifierReadRequestHints = await this.hintsBuilder.getNullifierReadRequestHints(
88
+ validationRequests.nullifierReadRequests,
89
+ endNonRevertibleData.newNullifiers,
90
+ end.newNullifiers,
91
+ );
92
+ const nullifierNonExistentReadRequestHints = await this.hintsBuilder.getNullifierNonExistentReadRequestHints(
93
+ validationRequests.nullifierNonExistentReadRequests,
94
+ endNonRevertibleData.newNullifiers,
95
+ end.newNullifiers,
96
+ );
97
+ const inputs = new PublicKernelTailCircuitPrivateInputs(
98
+ previousKernel,
99
+ nullifierReadRequestHints,
100
+ nullifierNonExistentReadRequestHints,
101
+ );
102
+ return this.publicKernel.publicKernelCircuitTail(inputs);
103
+ }
104
+
105
+ private sortNoteHashes<N extends number>(noteHashes: Tuple<SideEffect, N>): Tuple<Fr, N> {
106
+ return sortByCounter(noteHashes.map(n => ({ ...n, counter: n.counter.toNumber() }))).map(n => n.value) as Tuple<
107
+ Fr,
108
+ N
109
+ >;
46
110
  }
47
111
  }
@@ -45,6 +45,6 @@ export class TeardownPhaseManager extends AbstractPhaseManager {
45
45
  );
46
46
  tx.unencryptedLogs.addFunctionLogs(newUnencryptedFunctionLogs);
47
47
  await this.publicStateDB.checkpoint();
48
- return { publicKernelOutput, publicKernelProof, revertReason };
48
+ return { publicKernelOutput, publicKernelProof, revertReason, returnValues: undefined };
49
49
  }
50
50
  }
@@ -138,9 +138,7 @@ export class TxValidator {
138
138
  * @returns Whether this is a problematic double spend that the L1 contract would reject.
139
139
  */
140
140
  async #validateNullifiers(tx: Tx | ProcessedTx, thisBlockNullifiers: Set<bigint>): Promise<TxValidationStatus> {
141
- const newNullifiers = [...tx.data.endNonRevertibleData.newNullifiers, ...tx.data.end.newNullifiers]
142
- .filter(x => !x.isEmpty())
143
- .map(x => x.value.toBigInt());
141
+ const newNullifiers = tx.data.getNonEmptyNullifiers().map(x => x.toBigInt());
144
142
 
145
143
  // Ditch this tx if it has repeated nullifiers
146
144
  const uniqueNullifiers = new Set(newNullifiers);
@@ -172,7 +170,7 @@ export class TxValidator {
172
170
  }
173
171
 
174
172
  async #validateGasBalance(tx: Tx): Promise<TxValidationStatus> {
175
- if (!tx.data.needsTeardown) {
173
+ if (!tx.data.forPublic || !tx.data.forPublic.needsTeardown) {
176
174
  return VALID_TX;
177
175
  }
178
176
 
@@ -200,7 +198,11 @@ export class TxValidator {
200
198
  }
201
199
 
202
200
  #validateMaxBlockNumber(tx: Tx | ProcessedTx): TxValidationStatus {
203
- const maxBlockNumber = tx.data.rollupValidationRequests.maxBlockNumber;
201
+ const target =
202
+ tx instanceof Tx
203
+ ? tx.data.forRollup?.rollupValidationRequests || tx.data.forPublic!.validationRequests.forRollup
204
+ : tx.data.rollupValidationRequests;
205
+ const maxBlockNumber = target.maxBlockNumber;
204
206
 
205
207
  if (maxBlockNumber.isSome && maxBlockNumber.value < this.#globalVariables.blockNumber) {
206
208
  this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for low max block number`);
@@ -211,7 +213,7 @@ export class TxValidator {
211
213
  }
212
214
 
213
215
  async #validateFee(tx: Tx): Promise<TxValidationStatus> {
214
- if (!tx.data.needsTeardown) {
216
+ if (!tx.data.forPublic || !tx.data.forPublic.needsTeardown) {
215
217
  // TODO check if fees are mandatory and reject this tx
216
218
  this.#log.debug(`Tx ${Tx.getHash(tx)} doesn't pay for gas`);
217
219
  return VALID_TX;
@@ -7,13 +7,14 @@ import { CallRequest } from '@aztec/circuits.js';
7
7
  * @returns The highest side effect counter in the transaction so far
8
8
  */
9
9
  export function lastSideEffectCounter(tx: Tx): number {
10
+ const data = tx.data.forPublic!;
10
11
  const sideEffectCounters = [
11
- ...tx.data.endNonRevertibleData.newNoteHashes,
12
- ...tx.data.endNonRevertibleData.newNullifiers,
13
- ...tx.data.endNonRevertibleData.publicCallStack,
14
- ...tx.data.end.newNoteHashes,
15
- ...tx.data.end.newNullifiers,
16
- ...tx.data.end.publicCallStack,
12
+ ...data.endNonRevertibleData.newNoteHashes,
13
+ ...data.endNonRevertibleData.newNullifiers,
14
+ ...data.endNonRevertibleData.publicCallStack,
15
+ ...data.end.newNoteHashes,
16
+ ...data.end.newNullifiers,
17
+ ...data.end.publicCallStack,
17
18
  ];
18
19
 
19
20
  let max = 0;
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type KernelCircuitPublicInputs,
2
3
  type PublicKernelCircuitPrivateInputs,
3
4
  type PublicKernelCircuitPublicInputs,
4
5
  type PublicKernelTailCircuitPrivateInputs,
@@ -31,5 +32,5 @@ export interface PublicKernelCircuitSimulator {
31
32
  * @param inputs - Inputs to the circuit.
32
33
  * @returns The public inputs as outputs of the simulation.
33
34
  */
34
- publicKernelCircuitTail(inputs: PublicKernelTailCircuitPrivateInputs): Promise<PublicKernelCircuitPublicInputs>;
35
+ publicKernelCircuitTail(inputs: PublicKernelTailCircuitPrivateInputs): Promise<KernelCircuitPublicInputs>;
35
36
  }
@@ -1,5 +1,6 @@
1
1
  import { type CircuitSimulationStats } from '@aztec/circuit-types/stats';
2
2
  import {
3
+ type KernelCircuitPublicInputs,
3
4
  type PublicKernelCircuitPrivateInputs,
4
5
  type PublicKernelCircuitPublicInputs,
5
6
  type PublicKernelTailCircuitPrivateInputs,
@@ -120,7 +121,7 @@ export class RealPublicKernelCircuitSimulator implements PublicKernelCircuitSimu
120
121
  */
121
122
  public async publicKernelCircuitTail(
122
123
  input: PublicKernelTailCircuitPrivateInputs,
123
- ): Promise<PublicKernelCircuitPublicInputs> {
124
+ ): Promise<KernelCircuitPublicInputs> {
124
125
  const inputWitness = convertPublicTailInputsToWitnessMap(input);
125
126
  const [duration, witness] = await elapsed(() =>
126
127
  this.wasmSimulator.simulateCircuit(inputWitness, PublicKernelTailArtifact),