@aztec/sequencer-client 0.41.0 → 0.43.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 (38) hide show
  1. package/dest/client/sequencer-client.js +2 -2
  2. package/dest/config.d.ts +13 -0
  3. package/dest/config.d.ts.map +1 -1
  4. package/dest/config.js +53 -45
  5. package/dest/publisher/l1-publisher.d.ts +7 -1
  6. package/dest/publisher/l1-publisher.d.ts.map +1 -1
  7. package/dest/publisher/l1-publisher.js +11 -3
  8. package/dest/publisher/viem-tx-sender.d.ts +3 -0
  9. package/dest/publisher/viem-tx-sender.d.ts.map +1 -1
  10. package/dest/publisher/viem-tx-sender.js +16 -1
  11. package/dest/receiver.d.ts +4 -1
  12. package/dest/receiver.d.ts.map +1 -1
  13. package/dest/sequencer/sequencer.d.ts +7 -4
  14. package/dest/sequencer/sequencer.d.ts.map +1 -1
  15. package/dest/sequencer/sequencer.js +57 -20
  16. package/dest/tx_validator/gas_validator.d.ts +2 -1
  17. package/dest/tx_validator/gas_validator.d.ts.map +1 -1
  18. package/dest/tx_validator/gas_validator.js +36 -8
  19. package/dest/tx_validator/phases_validator.d.ts +4 -4
  20. package/dest/tx_validator/phases_validator.d.ts.map +1 -1
  21. package/dest/tx_validator/phases_validator.js +34 -22
  22. package/dest/tx_validator/test_utils.d.ts +21 -0
  23. package/dest/tx_validator/test_utils.d.ts.map +1 -0
  24. package/dest/tx_validator/test_utils.js +19 -0
  25. package/dest/tx_validator/tx_validator_factory.d.ts +5 -5
  26. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  27. package/dest/tx_validator/tx_validator_factory.js +5 -5
  28. package/package.json +14 -14
  29. package/src/client/sequencer-client.ts +1 -1
  30. package/src/config.ts +56 -51
  31. package/src/publisher/l1-publisher.ts +18 -2
  32. package/src/publisher/viem-tx-sender.ts +16 -0
  33. package/src/receiver.ts +4 -1
  34. package/src/sequencer/sequencer.ts +73 -24
  35. package/src/tx_validator/gas_validator.ts +41 -7
  36. package/src/tx_validator/phases_validator.ts +41 -24
  37. package/src/tx_validator/test_utils.ts +37 -0
  38. package/src/tx_validator/tx_validator_factory.ts +6 -6
package/src/config.ts CHANGED
@@ -1,14 +1,11 @@
1
- import { type AllowedFunction } from '@aztec/circuit-types';
1
+ import { type AllowedElement } from '@aztec/circuit-types';
2
2
  import { AztecAddress, Fr, FunctionSelector, getContractClassFromArtifact } from '@aztec/circuits.js';
3
3
  import { type L1ContractAddresses, NULL_KEY } from '@aztec/ethereum';
4
4
  import { EthAddress } from '@aztec/foundation/eth-address';
5
- import { EcdsaAccountContractArtifact } from '@aztec/noir-contracts.js/EcdsaAccount';
6
5
  import { FPCContract } from '@aztec/noir-contracts.js/FPC';
7
- import { GasTokenContract } from '@aztec/noir-contracts.js/GasToken';
8
- import { SchnorrAccountContractArtifact } from '@aztec/noir-contracts.js/SchnorrAccount';
9
- import { SchnorrHardcodedAccountContractArtifact } from '@aztec/noir-contracts.js/SchnorrHardcodedAccount';
10
- import { SchnorrSingleKeyAccountContractArtifact } from '@aztec/noir-contracts.js/SchnorrSingleKeyAccount';
11
6
  import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token';
7
+ import { AuthRegistryAddress } from '@aztec/protocol-contracts/auth-registry';
8
+ import { GasTokenAddress } from '@aztec/protocol-contracts/gas-token';
12
9
 
13
10
  import { type Hex } from 'viem';
14
11
 
@@ -50,6 +47,7 @@ export function getConfigEnvVars(): SequencerClientConfig {
50
47
  SEQ_MIN_TX_PER_BLOCK,
51
48
  SEQ_ALLOWED_SETUP_FN,
52
49
  SEQ_ALLOWED_TEARDOWN_FN,
50
+ SEQ_MAX_BLOCK_SIZE_IN_BYTES,
53
51
  AVAILABILITY_ORACLE_CONTRACT_ADDRESS,
54
52
  ROLLUP_CONTRACT_ADDRESS,
55
53
  REGISTRY_CONTRACT_ADDRESS,
@@ -61,6 +59,7 @@ export function getConfigEnvVars(): SequencerClientConfig {
61
59
  FEE_RECIPIENT,
62
60
  ACVM_WORKING_DIRECTORY,
63
61
  ACVM_BINARY_PATH,
62
+ ENFORCE_FEES = '',
64
63
  } = process.env;
65
64
 
66
65
  const publisherPrivateKey: Hex = SEQ_PUBLISHER_PRIVATE_KEY
@@ -82,6 +81,7 @@ export function getConfigEnvVars(): SequencerClientConfig {
82
81
  };
83
82
 
84
83
  return {
84
+ enforceFees: ['1', 'true'].includes(ENFORCE_FEES),
85
85
  rpcUrl: ETHEREUM_HOST ? ETHEREUM_HOST : '',
86
86
  chainId: CHAIN_ID ? +CHAIN_ID : 31337, // 31337 is the default chain id for anvil
87
87
  version: VERSION ? +VERSION : 1, // 1 is our default version
@@ -89,6 +89,7 @@ export function getConfigEnvVars(): SequencerClientConfig {
89
89
  requiredConfirmations: SEQ_REQUIRED_CONFIRMATIONS ? +SEQ_REQUIRED_CONFIRMATIONS : 1,
90
90
  l1BlockPublishRetryIntervalMS: SEQ_PUBLISH_RETRY_INTERVAL_MS ? +SEQ_PUBLISH_RETRY_INTERVAL_MS : 1_000,
91
91
  transactionPollingIntervalMS: SEQ_TX_POLLING_INTERVAL_MS ? +SEQ_TX_POLLING_INTERVAL_MS : 1_000,
92
+ maxBlockSizeInBytes: SEQ_MAX_BLOCK_SIZE_IN_BYTES ? +SEQ_MAX_BLOCK_SIZE_IN_BYTES : undefined,
92
93
  l1Contracts: addresses,
93
94
  publisherPrivateKey,
94
95
  maxTxsPerBlock: SEQ_MAX_TX_PER_BLOCK ? +SEQ_MAX_TX_PER_BLOCK : 32,
@@ -98,73 +99,81 @@ export function getConfigEnvVars(): SequencerClientConfig {
98
99
  feeRecipient: FEE_RECIPIENT ? AztecAddress.fromString(FEE_RECIPIENT) : undefined,
99
100
  acvmWorkingDirectory: ACVM_WORKING_DIRECTORY ? ACVM_WORKING_DIRECTORY : undefined,
100
101
  acvmBinaryPath: ACVM_BINARY_PATH ? ACVM_BINARY_PATH : undefined,
101
- allowedFunctionsInSetup: SEQ_ALLOWED_SETUP_FN
102
+ allowedInSetup: SEQ_ALLOWED_SETUP_FN
102
103
  ? parseSequencerAllowList(SEQ_ALLOWED_SETUP_FN)
103
104
  : getDefaultAllowedSetupFunctions(),
104
- allowedFunctionsInTeardown: SEQ_ALLOWED_TEARDOWN_FN
105
+ allowedInTeardown: SEQ_ALLOWED_TEARDOWN_FN
105
106
  ? parseSequencerAllowList(SEQ_ALLOWED_TEARDOWN_FN)
106
107
  : getDefaultAllowedTeardownFunctions(),
107
108
  };
108
109
  }
109
110
 
110
- function parseSequencerAllowList(value: string): AllowedFunction[] {
111
- const entries: AllowedFunction[] = [];
111
+ /**
112
+ * Parses a string to a list of allowed elements.
113
+ * Each encoded is expected to be of one of the following formats
114
+ * `I:${address}`
115
+ * `I:${address}:${selector}`
116
+ * `C:${classId}`
117
+ * `C:${classId}:${selector}`
118
+ *
119
+ * @param value The string to parse
120
+ * @returns A list of allowed elements
121
+ */
122
+ export function parseSequencerAllowList(value: string): AllowedElement[] {
123
+ const entries: AllowedElement[] = [];
112
124
 
113
125
  if (!value) {
114
126
  return entries;
115
127
  }
116
128
 
117
129
  for (const val of value.split(',')) {
118
- const [identifierString, selectorString] = val.split(':');
119
- const selector = FunctionSelector.fromString(selectorString);
120
-
121
- if (identifierString.startsWith('0x')) {
122
- entries.push({
123
- address: AztecAddress.fromString(identifierString),
124
- selector,
125
- });
126
- } else {
127
- entries.push({
128
- classId: Fr.fromString(identifierString),
129
- selector,
130
- });
130
+ const [typeString, identifierString, selectorString] = val.split(':');
131
+ const selector = selectorString !== undefined ? FunctionSelector.fromString(selectorString) : undefined;
132
+
133
+ if (typeString === 'I') {
134
+ if (selector) {
135
+ entries.push({
136
+ address: AztecAddress.fromString(identifierString),
137
+ selector,
138
+ });
139
+ } else {
140
+ entries.push({
141
+ address: AztecAddress.fromString(identifierString),
142
+ });
143
+ }
144
+ } else if (typeString === 'C') {
145
+ if (selector) {
146
+ entries.push({
147
+ classId: Fr.fromString(identifierString),
148
+ selector,
149
+ });
150
+ } else {
151
+ entries.push({
152
+ classId: Fr.fromString(identifierString),
153
+ });
154
+ }
131
155
  }
132
156
  }
133
157
 
134
158
  return entries;
135
159
  }
136
160
 
137
- function getDefaultAllowedSetupFunctions(): AllowedFunction[] {
161
+ function getDefaultAllowedSetupFunctions(): AllowedElement[] {
138
162
  return [
163
+ // needed for authwit support
139
164
  {
140
- classId: getContractClassFromArtifact(SchnorrAccountContractArtifact).id,
141
- selector: FunctionSelector.fromSignature('approve_public_authwit(Field)'),
142
- },
143
- {
144
- classId: getContractClassFromArtifact(SchnorrHardcodedAccountContractArtifact).id,
145
- selector: FunctionSelector.fromSignature('approve_public_authwit(Field)'),
146
- },
147
- {
148
- classId: getContractClassFromArtifact(SchnorrSingleKeyAccountContractArtifact).id,
149
- selector: FunctionSelector.fromSignature('approve_public_authwit(Field)'),
165
+ address: AuthRegistryAddress,
150
166
  },
167
+ // needed for claiming on the same tx as a spend
151
168
  {
152
- classId: getContractClassFromArtifact(EcdsaAccountContractArtifact).id,
153
- selector: FunctionSelector.fromSignature('approve_public_authwit(Field)'),
154
- },
155
-
156
- // needed for native payments while they are not yet enshrined
157
- {
158
- classId: getContractClassFromArtifact(GasTokenContract.artifact).id,
159
- selector: FunctionSelector.fromSignature('pay_fee(Field)'),
169
+ address: GasTokenAddress,
170
+ selector: FunctionSelector.fromSignature('_increase_public_balance((Field),Field)'),
160
171
  },
161
-
162
172
  // needed for private transfers via FPC
163
173
  {
164
174
  classId: getContractClassFromArtifact(TokenContractArtifact).id,
165
175
  selector: FunctionSelector.fromSignature('_increase_public_balance((Field),Field)'),
166
176
  },
167
-
168
177
  {
169
178
  classId: getContractClassFromArtifact(FPCContract.artifact).id,
170
179
  selector: FunctionSelector.fromSignature('prepare_fee((Field),Field,(Field),Field)'),
@@ -172,19 +181,15 @@ function getDefaultAllowedSetupFunctions(): AllowedFunction[] {
172
181
  ];
173
182
  }
174
183
 
175
- function getDefaultAllowedTeardownFunctions(): AllowedFunction[] {
184
+ function getDefaultAllowedTeardownFunctions(): AllowedElement[] {
176
185
  return [
177
- {
178
- classId: getContractClassFromArtifact(GasTokenContract.artifact).id,
179
- selector: FunctionSelector.fromSignature('pay_fee(Field)'),
180
- },
181
186
  {
182
187
  classId: getContractClassFromArtifact(FPCContract.artifact).id,
183
- selector: FunctionSelector.fromSignature('pay_fee((Field),Field,(Field))'),
188
+ selector: FunctionSelector.fromSignature('pay_refund((Field),Field,(Field))'),
184
189
  },
185
190
  {
186
191
  classId: getContractClassFromArtifact(FPCContract.artifact).id,
187
- selector: FunctionSelector.fromSignature('pay_fee_with_shielded_rebate(Field,(Field),Field)'),
192
+ selector: FunctionSelector.fromSignature('pay_refund_with_shielded_rebate(Field,(Field),Field)'),
188
193
  },
189
194
  ];
190
195
  }
@@ -1,6 +1,8 @@
1
1
  import { type L2Block } from '@aztec/circuit-types';
2
2
  import { type L1PublishStats } from '@aztec/circuit-types/stats';
3
+ import { type EthAddress, type Fr, type Proof } from '@aztec/circuits.js';
3
4
  import { createDebugLogger } from '@aztec/foundation/log';
5
+ import { serializeToBuffer } from '@aztec/foundation/serialize';
4
6
  import { InterruptibleSleep } from '@aztec/foundation/sleep';
5
7
 
6
8
  import pick from 'lodash.pick';
@@ -40,6 +42,10 @@ export type MinimalTransactionReceipt = {
40
42
  * Pushes txs to the L1 chain and waits for their completion.
41
43
  */
42
44
  export interface L1PublisherTxSender {
45
+ getSenderAddress(): Promise<EthAddress>;
46
+
47
+ getSubmitterAddressForBlock(blockNumber: number): Promise<EthAddress>;
48
+
43
49
  /**
44
50
  * Publishes tx effects to Availability Oracle.
45
51
  * @param encodedBody - Encoded block body.
@@ -91,6 +97,8 @@ export type L1ProcessArgs = {
91
97
  archive: Buffer;
92
98
  /** L2 block body. */
93
99
  body: Buffer;
100
+ /** Aggregation object needed to verify the proof */
101
+ aggregationObject: Buffer;
94
102
  /** Root rollup proof of the L2 block. */
95
103
  proof: Buffer;
96
104
  };
@@ -113,12 +121,18 @@ export class L1Publisher implements L2BlockReceiver {
113
121
  this.sleepTimeMs = config?.l1BlockPublishRetryIntervalMS ?? 60_000;
114
122
  }
115
123
 
124
+ public async isItMyTurnToSubmit(blockNumber: number): Promise<boolean> {
125
+ const submitter = await this.txSender.getSubmitterAddressForBlock(blockNumber);
126
+ const sender = await this.txSender.getSenderAddress();
127
+ return submitter.isZero() || submitter.equals(sender);
128
+ }
129
+
116
130
  /**
117
131
  * Publishes L2 block on L1.
118
132
  * @param block - L2 block to publish.
119
133
  * @returns True once the tx has been confirmed and is successful, false on revert or interrupt, blocks otherwise.
120
134
  */
121
- public async processL2Block(block: L2Block): Promise<boolean> {
135
+ public async processL2Block(block: L2Block, aggregationObject: Fr[], proof: Proof): Promise<boolean> {
122
136
  // TODO(#4148) Remove this block number check, it's here because we don't currently have proper genesis state on the contract
123
137
  const lastArchive = block.header.lastArchive.root.toBuffer();
124
138
  if (block.number != 1 && !(await this.checkLastArchiveHash(lastArchive))) {
@@ -166,7 +180,8 @@ export class L1Publisher implements L2BlockReceiver {
166
180
  header: block.header.toBuffer(),
167
181
  archive: block.archive.root.toBuffer(),
168
182
  body: encodedBody,
169
- proof: Buffer.alloc(0),
183
+ aggregationObject: serializeToBuffer(aggregationObject),
184
+ proof: proof.withoutPublicInputs(),
170
185
  };
171
186
 
172
187
  // Process block
@@ -242,6 +257,7 @@ export class L1Publisher implements L2BlockReceiver {
242
257
  private async sendPublishTx(encodedBody: Buffer): Promise<string | undefined> {
243
258
  while (!this.interrupted) {
244
259
  try {
260
+ this.log.info(`TxEffects size=${encodedBody.length} bytes`);
245
261
  return await this.txSender.sendPublishTx(encodedBody);
246
262
  } catch (err) {
247
263
  this.log.error(`TxEffects publish failed`, err);
@@ -1,4 +1,5 @@
1
1
  import { type L2Block } from '@aztec/circuit-types';
2
+ import { EthAddress } from '@aztec/circuits.js';
2
3
  import { createEthereumChain } from '@aztec/ethereum';
3
4
  import { createDebugLogger } from '@aztec/foundation/log';
4
5
  import { AvailabilityOracleAbi, RollupAbi } from '@aztec/l1-artifacts';
@@ -71,6 +72,20 @@ export class ViemTxSender implements L1PublisherTxSender {
71
72
  });
72
73
  }
73
74
 
75
+ getSenderAddress(): Promise<EthAddress> {
76
+ return Promise.resolve(EthAddress.fromString(this.account.address));
77
+ }
78
+
79
+ async getSubmitterAddressForBlock(blockNumber: number): Promise<EthAddress> {
80
+ try {
81
+ const submitter = await this.rollupContract.read.whoseTurnIsIt([BigInt(blockNumber)]);
82
+ return EthAddress.fromString(submitter);
83
+ } catch (err) {
84
+ this.log.warn(`Failed to get submitter for block ${blockNumber}: ${err}`);
85
+ return EthAddress.ZERO;
86
+ }
87
+ }
88
+
74
89
  async getCurrentArchive(): Promise<Buffer> {
75
90
  const archive = await this.rollupContract.read.archive();
76
91
  return Buffer.from(archive.replace('0x', ''), 'hex');
@@ -145,6 +160,7 @@ export class ViemTxSender implements L1PublisherTxSender {
145
160
  const args = [
146
161
  `0x${encodedData.header.toString('hex')}`,
147
162
  `0x${encodedData.archive.toString('hex')}`,
163
+ `0x${encodedData.aggregationObject.toString('hex')}`,
148
164
  `0x${encodedData.proof.toString('hex')}`,
149
165
  ] as const;
150
166
 
package/src/receiver.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type L2Block } from '@aztec/circuit-types';
2
+ import type { Fr, Proof } from '@aztec/circuits.js';
2
3
 
3
4
  /**
4
5
  * Given the necessary rollup data, verifies it, and updates the underlying state accordingly to advance the state of the system.
@@ -8,6 +9,8 @@ export interface L2BlockReceiver {
8
9
  /**
9
10
  * Receive and L2 block and process it, returns true if successful.
10
11
  * @param l2BlockData - L2 block to process.
12
+ * @param aggregationObject - The aggregation object for the block's proof.
13
+ * @param proof - The proof for the block.
11
14
  */
12
- processL2Block(l2BlockData: L2Block): Promise<boolean>;
15
+ processL2Block(l2BlockData: L2Block, aggregationObject: Fr[], proof: Proof): Promise<boolean>;
13
16
  }
@@ -6,9 +6,14 @@ import {
6
6
  Tx,
7
7
  type TxValidator,
8
8
  } from '@aztec/circuit-types';
9
- import { type AllowedFunction, type BlockProver, PROVING_STATUS } from '@aztec/circuit-types/interfaces';
9
+ import {
10
+ type AllowedElement,
11
+ BlockProofError,
12
+ type BlockProver,
13
+ PROVING_STATUS,
14
+ } from '@aztec/circuit-types/interfaces';
10
15
  import { type L2BlockBuiltStats } from '@aztec/circuit-types/stats';
11
- import { AztecAddress, EthAddress } from '@aztec/circuits.js';
16
+ import { AztecAddress, EthAddress, type Proof } from '@aztec/circuits.js';
12
17
  import { Fr } from '@aztec/foundation/fields';
13
18
  import { createDebugLogger } from '@aztec/foundation/log';
14
19
  import { RunningPromise } from '@aztec/foundation/running-promise';
@@ -41,8 +46,9 @@ export class Sequencer {
41
46
  private _feeRecipient = AztecAddress.ZERO;
42
47
  private lastPublishedBlock = 0;
43
48
  private state = SequencerState.STOPPED;
44
- private allowedFunctionsInSetup: AllowedFunction[] = [];
45
- private allowedFunctionsInTeardown: AllowedFunction[] = [];
49
+ private allowedInSetup: AllowedElement[] = [];
50
+ private allowedInTeardown: AllowedElement[] = [];
51
+ private maxBlockSizeInBytes: number = 1024 * 1024;
46
52
 
47
53
  constructor(
48
54
  private publisher: L1Publisher,
@@ -81,12 +87,15 @@ export class Sequencer {
81
87
  if (config.feeRecipient) {
82
88
  this._feeRecipient = config.feeRecipient;
83
89
  }
84
- if (config.allowedFunctionsInSetup) {
85
- this.allowedFunctionsInSetup = config.allowedFunctionsInSetup;
90
+ if (config.allowedInSetup) {
91
+ this.allowedInSetup = config.allowedInSetup;
92
+ }
93
+ if (config.maxBlockSizeInBytes) {
94
+ this.maxBlockSizeInBytes = config.maxBlockSizeInBytes;
86
95
  }
87
96
  // TODO(#5917) remove this. it is no longer needed since we don't need to whitelist functions in teardown
88
- if (config.allowedFunctionsInTeardown) {
89
- this.allowedFunctionsInTeardown = config.allowedFunctionsInTeardown;
97
+ if (config.allowedInTeardown) {
98
+ this.allowedInTeardown = config.allowedInTeardown;
90
99
  }
91
100
  }
92
101
 
@@ -153,6 +162,18 @@ export class Sequencer {
153
162
  return;
154
163
  }
155
164
 
165
+ const historicalHeader = (await this.l2BlockSource.getBlock(-1))?.header;
166
+ const newBlockNumber =
167
+ (historicalHeader === undefined
168
+ ? await this.l2BlockSource.getBlockNumber()
169
+ : Number(historicalHeader.globalVariables.blockNumber.toBigInt())) + 1;
170
+
171
+ // Do not go forward with new block if not my turn
172
+ if (!(await this.publisher.isItMyTurnToSubmit(newBlockNumber))) {
173
+ this.log.verbose('Not my turn to submit block');
174
+ return;
175
+ }
176
+
156
177
  const workTimer = new Timer();
157
178
  this.state = SequencerState.WAITING_FOR_TXS;
158
179
 
@@ -163,12 +184,6 @@ export class Sequencer {
163
184
  }
164
185
  this.log.debug(`Retrieved ${pendingTxs.length} txs from P2P pool`);
165
186
 
166
- const historicalHeader = (await this.l2BlockSource.getBlock(-1))?.header;
167
- const newBlockNumber =
168
- (historicalHeader === undefined
169
- ? await this.l2BlockSource.getBlockNumber()
170
- : Number(historicalHeader.globalVariables.blockNumber.toBigInt())) + 1;
171
-
172
187
  /**
173
188
  * We'll call this function before running expensive operations to avoid wasted work.
174
189
  */
@@ -177,6 +192,9 @@ export class Sequencer {
177
192
  if (currentBlockNumber + 1 !== newBlockNumber) {
178
193
  throw new Error('New block was emitted while building block');
179
194
  }
195
+ if (!(await this.publisher.isItMyTurnToSubmit(newBlockNumber))) {
196
+ throw new Error(`Not this sequencer turn to submit block`);
197
+ }
180
198
  };
181
199
 
182
200
  const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(
@@ -186,10 +204,18 @@ export class Sequencer {
186
204
  );
187
205
 
188
206
  // TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
189
- const validTxs = await this.takeValidTxs(
207
+ const allValidTxs = await this.takeValidTxs(
190
208
  pendingTxs,
191
- this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.allowedFunctionsInSetup),
209
+ this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.allowedInSetup),
192
210
  );
211
+
212
+ // TODO: We are taking the size of the tx from private-land, but we should be doing this after running
213
+ // public functions. Only reason why we do it here now is because the public processor and orchestrator
214
+ // are set up such that they require knowing the total number of txs in advance. Still, main reason for
215
+ // exceeding max block size in bytes is contract class registration, which happens in private-land. This
216
+ // may break if we start emitting lots of log data from public-land.
217
+ const validTxs = this.takeTxsWithinMaxSize(allValidTxs);
218
+
193
219
  if (validTxs.length < this.minTxsPerBLock) {
194
220
  return;
195
221
  }
@@ -205,16 +231,15 @@ export class Sequencer {
205
231
  // We create a fresh processor each time to reset any cached state (eg storage writes)
206
232
  const processor = await this.publicProcessorFactory.create(historicalHeader, newGlobalVariables);
207
233
 
208
- const emptyTx = processor.makeEmptyProcessedTx();
209
-
210
234
  const blockBuildingTimer = new Timer();
211
235
 
212
236
  // We must initialise the block to be a power of 2 in size
213
237
  const numRealTxs = validTxs.length;
214
238
  const pow2 = Math.log2(numRealTxs);
239
+ // TODO turn this back into a Math.ceil once we can pad blocks to the next-power-of-2 with empty txs
215
240
  const totalTxs = 2 ** Math.ceil(pow2);
216
241
  const blockSize = Math.max(2, totalTxs);
217
- const blockTicket = await this.prover.startNewBlock(blockSize, newGlobalVariables, l1ToL2Messages, emptyTx);
242
+ const blockTicket = await this.prover.startNewBlock(blockSize, newGlobalVariables, l1ToL2Messages);
218
243
 
219
244
  const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
220
245
  processor.process(validTxs, blockSize, this.prover, this.txValidatorFactory.validatorForProcessedTxs()),
@@ -247,8 +272,7 @@ export class Sequencer {
247
272
  await assertBlockHeight();
248
273
 
249
274
  // Block is proven, now finalise and publish!
250
- const blockResult = await this.prover.finaliseBlock();
251
- const block = blockResult.block;
275
+ const { block, aggregationObject, proof } = await this.prover.finaliseBlock();
252
276
 
253
277
  await assertBlockHeight();
254
278
 
@@ -260,9 +284,14 @@ export class Sequencer {
260
284
  ...block.getStats(),
261
285
  } satisfies L2BlockBuiltStats);
262
286
 
263
- await this.publishL2Block(block);
287
+ await this.publishL2Block(block, aggregationObject, proof);
264
288
  this.log.info(`Submitted rollup block ${block.number} with ${processedTxs.length} transactions`);
265
289
  } catch (err) {
290
+ if (BlockProofError.isBlockProofError(err)) {
291
+ const txHashes = err.txHashes.filter(h => !h.isZero());
292
+ this.log.warn(`Proving block failed, removing ${txHashes.length} txs from pool`);
293
+ await this.p2pClient.deleteTxs(txHashes);
294
+ }
266
295
  this.log.error(`Rolling back world state DB due to error assembling block`, (err as any).stack);
267
296
  // Cancel any further proving on the block
268
297
  this.prover?.cancelBlock();
@@ -274,10 +303,10 @@ export class Sequencer {
274
303
  * Publishes the L2Block to the rollup contract.
275
304
  * @param block - The L2Block to be published.
276
305
  */
277
- protected async publishL2Block(block: L2Block) {
306
+ protected async publishL2Block(block: L2Block, aggregationObject: Fr[], proof: Proof) {
278
307
  // Publishes new block to the network and awaits the tx to be mined
279
308
  this.state = SequencerState.PUBLISHING_BLOCK;
280
- const publishedL2Block = await this.publisher.processL2Block(block);
309
+ const publishedL2Block = await this.publisher.processL2Block(block, aggregationObject, proof);
281
310
  if (publishedL2Block) {
282
311
  this.lastPublishedBlock = block.number;
283
312
  } else {
@@ -295,6 +324,26 @@ export class Sequencer {
295
324
  return valid.slice(0, this.maxTxsPerBlock);
296
325
  }
297
326
 
327
+ protected takeTxsWithinMaxSize(txs: Tx[]): Tx[] {
328
+ const maxSize = this.maxBlockSizeInBytes;
329
+ let totalSize = 0;
330
+
331
+ const toReturn: Tx[] = [];
332
+ for (const tx of txs) {
333
+ const txSize = tx.getSize() - tx.proof.toBuffer().length;
334
+ if (totalSize + txSize > maxSize) {
335
+ this.log.warn(
336
+ `Dropping tx ${tx.getTxHash()} with estimated size ${txSize} due to exceeding ${maxSize} block size limit (currently at ${totalSize})`,
337
+ );
338
+ continue;
339
+ }
340
+ toReturn.push(tx);
341
+ totalSize += txSize;
342
+ }
343
+
344
+ return toReturn;
345
+ }
346
+
298
347
  /**
299
348
  * Returns whether the previous block sent has been mined, and all dependencies have caught up with it.
300
349
  * @returns Boolean indicating if our dependencies are synced to the latest block.
@@ -1,6 +1,8 @@
1
- import { type Tx, type TxValidator } from '@aztec/circuit-types';
1
+ import { PublicKernelType, type Tx, type TxValidator } from '@aztec/circuit-types';
2
2
  import { type AztecAddress, type Fr } from '@aztec/circuits.js';
3
3
  import { createDebugLogger } from '@aztec/foundation/log';
4
+ import { GasTokenArtifact } from '@aztec/protocol-contracts/gas-token';
5
+ import { AbstractPhaseManager, computeFeePayerBalanceStorageSlot } from '@aztec/simulator';
4
6
 
5
7
  /** Provides a view into public contract state */
6
8
  export interface PublicStateSource {
@@ -11,12 +13,10 @@ export class GasTxValidator implements TxValidator<Tx> {
11
13
  #log = createDebugLogger('aztec:sequencer:tx_validator:tx_gas');
12
14
  #publicDataSource: PublicStateSource;
13
15
  #gasTokenAddress: AztecAddress;
14
- #requireFees: boolean;
15
16
 
16
- constructor(publicDataSource: PublicStateSource, gasTokenAddress: AztecAddress, requireFees = false) {
17
+ constructor(publicDataSource: PublicStateSource, gasTokenAddress: AztecAddress, public enforceFees: boolean) {
17
18
  this.#publicDataSource = publicDataSource;
18
19
  this.#gasTokenAddress = gasTokenAddress;
19
- this.#requireFees = requireFees;
20
20
  }
21
21
 
22
22
  async validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[]]> {
@@ -34,9 +34,43 @@ export class GasTxValidator implements TxValidator<Tx> {
34
34
  return [validTxs, invalidTxs];
35
35
  }
36
36
 
37
- #validateTxFee(_tx: Tx): Promise<boolean> {
38
- return Promise.resolve(true);
37
+ async #validateTxFee(tx: Tx): Promise<boolean> {
38
+ const feePayer = tx.data.feePayer;
39
+ // TODO(@spalladino) Eventually remove the is_zero condition as we should always charge fees to every tx
40
+ if (feePayer.isZero()) {
41
+ if (this.enforceFees) {
42
+ this.#log.warn(`Rejecting transaction ${tx.getTxHash()} due to missing fee payer`);
43
+ } else {
44
+ return true;
45
+ }
46
+ }
47
+
48
+ // Compute the maximum fee that this tx may pay, based on its gasLimits and maxFeePerGas
49
+ const feeLimit = tx.data.constants.txContext.gasSettings.getFeeLimit();
39
50
 
40
- // TODO(#5920) re-enable sequencer checks after we have fee payer in kernel outputs
51
+ // Read current balance of the feePayer
52
+ const initialBalance = await this.#publicDataSource.storageRead(
53
+ this.#gasTokenAddress,
54
+ computeFeePayerBalanceStorageSlot(feePayer),
55
+ );
56
+
57
+ // If there is a claim in this tx that increases the fee payer balance in gas token, add it to balance
58
+ const { [PublicKernelType.SETUP]: setupFns } = AbstractPhaseManager.extractEnqueuedPublicCallsByPhase(tx);
59
+ const claimFunctionCall = setupFns.find(
60
+ fn =>
61
+ fn.contractAddress.equals(this.#gasTokenAddress) &&
62
+ fn.callContext.msgSender.equals(this.#gasTokenAddress) &&
63
+ fn.functionSelector.equals(GasTokenArtifact.functions.find(f => f.name === '_increase_public_balance')!) &&
64
+ fn.args[0].equals(feePayer) &&
65
+ !fn.callContext.isStaticCall &&
66
+ !fn.callContext.isDelegateCall,
67
+ );
68
+
69
+ const balance = claimFunctionCall ? initialBalance.add(claimFunctionCall.args[1]) : initialBalance;
70
+ if (balance.lt(feeLimit)) {
71
+ this.#log.info(`Rejecting transaction due to not enough fee payer balance`, { feePayer, balance, feeLimit });
72
+ return false;
73
+ }
74
+ return true;
41
75
  }
42
76
  }