@aztec/sequencer-client 0.57.0 → 0.58.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/block_builder/index.d.ts +2 -2
  2. package/dest/block_builder/index.d.ts.map +1 -1
  3. package/dest/block_builder/light.d.ts +3 -3
  4. package/dest/block_builder/light.d.ts.map +1 -1
  5. package/dest/block_builder/light.js +6 -5
  6. package/dest/block_builder/orchestrator.d.ts +3 -3
  7. package/dest/block_builder/orchestrator.d.ts.map +1 -1
  8. package/dest/block_builder/orchestrator.js +1 -1
  9. package/dest/client/sequencer-client.d.ts +2 -2
  10. package/dest/client/sequencer-client.d.ts.map +1 -1
  11. package/dest/client/sequencer-client.js +3 -4
  12. package/dest/config.d.ts.map +1 -1
  13. package/dest/config.js +4 -5
  14. package/dest/publisher/l1-publisher-metrics.d.ts.map +1 -1
  15. package/dest/publisher/l1-publisher-metrics.js +2 -1
  16. package/dest/publisher/l1-publisher.d.ts +9 -4
  17. package/dest/publisher/l1-publisher.d.ts.map +1 -1
  18. package/dest/publisher/l1-publisher.js +71 -25
  19. package/dest/sequencer/sequencer.d.ts +3 -4
  20. package/dest/sequencer/sequencer.d.ts.map +1 -1
  21. package/dest/sequencer/sequencer.js +98 -80
  22. package/dest/tx_validator/phases_validator.d.ts +1 -1
  23. package/dest/tx_validator/phases_validator.d.ts.map +1 -1
  24. package/dest/tx_validator/phases_validator.js +1 -1
  25. package/dest/tx_validator/tx_validator_factory.d.ts +9 -7
  26. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  27. package/dest/tx_validator/tx_validator_factory.js +20 -10
  28. package/package.json +19 -19
  29. package/src/block_builder/index.ts +2 -2
  30. package/src/block_builder/light.ts +7 -6
  31. package/src/block_builder/orchestrator.ts +8 -3
  32. package/src/client/sequencer-client.ts +3 -9
  33. package/src/config.ts +3 -4
  34. package/src/publisher/l1-publisher-metrics.ts +1 -0
  35. package/src/publisher/l1-publisher.ts +90 -26
  36. package/src/sequencer/sequencer.ts +117 -100
  37. package/src/tx_validator/phases_validator.ts +1 -1
  38. package/src/tx_validator/tx_validator_factory.ts +39 -15
@@ -8,10 +8,13 @@ import {
8
8
  Tx,
9
9
  type TxHash,
10
10
  type TxValidator,
11
- type WorldStateStatus,
12
11
  type WorldStateSynchronizer,
13
12
  } from '@aztec/circuit-types';
14
- import { type AllowedElement, BlockProofError } from '@aztec/circuit-types/interfaces';
13
+ import {
14
+ type AllowedElement,
15
+ BlockProofError,
16
+ type WorldStateSynchronizerStatus,
17
+ } from '@aztec/circuit-types/interfaces';
15
18
  import { type L2BlockBuiltStats } from '@aztec/circuit-types/stats';
16
19
  import {
17
20
  AppendOnlyTreeSnapshot,
@@ -67,7 +70,6 @@ export class Sequencer {
67
70
  // TODO: zero values should not be allowed for the following 2 values in PROD
68
71
  private _coinbase = EthAddress.ZERO;
69
72
  private _feeRecipient = AztecAddress.ZERO;
70
- private lastPublishedBlock = 0;
71
73
  private state = SequencerState.STOPPED;
72
74
  private allowedInSetup: AllowedElement[] = [];
73
75
  private allowedInTeardown: AllowedElement[] = [];
@@ -142,13 +144,12 @@ export class Sequencer {
142
144
  /**
143
145
  * Starts the sequencer and moves to IDLE state. Blocks until the initial sync is complete.
144
146
  */
145
- public async start() {
146
- await this.initialSync();
147
-
147
+ public start() {
148
148
  this.runningPromise = new RunningPromise(this.work.bind(this), this.pollingIntervalMs);
149
149
  this.runningPromise.start();
150
150
  this.state = SequencerState.IDLE;
151
151
  this.log.info('Sequencer started');
152
+ return Promise.resolve();
152
153
  }
153
154
 
154
155
  /**
@@ -180,11 +181,6 @@ export class Sequencer {
180
181
  return { state: this.state };
181
182
  }
182
183
 
183
- protected async initialSync() {
184
- // TODO: Should we wait for world state to be ready, or is the caller expected to run await start?
185
- this.lastPublishedBlock = await this.worldState.status().then((s: WorldStateStatus) => s.syncedToL2Block);
186
- }
187
-
188
184
  /**
189
185
  * @notice Performs most of the sequencer duties:
190
186
  * - Checks if we are up to date
@@ -279,15 +275,14 @@ export class Sequencer {
279
275
  // @note It is very important that the following function will FAIL and not just return early
280
276
  // if it have made any state changes. If not, we won't rollback the state, and you will
281
277
  // be in for a world of pain.
282
- await this.buildBlockAndPublish(validTxs, proposalHeader, historicalHeader);
278
+ await this.buildBlockAndAttemptToPublish(validTxs, proposalHeader, historicalHeader);
283
279
  } catch (err) {
284
280
  if (BlockProofError.isBlockProofError(err)) {
285
281
  const txHashes = err.txHashes.filter(h => !h.isZero());
286
282
  this.log.warn(`Proving block failed, removing ${txHashes.length} txs from pool`);
287
283
  await this.p2pClient.deleteTxs(txHashes);
288
284
  }
289
- this.log.error(`Rolling back world state DB due to error assembling block`, (err as any).stack);
290
- await this.worldState.getLatest().rollback();
285
+ this.log.error(`Error assembling block`, (err as any).stack);
291
286
  }
292
287
  }
293
288
 
@@ -314,6 +309,7 @@ export class Sequencer {
314
309
  this.log.debug(`Can propose block ${proposalBlockNumber} at slot ${slot}`);
315
310
  return slot;
316
311
  } catch (err) {
312
+ this.log.verbose(`Rejected from being able to propose at next block with ${tipArchive}`);
317
313
  prettyLogViemError(err, this.log);
318
314
  throw err;
319
315
  }
@@ -395,10 +391,10 @@ export class Sequencer {
395
391
  * @param proposalHeader - The partial header constructed for the proposal
396
392
  * @param historicalHeader - The historical header of the parent
397
393
  */
398
- @trackSpan('Sequencer.buildBlockAndPublish', (_validTxs, proposalHeader, _historicalHeader) => ({
394
+ @trackSpan('Sequencer.buildBlockAndAttemptToPublish', (_validTxs, proposalHeader, _historicalHeader) => ({
399
395
  [Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
400
396
  }))
401
- private async buildBlockAndPublish(
397
+ private async buildBlockAndAttemptToPublish(
402
398
  validTxs: Tx[],
403
399
  proposalHeader: Header,
404
400
  historicalHeader: Header | undefined,
@@ -419,85 +415,90 @@ export class Sequencer {
419
415
  `Retrieved ${l1ToL2Messages.length} L1 to L2 messages for block ${newGlobalVariables.blockNumber.toNumber()}`,
420
416
  );
421
417
 
422
- // We create a fresh processor each time to reset any cached state (eg storage writes)
423
- const processor = this.publicProcessorFactory.create(historicalHeader, newGlobalVariables);
424
-
425
418
  const numRealTxs = validTxs.length;
426
419
  const blockSize = Math.max(2, numRealTxs);
427
420
 
428
- const blockBuildingTimer = new Timer();
429
- const blockBuilder = this.blockBuilderFactory.create(this.worldState.getLatest());
430
- await blockBuilder.startNewBlock(blockSize, newGlobalVariables, l1ToL2Messages);
431
-
432
- const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
433
- processor.process(validTxs, blockSize, blockBuilder, this.txValidatorFactory.validatorForProcessedTxs()),
434
- );
435
- if (failedTxs.length > 0) {
436
- const failedTxData = failedTxs.map(fail => fail.tx);
437
- this.log.debug(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
438
- await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
439
- }
440
-
441
- await this.publisher.validateBlockForSubmission(proposalHeader);
442
-
443
- if (
444
- !this.shouldProposeBlock(historicalHeader, {
445
- validTxsCount: validTxs.length,
446
- processedTxsCount: processedTxs.length,
447
- })
448
- ) {
449
- // TODO: Roll back changes to world state
450
- throw new Error('Should not propose the block');
451
- }
452
-
453
- // All real transactions have been added, set the block as full and complete the proving.
454
- const block = await blockBuilder.setBlockCompleted();
421
+ const fork = await this.worldState.fork();
422
+ try {
423
+ // We create a fresh processor each time to reset any cached state (eg storage writes)
424
+ const processor = this.publicProcessorFactory.create(fork, historicalHeader, newGlobalVariables);
425
+ const blockBuildingTimer = new Timer();
426
+ const blockBuilder = this.blockBuilderFactory.create(fork);
427
+ await blockBuilder.startNewBlock(blockSize, newGlobalVariables, l1ToL2Messages);
428
+
429
+ const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
430
+ processor.process(validTxs, blockSize, blockBuilder, this.txValidatorFactory.validatorForProcessedTxs(fork)),
431
+ );
432
+ if (failedTxs.length > 0) {
433
+ const failedTxData = failedTxs.map(fail => fail.tx);
434
+ this.log.debug(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
435
+ await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
436
+ }
455
437
 
456
- // TODO(@PhilWindle) We should probably periodically check for things like another
457
- // block being published before ours instead of just waiting on our block
438
+ await this.publisher.validateBlockForSubmission(proposalHeader);
458
439
 
459
- await this.publisher.validateBlockForSubmission(block.header);
440
+ if (
441
+ !this.shouldProposeBlock(historicalHeader, {
442
+ validTxsCount: validTxs.length,
443
+ processedTxsCount: processedTxs.length,
444
+ })
445
+ ) {
446
+ // TODO: Roll back changes to world state
447
+ throw new Error('Should not propose the block');
448
+ }
460
449
 
461
- const workDuration = workTimer.ms();
462
- this.log.verbose(
463
- `Assembled block ${block.number} (txEffectsHash: ${block.header.contentCommitment.txsEffectsHash.toString(
464
- 'hex',
465
- )})`,
466
- {
467
- eventName: 'l2-block-built',
468
- duration: workDuration,
469
- publicProcessDuration: publicProcessorDuration,
470
- rollupCircuitsDuration: blockBuildingTimer.ms(),
471
- ...block.getStats(),
472
- } satisfies L2BlockBuiltStats,
473
- );
450
+ // All real transactions have been added, set the block as full and complete the proving.
451
+ const block = await blockBuilder.setBlockCompleted();
452
+
453
+ // TODO(@PhilWindle) We should probably periodically check for things like another
454
+ // block being published before ours instead of just waiting on our block
455
+
456
+ await this.publisher.validateBlockForSubmission(block.header);
457
+
458
+ const workDuration = workTimer.ms();
459
+ this.log.verbose(
460
+ `Assembled block ${block.number} (txEffectsHash: ${block.header.contentCommitment.txsEffectsHash.toString(
461
+ 'hex',
462
+ )})`,
463
+ {
464
+ eventName: 'l2-block-built',
465
+ creator: this.publisher.getSenderAddress().toString(),
466
+ duration: workDuration,
467
+ publicProcessDuration: publicProcessorDuration,
468
+ rollupCircuitsDuration: blockBuildingTimer.ms(),
469
+ ...block.getStats(),
470
+ } satisfies L2BlockBuiltStats,
471
+ );
474
472
 
475
- if (this.isFlushing) {
476
- this.log.verbose(`Flushing completed`);
477
- }
473
+ if (this.isFlushing) {
474
+ this.log.verbose(`Flushing completed`);
475
+ }
478
476
 
479
- const txHashes = validTxs.map(tx => tx.getTxHash());
477
+ const txHashes = validTxs.map(tx => tx.getTxHash());
480
478
 
481
- this.isFlushing = false;
482
- this.log.verbose('Collecting attestations');
483
- const attestations = await this.collectAttestations(block, txHashes);
484
- this.log.verbose('Attestations collected');
479
+ this.isFlushing = false;
480
+ this.log.verbose('Collecting attestations');
481
+ const attestations = await this.collectAttestations(block, txHashes);
482
+ this.log.verbose('Attestations collected');
485
483
 
486
- this.log.verbose('Collecting proof quotes');
487
- const proofQuote = await this.createProofClaimForPreviousEpoch(newGlobalVariables.slotNumber.toBigInt());
488
- this.log.verbose(proofQuote ? `Using proof quote ${inspect(proofQuote.payload)}` : 'No proof quote available');
484
+ this.log.verbose('Collecting proof quotes');
485
+ const proofQuote = await this.createProofClaimForPreviousEpoch(newGlobalVariables.slotNumber.toBigInt());
486
+ this.log.verbose(proofQuote ? `Using proof quote ${inspect(proofQuote.payload)}` : 'No proof quote available');
489
487
 
490
- try {
491
- await this.publishL2Block(block, attestations, txHashes, proofQuote);
492
- this.metrics.recordPublishedBlock(workDuration);
493
- this.log.info(
494
- `Submitted rollup block ${block.number} with ${processedTxs.length} transactions duration=${Math.ceil(
495
- workDuration,
496
- )}ms (Submitter: ${this.publisher.getSenderAddress()})`,
497
- );
498
- } catch (err) {
499
- this.metrics.recordFailedBlock();
500
- throw err;
488
+ try {
489
+ await this.publishL2Block(block, attestations, txHashes, proofQuote);
490
+ this.metrics.recordPublishedBlock(workDuration);
491
+ this.log.info(
492
+ `Submitted rollup block ${block.number} with ${processedTxs.length} transactions duration=${Math.ceil(
493
+ workDuration,
494
+ )}ms (Submitter: ${this.publisher.getSenderAddress()})`,
495
+ );
496
+ } catch (err) {
497
+ this.metrics.recordFailedBlock();
498
+ throw err;
499
+ }
500
+ } finally {
501
+ await fork.close();
501
502
  }
502
503
  }
503
504
 
@@ -506,6 +507,11 @@ export class Sequencer {
506
507
  this.isFlushing = true;
507
508
  }
508
509
 
510
+ @trackSpan('Sequencer.collectAttestations', (block, txHashes) => ({
511
+ [Attributes.BLOCK_NUMBER]: block.number,
512
+ [Attributes.BLOCK_ARCHIVE]: block.archive.toString(),
513
+ [Attributes.BLOCK_TXS_COUNT]: txHashes.length,
514
+ }))
509
515
  protected async collectAttestations(block: L2Block, txHashes: TxHash[]): Promise<Signature[] | undefined> {
510
516
  // TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962): inefficient to have a round trip in here - this should be cached
511
517
  const committee = await this.publisher.getCurrentEpochCommittee();
@@ -600,9 +606,7 @@ export class Sequencer {
600
606
  this.state = SequencerState.PUBLISHING_BLOCK;
601
607
 
602
608
  const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes, proofQuote);
603
- if (publishedL2Block) {
604
- this.lastPublishedBlock = block.number;
605
- } else {
609
+ if (!publishedL2Block) {
606
610
  throw new Error(`Failed to publish block ${block.number}`);
607
611
  }
608
612
  }
@@ -638,24 +642,37 @@ export class Sequencer {
638
642
  }
639
643
 
640
644
  /**
641
- * Returns whether the previous block sent has been mined, and all dependencies have caught up with it.
645
+ * Returns whether all dependencies have caught up.
646
+ * We don't check against the previous block submitted since it may have been reorg'd out.
642
647
  * @returns Boolean indicating if our dependencies are synced to the latest block.
643
648
  */
644
649
  protected async isBlockSynced() {
645
650
  const syncedBlocks = await Promise.all([
646
- this.worldState.status().then((s: WorldStateStatus) => s.syncedToL2Block),
647
- this.p2pClient.getStatus().then(s => s.syncedToL2Block),
648
- this.l2BlockSource.getBlockNumber(),
651
+ this.worldState.status().then((s: WorldStateSynchronizerStatus) => s.syncedToL2Block),
652
+ this.l2BlockSource.getL2Tips().then(t => t.latest),
653
+ this.p2pClient.getStatus().then(s => s.syncedToL2Block.number),
649
654
  this.l1ToL2MessageSource.getBlockNumber(),
650
- ]);
651
- const min = Math.min(...syncedBlocks);
652
- const [worldState, p2p, l2BlockSource, l1ToL2MessageSource] = syncedBlocks;
653
- const result = min >= this.lastPublishedBlock;
654
- this.log.debug(`Sync check to last published block ${this.lastPublishedBlock} ${result ? 'succeeded' : 'failed'}`, {
655
- worldState,
656
- p2p,
657
- l2BlockSource,
658
- l1ToL2MessageSource,
655
+ ] as const);
656
+ const [worldState, l2BlockSource, p2p, l1ToL2MessageSource] = syncedBlocks;
657
+ const result =
658
+ // check that world state has caught up with archiver
659
+ // note that the archiver reports undefined hash for the genesis block
660
+ // because it doesn't have access to world state to compute it (facepalm)
661
+ (l2BlockSource.hash === undefined || worldState.hash === l2BlockSource.hash) &&
662
+ // and p2p client and message source are at least at the same block
663
+ // this should change to hashes once p2p client handles reorgs
664
+ // and once we stop pretending that the l1tol2message source is not
665
+ // just the archiver under a different name
666
+ p2p >= l2BlockSource.number &&
667
+ l1ToL2MessageSource >= l2BlockSource.number;
668
+
669
+ this.log.verbose(`Sequencer sync check ${result ? 'succeeded' : 'failed'}`, {
670
+ worldStateNumber: worldState.number,
671
+ worldStateHash: worldState.hash,
672
+ l2BlockSourceNumber: l2BlockSource.number,
673
+ l2BlockSourceHash: l2BlockSource.hash,
674
+ p2pNumber: p2p,
675
+ l1ToL2MessageSourceNumber: l1ToL2MessageSource,
659
676
  });
660
677
  return result;
661
678
  }
@@ -5,9 +5,9 @@ import {
5
5
  Tx,
6
6
  type TxValidator,
7
7
  } from '@aztec/circuit-types';
8
+ import { type ContractDataSource } from '@aztec/circuits.js';
8
9
  import { createDebugLogger } from '@aztec/foundation/log';
9
10
  import { ContractsDataSourcePublicDB, EnqueuedCallsProcessor } from '@aztec/simulator';
10
- import { type ContractDataSource } from '@aztec/types/contracts';
11
11
 
12
12
  export class PhasesTxValidator implements TxValidator<Tx> {
13
13
  #log = createDebugLogger('aztec:sequencer:tx_validator:tx_phases');
@@ -1,33 +1,57 @@
1
- import { type AllowedElement, type ProcessedTx, type Tx, type TxValidator } from '@aztec/circuit-types';
2
- import { type GlobalVariables } from '@aztec/circuits.js';
3
- import { AggregateTxValidator, DataTxValidator, DoubleSpendTxValidator, MetadataTxValidator } from '@aztec/p2p';
4
- import { FeeJuiceAddress } from '@aztec/protocol-contracts/fee-juice';
5
- import { WorldStateDB } from '@aztec/simulator';
6
- import { type ContractDataSource } from '@aztec/types/contracts';
7
- import { type MerkleTreeOperations } from '@aztec/world-state';
1
+ import {
2
+ type AllowedElement,
3
+ MerkleTreeId,
4
+ type MerkleTreeReadOperations,
5
+ type ProcessedTx,
6
+ type Tx,
7
+ type TxValidator,
8
+ } from '@aztec/circuit-types';
9
+ import { type ContractDataSource, type GlobalVariables } from '@aztec/circuits.js';
10
+ import {
11
+ AggregateTxValidator,
12
+ DataTxValidator,
13
+ DoubleSpendTxValidator,
14
+ MetadataTxValidator,
15
+ type NullifierSource,
16
+ } from '@aztec/p2p';
17
+ import { ProtocolContractAddress } from '@aztec/protocol-contracts';
18
+ import { readPublicState } from '@aztec/simulator';
8
19
 
9
- import { GasTxValidator } from './gas_validator.js';
20
+ import { GasTxValidator, type PublicStateSource } from './gas_validator.js';
10
21
  import { PhasesTxValidator } from './phases_validator.js';
11
22
 
12
23
  export class TxValidatorFactory {
24
+ nullifierSource: NullifierSource;
25
+ publicStateSource: PublicStateSource;
13
26
  constructor(
14
- private merkleTreeDb: MerkleTreeOperations,
27
+ private committedDb: MerkleTreeReadOperations,
15
28
  private contractDataSource: ContractDataSource,
16
29
  private enforceFees: boolean,
17
- ) {}
30
+ ) {
31
+ this.nullifierSource = {
32
+ getNullifierIndex: nullifier => this.committedDb.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()),
33
+ };
34
+
35
+ this.publicStateSource = {
36
+ storageRead: (contractAddress, slot) => {
37
+ return readPublicState(this.committedDb, contractAddress, slot);
38
+ },
39
+ };
40
+ }
18
41
 
19
42
  validatorForNewTxs(globalVariables: GlobalVariables, setupAllowList: AllowedElement[]): TxValidator<Tx> {
20
- const worldStateDB = new WorldStateDB(this.merkleTreeDb, this.contractDataSource);
21
43
  return new AggregateTxValidator(
22
44
  new DataTxValidator(),
23
45
  new MetadataTxValidator(globalVariables.chainId, globalVariables.blockNumber),
24
- new DoubleSpendTxValidator(worldStateDB),
46
+ new DoubleSpendTxValidator(this.nullifierSource),
25
47
  new PhasesTxValidator(this.contractDataSource, setupAllowList),
26
- new GasTxValidator(worldStateDB, FeeJuiceAddress, this.enforceFees),
48
+ new GasTxValidator(this.publicStateSource, ProtocolContractAddress.FeeJuice, this.enforceFees),
27
49
  );
28
50
  }
29
51
 
30
- validatorForProcessedTxs(): TxValidator<ProcessedTx> {
31
- return new DoubleSpendTxValidator(new WorldStateDB(this.merkleTreeDb, this.contractDataSource));
52
+ validatorForProcessedTxs(fork: MerkleTreeReadOperations): TxValidator<ProcessedTx> {
53
+ return new DoubleSpendTxValidator({
54
+ getNullifierIndex: nullifier => fork.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()),
55
+ });
32
56
  }
33
57
  }