@aztec/sequencer-client 0.69.0-devnet → 0.69.1-devnet

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 (70) hide show
  1. package/dest/client/sequencer-client.d.ts +2 -0
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +3 -4
  4. package/dest/config.d.ts.map +1 -1
  5. package/dest/config.js +11 -1
  6. package/dest/index.d.ts +2 -1
  7. package/dest/index.d.ts.map +1 -1
  8. package/dest/index.js +3 -2
  9. package/dest/publisher/config.d.ts +4 -0
  10. package/dest/publisher/config.d.ts.map +1 -1
  11. package/dest/publisher/config.js +6 -1
  12. package/dest/publisher/index.d.ts +0 -1
  13. package/dest/publisher/index.d.ts.map +1 -1
  14. package/dest/publisher/index.js +1 -2
  15. package/dest/publisher/l1-publisher.d.ts +21 -2
  16. package/dest/publisher/l1-publisher.d.ts.map +1 -1
  17. package/dest/publisher/l1-publisher.js +91 -95
  18. package/dest/sequencer/index.d.ts +1 -0
  19. package/dest/sequencer/index.d.ts.map +1 -1
  20. package/dest/sequencer/index.js +2 -1
  21. package/dest/sequencer/sequencer.d.ts +21 -30
  22. package/dest/sequencer/sequencer.d.ts.map +1 -1
  23. package/dest/sequencer/sequencer.js +60 -130
  24. package/dest/sequencer/utils.d.ts +2 -2
  25. package/dest/sequencer/utils.d.ts.map +1 -1
  26. package/dest/sequencer/utils.js +3 -3
  27. package/dest/test/index.d.ts +18 -0
  28. package/dest/test/index.d.ts.map +1 -0
  29. package/dest/test/index.js +8 -0
  30. package/dest/{publisher → test}/test-l1-publisher.d.ts +1 -1
  31. package/dest/test/test-l1-publisher.d.ts.map +1 -0
  32. package/dest/test/test-l1-publisher.js +11 -0
  33. package/dest/tx_validator/archive_cache.d.ts +14 -0
  34. package/dest/tx_validator/archive_cache.d.ts.map +1 -0
  35. package/dest/tx_validator/archive_cache.js +22 -0
  36. package/dest/tx_validator/gas_validator.d.ts +2 -3
  37. package/dest/tx_validator/gas_validator.d.ts.map +1 -1
  38. package/dest/tx_validator/gas_validator.js +9 -22
  39. package/dest/tx_validator/nullifier_cache.d.ts +16 -0
  40. package/dest/tx_validator/nullifier_cache.d.ts.map +1 -0
  41. package/dest/tx_validator/nullifier_cache.js +24 -0
  42. package/dest/tx_validator/phases_validator.d.ts +2 -3
  43. package/dest/tx_validator/phases_validator.d.ts.map +1 -1
  44. package/dest/tx_validator/phases_validator.js +15 -24
  45. package/dest/tx_validator/tx_validator_factory.d.ts +15 -14
  46. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  47. package/dest/tx_validator/tx_validator_factory.js +41 -24
  48. package/package.json +23 -20
  49. package/src/client/sequencer-client.ts +5 -3
  50. package/src/config.ts +10 -0
  51. package/src/index.ts +2 -1
  52. package/src/publisher/config.ts +10 -0
  53. package/src/publisher/index.ts +0 -1
  54. package/src/publisher/l1-publisher.ts +119 -93
  55. package/src/sequencer/index.ts +1 -0
  56. package/src/sequencer/sequencer.ts +82 -193
  57. package/src/sequencer/utils.ts +2 -2
  58. package/src/test/index.ts +23 -0
  59. package/src/{publisher → test}/test-l1-publisher.ts +1 -1
  60. package/src/tx_validator/archive_cache.ts +27 -0
  61. package/src/tx_validator/gas_validator.ts +11 -24
  62. package/src/tx_validator/nullifier_cache.ts +29 -0
  63. package/src/tx_validator/phases_validator.ts +22 -33
  64. package/src/tx_validator/tx_validator_factory.ts +89 -40
  65. package/dest/publisher/test-l1-publisher.d.ts.map +0 -1
  66. package/dest/publisher/test-l1-publisher.js +0 -11
  67. package/dest/publisher/utils.d.ts +0 -2
  68. package/dest/publisher/utils.d.ts.map +0 -1
  69. package/dest/publisher/utils.js +0 -13
  70. package/src/publisher/utils.ts +0 -14
@@ -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,10 +15,13 @@ 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';
24
+ import { prettyLogViemErrorMsg } from '@aztec/ethereum';
24
25
  import { AztecAddress } from '@aztec/foundation/aztec-address';
25
26
  import { omit } from '@aztec/foundation/collection';
26
27
  import { EthAddress } from '@aztec/foundation/eth-address';
@@ -37,9 +38,8 @@ import { type ValidatorClient } from '@aztec/validator-client';
37
38
 
38
39
  import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
39
40
  import { type L1Publisher, VoteType } from '../publisher/l1-publisher.js';
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,7 +84,8 @@ export class Sequencer {
90
84
  private state = SequencerState.STOPPED;
91
85
  private allowedInSetup: AllowedElement[] = getDefaultAllowedSetupFunctions();
92
86
  private maxBlockSizeInBytes: number = 1024 * 1024;
93
- private processTxTime: number = 12;
87
+ private maxBlockGas: Gas = new Gas(10e9, 10e9);
88
+ protected processTxTime: number = 12;
94
89
  private metrics: SequencerMetrics;
95
90
  private isFlushing: boolean = false;
96
91
 
@@ -102,22 +97,22 @@ export class Sequencer {
102
97
  protected enforceTimeTable: boolean = false;
103
98
 
104
99
  constructor(
105
- private publisher: L1Publisher,
106
- private validatorClient: ValidatorClient | undefined, // During migration the validator client can be inactive
107
- private globalsBuilder: GlobalVariableBuilder,
108
- private p2pClient: P2P,
109
- private worldState: WorldStateSynchronizer,
110
- private slasherClient: SlasherClient,
111
- private blockBuilderFactory: BlockBuilderFactory,
112
- private l2BlockSource: L2BlockSource,
113
- private l1ToL2MessageSource: L1ToL2MessageSource,
114
- private publicProcessorFactory: PublicProcessorFactory,
115
- private txValidatorFactory: TxValidatorFactory,
100
+ protected publisher: L1Publisher,
101
+ protected validatorClient: ValidatorClient | undefined, // During migration the validator client can be inactive
102
+ protected globalsBuilder: GlobalVariableBuilder,
103
+ protected p2pClient: P2P,
104
+ protected worldState: WorldStateSynchronizer,
105
+ protected slasherClient: SlasherClient,
106
+ protected blockBuilderFactory: BlockBuilderFactory,
107
+ protected l2BlockSource: L2BlockSource,
108
+ protected l1ToL2MessageSource: L1ToL2MessageSource,
109
+ protected publicProcessorFactory: PublicProcessorFactory,
110
+ protected contractDataSource: ContractDataSource,
116
111
  protected l1Constants: SequencerRollupConstants,
117
- private dateProvider: DateProvider,
112
+ protected dateProvider: DateProvider,
118
113
  telemetry: TelemetryClient,
119
- private config: SequencerConfig = {},
120
- private log = createLogger('sequencer'),
114
+ protected config: SequencerConfig = {},
115
+ protected log = createLogger('sequencer'),
121
116
  ) {
122
117
  this.updateConfig(config);
123
118
  this.metrics = new SequencerMetrics(telemetry, () => this.state, 'Sequencer');
@@ -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(),
@@ -859,42 +772,18 @@ export class Sequencer {
859
772
  // Publishes new block to the network and awaits the tx to be mined
860
773
  this.setState(SequencerState.PUBLISHING_BLOCK, block.header.globalVariables.slotNumber.toBigInt());
861
774
 
862
- const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes, proofQuote);
775
+ // Time out tx at the end of the slot
776
+ const slot = block.header.globalVariables.slotNumber.toNumber();
777
+ const txTimeoutAt = new Date((this.getSlotStartTimestamp(slot) + this.aztecSlotDuration) * 1000);
778
+
779
+ const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes, proofQuote, {
780
+ txTimeoutAt,
781
+ });
863
782
  if (!publishedL2Block) {
864
783
  throw new Error(`Failed to publish block ${block.number}`);
865
784
  }
866
785
  }
867
786
 
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
787
  @trackSpan(
899
788
  'Sequencer.claimEpochProofRightIfAvailable',
900
789
  slotNumber => ({ [Attributes.SLOT_NUMBER]: Number(slotNumber) }),
@@ -19,9 +19,9 @@ export enum SequencerState {
19
19
  */
20
20
  PROPOSER_CHECK = 'PROPOSER_CHECK',
21
21
  /**
22
- * Polling the P2P module for txs to include in a block. Will move to CREATING_BLOCK if there are valid txs to include, or back to SYNCHRONIZING otherwise.
22
+ * Initializing the block proposal. Will move to CREATING_BLOCK if there are valid txs to include, or back to SYNCHRONIZING otherwise.
23
23
  */
24
- WAITING_FOR_TXS = 'WAITING_FOR_TXS',
24
+ INITIALIZING_PROPOSAL = 'INITIALIZING_PROPOSAL',
25
25
  /**
26
26
  * Creating a new L2 block. Includes processing public function calls and running rollup circuits. Will move to PUBLISHING_CONTRACT_DATA.
27
27
  */
@@ -0,0 +1,23 @@
1
+ import { type PublicProcessorFactory } from '@aztec/simulator';
2
+
3
+ import { SequencerClient } from '../client/sequencer-client.js';
4
+ import { type L1Publisher } from '../publisher/l1-publisher.js';
5
+ import { Sequencer } from '../sequencer/sequencer.js';
6
+ import { type SequencerState } from '../sequencer/utils.js';
7
+
8
+ class TestSequencer_ extends Sequencer {
9
+ public override publicProcessorFactory!: PublicProcessorFactory;
10
+ public override timeTable!: Record<SequencerState, number>;
11
+ public override processTxTime!: number;
12
+ public override publisher!: L1Publisher;
13
+ }
14
+
15
+ export type TestSequencer = TestSequencer_;
16
+
17
+ class TestSequencerClient_ extends SequencerClient {
18
+ public override sequencer!: TestSequencer;
19
+ }
20
+
21
+ export type TestSequencerClient = TestSequencerClient_;
22
+
23
+ export * from './test-l1-publisher.js';
@@ -3,7 +3,7 @@ import { type Delayer, withDelayer } from '@aztec/ethereum/test';
3
3
 
4
4
  import { type Chain, type HttpTransport, type PrivateKeyAccount, type WalletClient } from 'viem';
5
5
 
6
- import { L1Publisher } from './l1-publisher.js';
6
+ import { L1Publisher } from '../publisher/l1-publisher.js';
7
7
 
8
8
  export class TestL1Publisher extends L1Publisher {
9
9
  public delayer: Delayer | undefined;
@@ -0,0 +1,27 @@
1
+ import { MerkleTreeId, type MerkleTreeReadOperations } from '@aztec/circuit-types';
2
+ import { type Fr } from '@aztec/circuits.js';
3
+ import { type ArchiveSource } from '@aztec/p2p';
4
+
5
+ /**
6
+ * Implements an archive source by checking a DB and an in-memory collection.
7
+ * Intended for validating transactions as they are added to a block.
8
+ */
9
+ export class ArchiveCache implements ArchiveSource {
10
+ archives: Map<string, bigint>;
11
+
12
+ constructor(private db: MerkleTreeReadOperations) {
13
+ this.archives = new Map<string, bigint>();
14
+ }
15
+
16
+ public async getArchiveIndices(archives: Fr[]): Promise<(bigint | undefined)[]> {
17
+ const toCheckDb = archives.filter(n => !this.archives.has(n.toString()));
18
+ const dbHits = await this.db.findLeafIndices(MerkleTreeId.ARCHIVE, toCheckDb);
19
+ dbHits.forEach((x, index) => {
20
+ if (x !== undefined) {
21
+ this.archives.set(toCheckDb[index].toString(), x);
22
+ }
23
+ });
24
+
25
+ return archives.map(n => this.archives.get(n.toString()));
26
+ }
27
+ }
@@ -1,4 +1,4 @@
1
- import { type Tx, TxExecutionPhase, type TxValidator } from '@aztec/circuit-types';
1
+ import { type Tx, TxExecutionPhase, type TxValidationResult, type TxValidator } from '@aztec/circuit-types';
2
2
  import { type AztecAddress, type Fr, FunctionSelector, type GasFees } from '@aztec/circuits.js';
3
3
  import { createLogger } from '@aztec/foundation/log';
4
4
  import { computeFeePayerBalanceStorageSlot, getExecutionRequestsByPhase } from '@aztec/simulator';
@@ -27,25 +27,10 @@ export class GasTxValidator implements TxValidator<Tx> {
27
27
  this.#gasFees = gasFees;
28
28
  }
29
29
 
30
- async validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[], skippedTxs: Tx[]]> {
31
- const validTxs: Tx[] = [];
32
- const invalidTxs: Tx[] = [];
33
- const skippedTxs: Tx[] = [];
34
-
35
- for (const tx of txs) {
36
- if (this.#shouldSkip(tx)) {
37
- skippedTxs.push(tx);
38
- } else if (await this.#validateTxFee(tx)) {
39
- validTxs.push(tx);
40
- } else {
41
- invalidTxs.push(tx);
42
- }
30
+ validateTx(tx: Tx): Promise<TxValidationResult> {
31
+ if (this.#shouldSkip(tx)) {
32
+ return Promise.resolve({ result: 'skipped', reason: ['Insufficient fee per gas'] });
43
33
  }
44
-
45
- return [validTxs, invalidTxs, skippedTxs];
46
- }
47
-
48
- validateTx(tx: Tx): Promise<boolean> {
49
34
  return this.#validateTxFee(tx);
50
35
  }
51
36
 
@@ -57,20 +42,22 @@ export class GasTxValidator implements TxValidator<Tx> {
57
42
  const notEnoughMaxFees =
58
43
  maxFeesPerGas.feePerDaGas.lt(this.#gasFees.feePerDaGas) ||
59
44
  maxFeesPerGas.feePerL2Gas.lt(this.#gasFees.feePerL2Gas);
45
+
60
46
  if (notEnoughMaxFees) {
61
47
  this.#log.warn(`Skipping transaction ${tx.getTxHash()} due to insufficient fee per gas`);
62
48
  }
63
49
  return notEnoughMaxFees;
64
50
  }
65
51
 
66
- async #validateTxFee(tx: Tx): Promise<boolean> {
52
+ async #validateTxFee(tx: Tx): Promise<TxValidationResult> {
67
53
  const feePayer = tx.data.feePayer;
68
54
  // TODO(@spalladino) Eventually remove the is_zero condition as we should always charge fees to every tx
69
55
  if (feePayer.isZero()) {
70
56
  if (this.#enforceFees) {
71
57
  this.#log.warn(`Rejecting transaction ${tx.getTxHash()} due to missing fee payer`);
58
+ return { result: 'invalid', reason: ['Missing fee payer'] };
72
59
  } else {
73
- return true;
60
+ return { result: 'valid' };
74
61
  }
75
62
  }
76
63
 
@@ -98,13 +85,13 @@ export class GasTxValidator implements TxValidator<Tx> {
98
85
 
99
86
  const balance = claimFunctionCall ? initialBalance.add(claimFunctionCall.args[2]) : initialBalance;
100
87
  if (balance.lt(feeLimit)) {
101
- this.#log.info(`Rejecting transaction due to not enough fee payer balance`, {
88
+ this.#log.warn(`Rejecting transaction due to not enough fee payer balance`, {
102
89
  feePayer,
103
90
  balance: balance.toBigInt(),
104
91
  feeLimit: feeLimit.toBigInt(),
105
92
  });
106
- return false;
93
+ return { result: 'invalid', reason: ['Insufficient fee payer balance'] };
107
94
  }
108
- return true;
95
+ return { result: 'valid' };
109
96
  }
110
97
  }
@@ -0,0 +1,29 @@
1
+ import { MerkleTreeId, type MerkleTreeReadOperations } from '@aztec/circuit-types';
2
+ import { type NullifierSource } from '@aztec/p2p';
3
+
4
+ /**
5
+ * Implements a nullifier source by checking a DB and an in-memory collection.
6
+ * Intended for validating transactions as they are added to a block.
7
+ */
8
+ export class NullifierCache implements NullifierSource {
9
+ nullifiers: Set<string>;
10
+
11
+ constructor(private db: MerkleTreeReadOperations) {
12
+ this.nullifiers = new Set();
13
+ }
14
+
15
+ public async nullifiersExist(nullifiers: Buffer[]): Promise<boolean[]> {
16
+ const cacheResults = nullifiers.map(n => this.nullifiers.has(n.toString()));
17
+ const toCheckDb = nullifiers.filter((_n, index) => !cacheResults[index]);
18
+ const dbHits = await this.db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, toCheckDb);
19
+
20
+ let dbIndex = 0;
21
+ return nullifiers.map((_n, index) => cacheResults[index] || dbHits[dbIndex++] !== undefined);
22
+ }
23
+
24
+ public addNullifiers(nullifiers: Buffer[]) {
25
+ for (const nullifier of nullifiers) {
26
+ this.nullifiers.add(nullifier.toString());
27
+ }
28
+ }
29
+ }