@aztec/sequencer-client 0.69.0-devnet → 0.69.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dest/client/sequencer-client.d.ts.map +1 -1
  2. package/dest/client/sequencer-client.js +2 -3
  3. package/dest/config.d.ts.map +1 -1
  4. package/dest/config.js +11 -1
  5. package/dest/index.d.ts +2 -1
  6. package/dest/index.d.ts.map +1 -1
  7. package/dest/index.js +3 -2
  8. package/dest/publisher/config.d.ts +4 -0
  9. package/dest/publisher/config.d.ts.map +1 -1
  10. package/dest/publisher/config.js +6 -1
  11. package/dest/publisher/l1-publisher.d.ts +13 -0
  12. package/dest/publisher/l1-publisher.d.ts.map +1 -1
  13. package/dest/publisher/l1-publisher.js +60 -50
  14. package/dest/sequencer/index.d.ts +1 -0
  15. package/dest/sequencer/index.d.ts.map +1 -1
  16. package/dest/sequencer/index.js +2 -1
  17. package/dest/sequencer/sequencer.d.ts +7 -16
  18. package/dest/sequencer/sequencer.d.ts.map +1 -1
  19. package/dest/sequencer/sequencer.js +53 -128
  20. package/dest/sequencer/utils.d.ts +2 -2
  21. package/dest/sequencer/utils.d.ts.map +1 -1
  22. package/dest/sequencer/utils.js +3 -3
  23. package/dest/tx_validator/gas_validator.d.ts +2 -3
  24. package/dest/tx_validator/gas_validator.d.ts.map +1 -1
  25. package/dest/tx_validator/gas_validator.js +9 -22
  26. package/dest/tx_validator/nullifier_cache.d.ts +16 -0
  27. package/dest/tx_validator/nullifier_cache.d.ts.map +1 -0
  28. package/dest/tx_validator/nullifier_cache.js +24 -0
  29. package/dest/tx_validator/phases_validator.d.ts +2 -3
  30. package/dest/tx_validator/phases_validator.d.ts.map +1 -1
  31. package/dest/tx_validator/phases_validator.js +15 -24
  32. package/dest/tx_validator/tx_validator_factory.d.ts +15 -14
  33. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  34. package/dest/tx_validator/tx_validator_factory.js +38 -24
  35. package/package.json +20 -19
  36. package/src/client/sequencer-client.ts +1 -2
  37. package/src/config.ts +10 -0
  38. package/src/index.ts +2 -1
  39. package/src/publisher/config.ts +10 -0
  40. package/src/publisher/l1-publisher.ts +70 -46
  41. package/src/sequencer/index.ts +1 -0
  42. package/src/sequencer/sequencer.ts +60 -177
  43. package/src/sequencer/utils.ts +2 -2
  44. package/src/tx_validator/gas_validator.ts +11 -24
  45. package/src/tx_validator/nullifier_cache.ts +29 -0
  46. package/src/tx_validator/phases_validator.ts +22 -33
  47. package/src/tx_validator/tx_validator_factory.ts +82 -40
@@ -3,7 +3,7 @@ import {
3
3
  type EpochProofClaim,
4
4
  type EpochProofQuote,
5
5
  type L2Block,
6
- SignatureDomainSeperator,
6
+ SignatureDomainSeparator,
7
7
  type TxHash,
8
8
  getHashedSignaturePayload,
9
9
  } from '@aztec/circuit-types';
@@ -27,7 +27,7 @@ import { type Logger, createLogger } from '@aztec/foundation/log';
27
27
  import { type Tuple, serializeToBuffer } from '@aztec/foundation/serialize';
28
28
  import { InterruptibleSleep } from '@aztec/foundation/sleep';
29
29
  import { Timer } from '@aztec/foundation/timer';
30
- import { EmpireBaseAbi, ExtRollupLibAbi, LeonidasLibAbi, RollupAbi, SlasherAbi } from '@aztec/l1-artifacts';
30
+ import { EmpireBaseAbi, RollupAbi, SlasherAbi } from '@aztec/l1-artifacts';
31
31
  import { type TelemetryClient } from '@aztec/telemetry-client';
32
32
 
33
33
  import pick from 'lodash.pick';
@@ -35,7 +35,7 @@ import {
35
35
  type BaseError,
36
36
  type Chain,
37
37
  type Client,
38
- ContractFunctionExecutionError,
38
+ type ContractFunctionExecutionError,
39
39
  ContractFunctionRevertedError,
40
40
  type GetContractReturnType,
41
41
  type Hex,
@@ -95,6 +95,8 @@ export type MinimalTransactionReceipt = {
95
95
  logs: any[];
96
96
  /** Block number in which this tx was mined. */
97
97
  blockNumber: bigint;
98
+ /** The block hash in which this tx was mined */
99
+ blockHash: `0x${string}`;
98
100
  };
99
101
 
100
102
  /** Arguments to the process method of the rollup contract */
@@ -175,6 +177,8 @@ export class L1Publisher {
175
177
  protected account: PrivateKeyAccount;
176
178
  protected ethereumSlotDuration: bigint;
177
179
 
180
+ private blobSinkUrl: string | undefined;
181
+
178
182
  // @note - with blobs, the below estimate seems too large.
179
183
  // Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
180
184
  // Total used for emptier block from above test: 429k (of which 84k is 1x blob)
@@ -189,6 +193,7 @@ export class L1Publisher {
189
193
  ) {
190
194
  this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
191
195
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
196
+ this.blobSinkUrl = config.blobSinkUrl;
192
197
  this.metrics = new L1PublisherMetrics(client, 'L1Publisher');
193
198
 
194
199
  const { l1RpcUrl: rpcUrl, l1ChainId: chainId, publisherPrivateKey, l1Contracts } = config;
@@ -416,38 +421,6 @@ export class L1Publisher {
416
421
  if (error instanceof ContractFunctionRevertedError) {
417
422
  const err = error as ContractFunctionRevertedError;
418
423
  this.log.debug(`Validation failed: ${err.message}`, err.data);
419
- } else if (error instanceof ContractFunctionExecutionError) {
420
- let err = error as ContractFunctionRevertedError;
421
- if (!tryGetCustomErrorName(err)) {
422
- // If we get here, it's because the custom error no longer exists in Rollup.sol,
423
- // but in another lib. The below reconstructs the error message.
424
- try {
425
- await this.publicClient.estimateGas({
426
- data: encodeFunctionData({
427
- abi: this.rollupContract.abi,
428
- functionName: 'validateHeader',
429
- args,
430
- }),
431
- account: this.account,
432
- to: this.rollupContract.address,
433
- });
434
- } catch (estGasErr: unknown) {
435
- const possibleAbis = [ExtRollupLibAbi, LeonidasLibAbi];
436
- possibleAbis.forEach(abi => {
437
- const possibleErr = getContractError(estGasErr as BaseError, {
438
- args: [],
439
- abi: abi,
440
- functionName: 'validateHeader',
441
- address: this.rollupContract.address,
442
- sender: this.account.address,
443
- });
444
- err = tryGetCustomErrorName(possibleErr) ? possibleErr : err;
445
- });
446
- }
447
- throw err;
448
- }
449
- } else {
450
- this.log.debug(`Unexpected error during validation: ${error}`);
451
424
  }
452
425
  throw error;
453
426
  }
@@ -593,16 +566,19 @@ export class L1Publisher {
593
566
 
594
567
  const consensusPayload = new ConsensusPayload(block.header, block.archive.root, txHashes ?? []);
595
568
 
596
- const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeperator.blockAttestation);
569
+ const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
570
+
571
+ const blobs = Blob.getBlobs(block.body.toBlobFields());
597
572
  const proposeTxArgs = {
598
573
  header: block.header.toBuffer(),
599
574
  archive: block.archive.root.toBuffer(),
600
575
  blockHash: block.header.hash().toBuffer(),
601
576
  body: block.body.toBuffer(),
602
- blobs: Blob.getBlobs(block.body.toBlobFields()),
577
+ blobs,
603
578
  attestations,
604
579
  txHashes: txHashes ?? [],
605
580
  };
581
+
606
582
  // Publish body and propose block (if not already published)
607
583
  if (this.interrupted) {
608
584
  this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx);
@@ -647,6 +623,12 @@ export class L1Publisher {
647
623
  };
648
624
  this.log.verbose(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx });
649
625
  this.metrics.recordProcessBlockTx(timer.ms(), stats);
626
+
627
+ // Send the blobs to the blob sink
628
+ this.sendBlobsToBlobSink(receipt.blockHash, blobs).catch(_err => {
629
+ this.log.error('Failed to send blobs to blob sink');
630
+ });
631
+
650
632
  return true;
651
633
  }
652
634
 
@@ -661,7 +643,7 @@ export class L1Publisher {
661
643
  address: this.rollupContract.address,
662
644
  },
663
645
  {
664
- blobs: proposeTxArgs.blobs.map(b => b.data),
646
+ blobs: proposeTxArgs.blobs.map(b => b.dataWithZeros),
665
647
  kzg,
666
648
  maxFeePerBlobGas: 10000000000n,
667
649
  },
@@ -757,8 +739,7 @@ export class L1Publisher {
757
739
  },
758
740
  ],
759
741
  });
760
- // If the above passes, we have a blob error. We cannot simulate blob txs, and failed txs no longer throw errors,
761
- // and viem provides no way to get the revert reason from a given tx.
742
+ // If the above passes, we have a blob error. We cannot simulate blob txs, and failed txs no longer throw errors.
762
743
  // Strangely, the only way to throw the revert reason as an error and provide blobs is prepareTransactionRequest.
763
744
  // See: https://github.com/wevm/viem/issues/2075
764
745
  // This throws a EstimateGasExecutionError with the custom error information:
@@ -770,13 +751,13 @@ export class L1Publisher {
770
751
  });
771
752
  return undefined;
772
753
  } catch (simulationErr: any) {
773
- // If we don't have a ContractFunctionExecutionError, we have a blob related error => use ExtRollupLibAbi to get the error msg.
754
+ // If we don't have a ContractFunctionExecutionError, we have a blob related error => use getContractError to get the error msg.
774
755
  const contractErr =
775
756
  simulationErr.name === 'ContractFunctionExecutionError'
776
757
  ? simulationErr
777
758
  : getContractError(simulationErr as BaseError, {
778
759
  args: [],
779
- abi: ExtRollupLibAbi,
760
+ abi: RollupAbi,
780
761
  functionName: args.functionName,
781
762
  address: args.address,
782
763
  sender: this.account.address,
@@ -966,7 +947,7 @@ export class L1Publisher {
966
947
  },
967
948
  {},
968
949
  {
969
- blobs: encodedData.blobs.map(b => b.data),
950
+ blobs: encodedData.blobs.map(b => b.dataWithZeros),
970
951
  kzg,
971
952
  maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
972
953
  },
@@ -1056,7 +1037,7 @@ export class L1Publisher {
1056
1037
  fixedGas: gas,
1057
1038
  },
1058
1039
  {
1059
- blobs: encodedData.blobs.map(b => b.data),
1040
+ blobs: encodedData.blobs.map(b => b.dataWithZeros),
1060
1041
  kzg,
1061
1042
  maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
1062
1043
  },
@@ -1095,7 +1076,7 @@ export class L1Publisher {
1095
1076
  },
1096
1077
  { fixedGas: gas },
1097
1078
  {
1098
- blobs: encodedData.blobs.map(b => b.data),
1079
+ blobs: encodedData.blobs.map(b => b.dataWithZeros),
1099
1080
  kzg,
1100
1081
  maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
1101
1082
  },
@@ -1137,6 +1118,7 @@ export class L1Publisher {
1137
1118
  gasPrice: receipt.effectiveGasPrice,
1138
1119
  logs: receipt.logs,
1139
1120
  blockNumber: receipt.blockNumber,
1121
+ blockHash: receipt.blockHash,
1140
1122
  };
1141
1123
  }
1142
1124
 
@@ -1152,9 +1134,51 @@ export class L1Publisher {
1152
1134
  protected async sleepOrInterrupted() {
1153
1135
  await this.interruptibleSleep.sleep(this.sleepTimeMs);
1154
1136
  }
1137
+
1138
+ /**
1139
+ * Send blobs to the blob sink
1140
+ *
1141
+ * If a blob sink url is configured, then we send blobs to the blob sink
1142
+ * - for now we use the blockHash as the identifier for the blobs;
1143
+ * In the future this will move to be the beacon block id - which takes a bit more work
1144
+ * to calculate and will need to be mocked in e2e tests
1145
+ */
1146
+ protected async sendBlobsToBlobSink(blockHash: string, blobs: Blob[]): Promise<boolean> {
1147
+ // TODO(md): for now we are assuming the indexes of the blobs will be 0, 1, 2
1148
+ // When in reality they will not, but for testing purposes this is fine
1149
+ if (!this.blobSinkUrl) {
1150
+ this.log.verbose('No blob sink url configured');
1151
+ return false;
1152
+ }
1153
+
1154
+ this.log.verbose(`Sending ${blobs.length} blobs to blob sink`);
1155
+ try {
1156
+ const res = await fetch(`${this.blobSinkUrl}/blob_sidecar`, {
1157
+ method: 'POST',
1158
+ headers: {
1159
+ 'Content-Type': 'application/json',
1160
+ },
1161
+ body: JSON.stringify({
1162
+ // eslint-disable-next-line camelcase
1163
+ block_id: blockHash,
1164
+ blobs: blobs.map((b, i) => ({ blob: b.toBuffer(), index: i })),
1165
+ }),
1166
+ });
1167
+
1168
+ if (res.ok) {
1169
+ return true;
1170
+ }
1171
+
1172
+ this.log.error('Failed to send blobs to blob sink', res.status);
1173
+ return false;
1174
+ } catch (err) {
1175
+ this.log.error(`Error sending blobs to blob sink`, err);
1176
+ return false;
1177
+ }
1178
+ }
1155
1179
  }
1156
1180
 
1157
- /**
1181
+ /*
1158
1182
  * Returns cost of calldata usage in Ethereum.
1159
1183
  * @param data - Calldata.
1160
1184
  * @returns 4 for each zero byte, 16 for each nonzero.
@@ -1,2 +1,3 @@
1
1
  export * from './config.js';
2
2
  export * from './sequencer.js';
3
+ export * from './allowed.js';
@@ -4,11 +4,9 @@ import {
4
4
  type L1ToL2MessageSource,
5
5
  type L2Block,
6
6
  type L2BlockSource,
7
- type ProcessedTx,
8
7
  SequencerConfigSchema,
9
8
  Tx,
10
9
  type TxHash,
11
- type TxValidator,
12
10
  type WorldStateSynchronizer,
13
11
  } from '@aztec/circuit-types';
14
12
  import type { AllowedElement, Signature, WorldStateSynchronizerStatus } from '@aztec/circuit-types/interfaces';
@@ -17,7 +15,9 @@ import {
17
15
  AppendOnlyTreeSnapshot,
18
16
  BlockHeader,
19
17
  ContentCommitment,
18
+ type ContractDataSource,
20
19
  GENESIS_ARCHIVE_ROOT,
20
+ Gas,
21
21
  type GlobalVariables,
22
22
  StateReference,
23
23
  } from '@aztec/circuits.js';
@@ -39,7 +39,7 @@ import { type GlobalVariableBuilder } from '../global_variable_builder/global_bu
39
39
  import { type L1Publisher, VoteType } from '../publisher/l1-publisher.js';
40
40
  import { prettyLogViemErrorMsg } from '../publisher/utils.js';
41
41
  import { type SlasherClient } from '../slasher/slasher_client.js';
42
- import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js';
42
+ import { createValidatorsForBlockBuilding } from '../tx_validator/tx_validator_factory.js';
43
43
  import { getDefaultAllowedSetupFunctions } from './allowed.js';
44
44
  import { type SequencerConfig } from './config.js';
45
45
  import { SequencerMetrics } from './metrics.js';
@@ -47,12 +47,6 @@ import { SequencerState, orderAttestations } from './utils.js';
47
47
 
48
48
  export { SequencerState };
49
49
 
50
- export type ShouldProposeArgs = {
51
- pendingTxsCount?: number;
52
- validTxsCount?: number;
53
- processedTxsCount?: number;
54
- };
55
-
56
50
  export class SequencerTooSlowError extends Error {
57
51
  constructor(
58
52
  public readonly currentState: SequencerState,
@@ -90,6 +84,7 @@ export class Sequencer {
90
84
  private state = SequencerState.STOPPED;
91
85
  private allowedInSetup: AllowedElement[] = getDefaultAllowedSetupFunctions();
92
86
  private maxBlockSizeInBytes: number = 1024 * 1024;
87
+ private maxBlockGas: Gas = new Gas(10e9, 10e9);
93
88
  private processTxTime: number = 12;
94
89
  private metrics: SequencerMetrics;
95
90
  private isFlushing: boolean = false;
@@ -112,7 +107,7 @@ export class Sequencer {
112
107
  private l2BlockSource: L2BlockSource,
113
108
  private l1ToL2MessageSource: L1ToL2MessageSource,
114
109
  private publicProcessorFactory: PublicProcessorFactory,
115
- private txValidatorFactory: TxValidatorFactory,
110
+ private contractDataSource: ContractDataSource,
116
111
  protected l1Constants: SequencerRollupConstants,
117
112
  private dateProvider: DateProvider,
118
113
  telemetry: TelemetryClient,
@@ -149,6 +144,12 @@ export class Sequencer {
149
144
  if (config.minTxsPerBlock !== undefined) {
150
145
  this.minTxsPerBLock = config.minTxsPerBlock;
151
146
  }
147
+ if (config.maxDABlockGas !== undefined) {
148
+ this.maxBlockGas = new Gas(config.maxDABlockGas, this.maxBlockGas.l2Gas);
149
+ }
150
+ if (config.maxL2BlockGas !== undefined) {
151
+ this.maxBlockGas = new Gas(this.maxBlockGas.daGas, config.maxL2BlockGas);
152
+ }
152
153
  if (config.coinbase) {
153
154
  this._coinbase = config.coinbase;
154
155
  }
@@ -179,7 +180,7 @@ export class Sequencer {
179
180
  // How late into the slot can we be to start working
180
181
  const initialTime = 2;
181
182
 
182
- // How long it takes to validate the txs collected and get ready to start building
183
+ // How long it takes to get ready to start building
183
184
  const blockPrepareTime = 1;
184
185
 
185
186
  // How long it takes to for attestations to travel across the p2p layer.
@@ -218,9 +219,9 @@ export class Sequencer {
218
219
  [SequencerState.SYNCHRONIZING]: this.aztecSlotDuration,
219
220
  // We always want to allow the full slot to check if we are the proposer
220
221
  [SequencerState.PROPOSER_CHECK]: this.aztecSlotDuration,
221
- // First transition towards building a block
222
- [SequencerState.WAITING_FOR_TXS]: initialTime,
223
- // We then validate the txs and prepare to start building the block
222
+ // How late we can start initializing a new block proposal
223
+ [SequencerState.INITIALIZING_PROPOSAL]: initialTime,
224
+ // When we start building a block
224
225
  [SequencerState.CREATING_BLOCK]: initialTime + blockPrepareTime,
225
226
  // We start collecting attestations after building the block
226
227
  [SequencerState.COLLECTING_ATTESTATIONS]: initialTime + blockPrepareTime + processTxsTime + blockValidationTime,
@@ -323,25 +324,27 @@ export class Sequencer {
323
324
  void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.GOVERNANCE);
324
325
  void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.SLASHING);
325
326
 
326
- if (!this.shouldProposeBlock(historicalHeader, {})) {
327
+ // Check the pool has enough txs to build a block
328
+ const pendingTxCount = this.p2pClient.getPendingTxCount();
329
+ if (pendingTxCount < this.minTxsPerBLock && !this.isFlushing) {
330
+ this.log.verbose(`Not enough txs to propose block. Got ${pendingTxCount} min ${this.minTxsPerBLock}.`, {
331
+ slot,
332
+ blockNumber: newBlockNumber,
333
+ });
334
+ await this.claimEpochProofRightIfAvailable(slot);
327
335
  return;
328
336
  }
329
337
 
338
+ this.setState(SequencerState.INITIALIZING_PROPOSAL, slot);
330
339
  this.log.verbose(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, {
331
340
  chainTipArchive: new Fr(chainTipArchive),
332
341
  blockNumber: newBlockNumber,
333
342
  slot,
334
343
  });
335
344
 
336
- this.setState(SequencerState.WAITING_FOR_TXS, slot);
337
-
338
- // Get txs to build the new block.
339
- const pendingTxs = await this.p2pClient.getPendingTxs();
340
-
341
- if (!this.shouldProposeBlock(historicalHeader, { pendingTxsCount: pendingTxs.length })) {
342
- await this.claimEpochProofRightIfAvailable(slot);
343
- return;
344
- }
345
+ // We don't fetch exactly maxTxsPerBlock txs here because we may not need all of them if we hit a limit before,
346
+ // and also we may need to fetch more if we don't have enough valid txs.
347
+ const pendingTxs = this.p2pClient.iteratePendingTxs();
345
348
 
346
349
  // If I created a "partial" header here that should make our job much easier.
347
350
  const proposalHeader = new BlockHeader(
@@ -353,35 +356,12 @@ export class Sequencer {
353
356
  Fr.ZERO,
354
357
  );
355
358
 
356
- // TODO: It should be responsibility of the P2P layer to validate txs before passing them on here.
357
- // TODO: We should validate only the number of txs we need to speed up this process.
358
- const allValidTxs = await this.takeValidTxs(
359
- pendingTxs,
360
- this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.allowedInSetup),
361
- );
362
-
363
- // TODO: We are taking the size of the tx from private-land, but we should be doing this after running
364
- // public functions. Only reason why we do it here now is because the public processor and orchestrator
365
- // are set up such that they require knowing the total number of txs in advance. Still, main reason for
366
- // exceeding max block size in bytes is contract class registration, which happens in private-land. This
367
- // may break if we start emitting lots of log data from public-land.
368
- const validTxs = this.takeTxsWithinMaxSize(allValidTxs);
369
-
370
- this.log.verbose(
371
- `Collected ${validTxs.length} txs out of ${allValidTxs.length} valid txs out of ${pendingTxs.length} total pending txs for block ${newBlockNumber}`,
372
- );
373
-
374
- // Bail if we don't have enough valid txs
375
- if (!this.shouldProposeBlock(historicalHeader, { validTxsCount: validTxs.length })) {
376
- await this.claimEpochProofRightIfAvailable(slot);
377
- return;
378
- }
379
-
380
359
  try {
360
+ // TODO(palla/txs) Is the note below still valid? We don't seem to be doing any rollback in there.
381
361
  // @note It is very important that the following function will FAIL and not just return early
382
362
  // if it have made any state changes. If not, we won't rollback the state, and you will
383
363
  // be in for a world of pain.
384
- await this.buildBlockAndAttemptToPublish(validTxs, proposalHeader, historicalHeader);
364
+ await this.buildBlockAndAttemptToPublish(pendingTxs, proposalHeader, historicalHeader);
385
365
  } catch (err) {
386
366
  this.log.error(`Error assembling block`, err, { blockNumber: newBlockNumber, slot });
387
367
  }
@@ -469,64 +449,20 @@ export class Sequencer {
469
449
  this.state = proposedState;
470
450
  }
471
451
 
472
- shouldProposeBlock(historicalHeader: BlockHeader | undefined, args: ShouldProposeArgs): boolean {
473
- if (this.isFlushing) {
474
- this.log.verbose(`Flushing all pending txs in new block`);
475
- return true;
476
- }
477
-
478
- // Compute time elapsed since the previous block
479
- const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
480
- const currentTime = Math.floor(Date.now() / 1000);
481
- const elapsedSinceLastBlock = currentTime - lastBlockTime;
482
- this.log.debug(
483
- `Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
484
- );
485
-
486
- // We need to have at least minTxsPerBLock txs.
487
- if (args.pendingTxsCount !== undefined && args.pendingTxsCount < this.minTxsPerBLock) {
488
- this.log.verbose(
489
- `Not creating block because not enough txs in the pool (got ${args.pendingTxsCount} min ${this.minTxsPerBLock})`,
490
- );
491
- return false;
492
- }
493
-
494
- // Bail if we don't have enough valid txs
495
- if (args.validTxsCount !== undefined && args.validTxsCount < this.minTxsPerBLock) {
496
- this.log.verbose(
497
- `Not creating block because not enough valid txs loaded from the pool (got ${args.validTxsCount} min ${this.minTxsPerBLock})`,
498
- );
499
- return false;
500
- }
501
-
502
- // TODO: This check should be processedTxs.length < this.minTxsPerBLock, so we don't publish a block with
503
- // less txs than the minimum. But that'd cause the entire block to be aborted and retried. Instead, we should
504
- // go back to the p2p pool and load more txs until we hit our minTxsPerBLock target. Only if there are no txs
505
- // we should bail.
506
- if (args.processedTxsCount === 0 && this.minTxsPerBLock > 0) {
507
- this.log.verbose('No txs processed correctly to build block.');
508
- return false;
509
- }
510
-
511
- return true;
512
- }
513
-
514
452
  /**
515
453
  * Build a block
516
454
  *
517
455
  * Shared between the sequencer and the validator for re-execution
518
456
  *
519
- * @param validTxs - The valid transactions to construct the block from
457
+ * @param pendingTxs - The pending transactions to construct the block from
520
458
  * @param newGlobalVariables - The global variables for the new block
521
459
  * @param historicalHeader - The historical header of the parent
522
- * @param interrupt - The interrupt callback, used to validate the block for submission and check if we should propose the block
523
460
  * @param opts - Whether to just validate the block as a validator, as opposed to building it as a proposal
524
461
  */
525
462
  private async buildBlock(
526
- validTxs: Tx[],
463
+ pendingTxs: Iterable<Tx>,
527
464
  newGlobalVariables: GlobalVariables,
528
465
  historicalHeader?: BlockHeader,
529
- interrupt?: (processedTxs: ProcessedTx[]) => Promise<void>,
530
466
  opts: { validateOnly?: boolean } = {},
531
467
  ) {
532
468
  const blockNumber = newGlobalVariables.blockNumber.toBigInt();
@@ -534,19 +470,9 @@ export class Sequencer {
534
470
 
535
471
  this.log.debug(`Requesting L1 to L2 messages from contract for block ${blockNumber}`);
536
472
  const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockNumber);
473
+ const msgCount = l1ToL2Messages.length;
537
474
 
538
- this.log.verbose(
539
- `Building block ${blockNumber} with ${validTxs.length} txs and ${l1ToL2Messages.length} messages`,
540
- {
541
- msgCount: l1ToL2Messages.length,
542
- txCount: validTxs.length,
543
- slot,
544
- blockNumber,
545
- },
546
- );
547
-
548
- const numRealTxs = validTxs.length;
549
- const blockSize = Math.max(2, numRealTxs);
475
+ this.log.verbose(`Building block ${blockNumber} for slot ${slot}`, { slot, blockNumber, msgCount });
550
476
 
551
477
  // Sync to the previous block at least
552
478
  await this.worldState.syncImmediate(newGlobalVariables.blockNumber.toNumber() - 1);
@@ -570,18 +496,30 @@ export class Sequencer {
570
496
  // We set the deadline for tx processing to the start of the CREATING_BLOCK phase, plus the expected time for tx processing.
571
497
  // Deadline is only set if enforceTimeTable is enabled.
572
498
  const processingEndTimeWithinSlot = this.timeTable[SequencerState.CREATING_BLOCK] + this.processTxTime;
573
- const processingDeadline = this.enforceTimeTable
499
+ const deadline = this.enforceTimeTable
574
500
  ? new Date((this.getSlotStartTimestamp(slot) + processingEndTimeWithinSlot) * 1000)
575
501
  : undefined;
576
- this.log.verbose(`Processing ${validTxs.length} txs`, {
502
+ this.log.verbose(`Processing pending txs`, {
577
503
  slot,
578
504
  slotStart: new Date(this.getSlotStartTimestamp(slot) * 1000),
579
505
  now: new Date(this.dateProvider.now()),
580
- deadline: processingDeadline,
506
+ deadline,
581
507
  });
582
- const processingTxValidator = this.txValidatorFactory.validatorForProcessedTxs(publicProcessorFork);
508
+
509
+ const validators = createValidatorsForBlockBuilding(
510
+ publicProcessorFork,
511
+ this.contractDataSource,
512
+ newGlobalVariables,
513
+ !!this.config.enforceFees,
514
+ this.allowedInSetup,
515
+ );
516
+
517
+ // REFACTOR: Public processor should just handle processing, one tx at a time. It should be responsibility
518
+ // of the sequencer to update world state and iterate over txs. We should refactor this along with unifying the
519
+ // publicProcessorFork and orchestratorFork, to avoid doing tree insertions twice when building the block.
520
+ const limits = { deadline, maxTransactions: this.maxTxsPerBlock, maxBlockSize: this.maxBlockSizeInBytes };
583
521
  const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
584
- processor.process(validTxs, blockSize, processingTxValidator, processingDeadline),
522
+ processor.process(pendingTxs, limits, validators),
585
523
  );
586
524
 
587
525
  if (failedTxs.length > 0) {
@@ -609,8 +547,6 @@ export class Sequencer {
609
547
  const duration = Number(end - start) / 1_000;
610
548
  this.metrics.recordBlockBuilderTreeInsertions(duration);
611
549
 
612
- await interrupt?.(processedTxs);
613
-
614
550
  // All real transactions have been added, set the block as full and pad if needed
615
551
  const block = await blockBuilder.setBlockCompleted();
616
552
 
@@ -618,7 +554,7 @@ export class Sequencer {
618
554
  block,
619
555
  publicProcessorDuration,
620
556
  numMsgs: l1ToL2Messages.length,
621
- numProcessedTxs: processedTxs.length,
557
+ numTxs: processedTxs.length,
622
558
  blockBuildingTimer,
623
559
  };
624
560
  } finally {
@@ -642,7 +578,7 @@ export class Sequencer {
642
578
  * @dev MUST throw instead of exiting early to ensure that world-state
643
579
  * is being rolled back if the block is dropped.
644
580
  *
645
- * @param validTxs - The valid transactions to construct the block from
581
+ * @param pendingTxs - Iterable of pending transactions to construct the block from
646
582
  * @param proposalHeader - The partial header constructed for the proposal
647
583
  * @param historicalHeader - The historical header of the parent
648
584
  */
@@ -650,7 +586,7 @@ export class Sequencer {
650
586
  [Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
651
587
  }))
652
588
  private async buildBlockAndAttemptToPublish(
653
- validTxs: Tx[],
589
+ pendingTxs: Iterable<Tx>,
654
590
  proposalHeader: BlockHeader,
655
591
  historicalHeader: BlockHeader | undefined,
656
592
  ): Promise<void> {
@@ -660,40 +596,19 @@ export class Sequencer {
660
596
  const blockNumber = newGlobalVariables.blockNumber.toNumber();
661
597
  const slot = newGlobalVariables.slotNumber.toBigInt();
662
598
 
663
- this.metrics.recordNewBlock(blockNumber, validTxs.length);
599
+ // this.metrics.recordNewBlock(blockNumber, validTxs.length);
664
600
  const workTimer = new Timer();
665
601
  this.setState(SequencerState.CREATING_BLOCK, slot);
666
602
 
667
- /**
668
- * BuildBlock is shared between the sequencer and the validator for re-execution
669
- * We use the interrupt callback to validate the block for submission and check if we should propose the block
670
- *
671
- * If we fail, we throw an error in order to roll back
672
- */
673
- const interrupt = async (processedTxs: ProcessedTx[]) => {
674
- await this.publisher.validateBlockForSubmission(proposalHeader);
675
-
676
- if (
677
- !this.shouldProposeBlock(historicalHeader, {
678
- validTxsCount: validTxs.length,
679
- processedTxsCount: processedTxs.length,
680
- })
681
- ) {
682
- // TODO: Roll back changes to world state
683
- throw new Error('Should not propose the block');
684
- }
685
- };
686
-
687
603
  // Start collecting proof quotes for the previous epoch if needed in the background
688
604
  const proofQuotePromise = this.createProofClaimForPreviousEpoch(slot);
689
605
 
690
606
  try {
691
- const buildBlockRes = await this.buildBlock(validTxs, newGlobalVariables, historicalHeader, interrupt);
692
- const { block, publicProcessorDuration, numProcessedTxs, numMsgs, blockBuildingTimer } = buildBlockRes;
607
+ const buildBlockRes = await this.buildBlock(pendingTxs, newGlobalVariables, historicalHeader);
608
+ const { block, publicProcessorDuration, numTxs, numMsgs, blockBuildingTimer } = buildBlockRes;
693
609
 
694
610
  // TODO(@PhilWindle) We should probably periodically check for things like another
695
611
  // block being published before ours instead of just waiting on our block
696
-
697
612
  await this.publisher.validateBlockForSubmission(block.header);
698
613
 
699
614
  const workDuration = workTimer.ms();
@@ -707,8 +622,8 @@ export class Sequencer {
707
622
  };
708
623
 
709
624
  const blockHash = block.hash();
710
- const txHashes = validTxs.map(tx => tx.getTxHash());
711
- this.log.info(`Built block ${block.number} with hash ${blockHash}`, {
625
+ const txHashes = block.body.txEffects.map(tx => tx.txHash);
626
+ this.log.info(`Built block ${block.number} for slot ${slot} with ${numTxs} txs`, {
712
627
  blockHash,
713
628
  globalVariables: block.header.globalVariables.toInspect(),
714
629
  txHashes,
@@ -734,14 +649,12 @@ export class Sequencer {
734
649
  await this.publishL2Block(block, attestations, txHashes, proofQuote);
735
650
  this.metrics.recordPublishedBlock(workDuration);
736
651
  this.log.info(
737
- `Published rollup block ${
738
- block.number
739
- } with ${numProcessedTxs} transactions and ${numMsgs} messages in ${Math.ceil(workDuration)}ms`,
652
+ `Published block ${block.number} with ${numTxs} txs and ${numMsgs} messages in ${Math.ceil(workDuration)}ms`,
740
653
  {
741
654
  blockNumber: block.number,
742
655
  blockHash: blockHash,
743
656
  slot,
744
- txCount: numProcessedTxs,
657
+ txCount: txHashes.length,
745
658
  msgCount: numMsgs,
746
659
  duration: Math.ceil(workDuration),
747
660
  submitter: this.publisher.getSenderAddress().toString(),
@@ -865,36 +778,6 @@ export class Sequencer {
865
778
  }
866
779
  }
867
780
 
868
- protected async takeValidTxs<T extends Tx | ProcessedTx>(txs: T[], validator: TxValidator<T>): Promise<T[]> {
869
- const [valid, invalid] = await validator.validateTxs(txs);
870
- if (invalid.length > 0) {
871
- this.log.debug(`Dropping invalid txs from the p2p pool ${Tx.getHashes(invalid).join(', ')}`);
872
- await this.p2pClient.deleteTxs(Tx.getHashes(invalid));
873
- }
874
-
875
- return valid.slice(0, this.maxTxsPerBlock);
876
- }
877
-
878
- protected takeTxsWithinMaxSize(txs: Tx[]): Tx[] {
879
- const maxSize = this.maxBlockSizeInBytes;
880
- let totalSize = 0;
881
-
882
- const toReturn: Tx[] = [];
883
- for (const tx of txs) {
884
- const txSize = tx.getSize() - tx.clientIvcProof.clientIvcProofBuffer.length;
885
- if (totalSize + txSize > maxSize) {
886
- this.log.debug(
887
- `Dropping tx ${tx.getTxHash()} with estimated size ${txSize} due to exceeding ${maxSize} block size limit (currently at ${totalSize})`,
888
- );
889
- continue;
890
- }
891
- toReturn.push(tx);
892
- totalSize += txSize;
893
- }
894
-
895
- return toReturn;
896
- }
897
-
898
781
  @trackSpan(
899
782
  'Sequencer.claimEpochProofRightIfAvailable',
900
783
  slotNumber => ({ [Attributes.SLOT_NUMBER]: Number(slotNumber) }),