@aztec/sequencer-client 0.87.6 → 1.0.0-nightly.20250604

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 (50) hide show
  1. package/dest/client/sequencer-client.d.ts +5 -5
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +8 -8
  4. package/dest/global_variable_builder/global_builder.d.ts +4 -1
  5. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  6. package/dest/global_variable_builder/global_builder.js +14 -2
  7. package/dest/index.d.ts +1 -2
  8. package/dest/index.d.ts.map +1 -1
  9. package/dest/index.js +1 -2
  10. package/dest/publisher/config.js +1 -4
  11. package/dest/publisher/sequencer-publisher.d.ts +4 -4
  12. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  13. package/dest/publisher/sequencer-publisher.js +23 -14
  14. package/dest/sequencer/block_builder.d.ts +33 -0
  15. package/dest/sequencer/block_builder.d.ts.map +1 -0
  16. package/dest/sequencer/block_builder.js +109 -0
  17. package/dest/sequencer/index.d.ts +1 -0
  18. package/dest/sequencer/index.d.ts.map +1 -1
  19. package/dest/sequencer/index.js +1 -0
  20. package/dest/sequencer/sequencer.d.ts +17 -64
  21. package/dest/sequencer/sequencer.d.ts.map +1 -1
  22. package/dest/sequencer/sequencer.js +74 -173
  23. package/dest/sequencer/utils.d.ts +2 -2
  24. package/dest/sequencer/utils.d.ts.map +1 -1
  25. package/dest/sequencer/utils.js +6 -4
  26. package/dest/tx_validator/tx_validator_factory.d.ts +2 -6
  27. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  28. package/package.json +26 -25
  29. package/src/client/sequencer-client.ts +12 -20
  30. package/src/global_variable_builder/global_builder.ts +16 -2
  31. package/src/index.ts +1 -2
  32. package/src/publisher/config.ts +1 -1
  33. package/src/publisher/sequencer-publisher.ts +32 -21
  34. package/src/sequencer/block_builder.ts +192 -0
  35. package/src/sequencer/index.ts +1 -0
  36. package/src/sequencer/sequencer.ts +96 -221
  37. package/src/sequencer/utils.ts +14 -6
  38. package/src/tx_validator/tx_validator_factory.ts +2 -4
  39. package/dest/slasher/factory.d.ts +0 -7
  40. package/dest/slasher/factory.d.ts.map +0 -1
  41. package/dest/slasher/factory.js +0 -8
  42. package/dest/slasher/index.d.ts +0 -3
  43. package/dest/slasher/index.d.ts.map +0 -1
  44. package/dest/slasher/index.js +0 -2
  45. package/dest/slasher/slasher_client.d.ts +0 -75
  46. package/dest/slasher/slasher_client.d.ts.map +0 -1
  47. package/dest/slasher/slasher_client.js +0 -135
  48. package/src/slasher/factory.ts +0 -15
  49. package/src/slasher/index.ts +0 -2
  50. package/src/slasher/slasher_client.ts +0 -199
@@ -1,24 +1,23 @@
1
- import { type L2Block, retryUntil } from '@aztec/aztec.js';
1
+ import type { L2Block } from '@aztec/aztec.js';
2
2
  import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
3
- import type { ViemPublicClient } from '@aztec/ethereum';
3
+ import { FormattedViemError, type ViemPublicClient } from '@aztec/ethereum';
4
4
  import { omit } from '@aztec/foundation/collection';
5
5
  import { EthAddress } from '@aztec/foundation/eth-address';
6
- import type { Signature } from '@aztec/foundation/eth-signature';
7
6
  import { Fr } from '@aztec/foundation/fields';
8
7
  import { createLogger } from '@aztec/foundation/log';
9
8
  import { RunningPromise } from '@aztec/foundation/running-promise';
10
- import { type DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
9
+ import { type DateProvider, Timer } from '@aztec/foundation/timer';
11
10
  import type { P2P } from '@aztec/p2p';
12
11
  import { getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
13
- import type { BlockBuilderFactory } from '@aztec/prover-client/block-builder';
14
- import type { PublicProcessorFactory } from '@aztec/simulator/server';
12
+ import type { SlasherClient } from '@aztec/slasher';
15
13
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
16
- import type { L2BlockSource } from '@aztec/stdlib/block';
17
- import type { ContractDataSource } from '@aztec/stdlib/contract';
14
+ import type { CommitteeAttestation, L2BlockSource } from '@aztec/stdlib/block';
18
15
  import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
19
16
  import { Gas } from '@aztec/stdlib/gas';
20
17
  import {
21
18
  type AllowedElement,
19
+ type BuildBlockOptions,
20
+ type IFullNodeBlockBuilder,
22
21
  SequencerConfigSchema,
23
22
  type WorldStateSynchronizer,
24
23
  } from '@aztec/stdlib/interfaces/server';
@@ -27,7 +26,14 @@ import type { BlockProposalOptions } from '@aztec/stdlib/p2p';
27
26
  import { pickFromSchema } from '@aztec/stdlib/schemas';
28
27
  import type { L2BlockBuiltStats } from '@aztec/stdlib/stats';
29
28
  import { MerkleTreeId } from '@aztec/stdlib/trees';
30
- import { ContentCommitment, GlobalVariables, ProposedBlockHeader, Tx, type TxHash } from '@aztec/stdlib/tx';
29
+ import {
30
+ ContentCommitment,
31
+ type FailedTx,
32
+ GlobalVariables,
33
+ ProposedBlockHeader,
34
+ Tx,
35
+ type TxHash,
36
+ } from '@aztec/stdlib/tx';
31
37
  import {
32
38
  Attributes,
33
39
  L1Metrics,
@@ -40,8 +46,6 @@ import type { ValidatorClient } from '@aztec/validator-client';
40
46
 
41
47
  import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
42
48
  import { type SequencerPublisher, VoteType } from '../publisher/sequencer-publisher.js';
43
- import type { SlasherClient } from '../slasher/slasher_client.js';
44
- import { createValidatorForBlockBuilding } from '../tx_validator/tx_validator_factory.js';
45
49
  import type { SequencerConfig } from './config.js';
46
50
  import { SequencerMetrics } from './metrics.js';
47
51
  import { SequencerTimetable, SequencerTooSlowError } from './timetable.js';
@@ -89,15 +93,13 @@ export class Sequencer {
89
93
  protected p2pClient: P2P,
90
94
  protected worldState: WorldStateSynchronizer,
91
95
  protected slasherClient: SlasherClient,
92
- protected blockBuilderFactory: BlockBuilderFactory,
93
96
  protected l2BlockSource: L2BlockSource,
94
97
  protected l1ToL2MessageSource: L1ToL2MessageSource,
95
- protected publicProcessorFactory: PublicProcessorFactory,
96
- protected contractDataSource: ContractDataSource,
98
+ protected blockBuilder: IFullNodeBlockBuilder,
97
99
  protected l1Constants: SequencerRollupConstants,
98
100
  protected dateProvider: DateProvider,
99
101
  protected config: SequencerConfig = {},
100
- telemetry: TelemetryClient = getTelemetryClient(),
102
+ protected telemetry: TelemetryClient = getTelemetryClient(),
101
103
  protected log = createLogger('sequencer'),
102
104
  ) {
103
105
  this.metrics = new SequencerMetrics(
@@ -113,9 +115,6 @@ export class Sequencer {
113
115
  [publisher.getSenderAddress()],
114
116
  );
115
117
 
116
- // Register the block builder with the validator client for re-execution
117
- this.validatorClient?.registerBlockBuilder(this.buildBlockFromProposal.bind(this));
118
-
119
118
  // Register the slasher on the publisher to fetch slashing payloads
120
119
  this.publisher.registerSlashPayloadGetter(this.slasherClient.getSlashPayload.bind(this.slasherClient));
121
120
  }
@@ -124,8 +123,8 @@ export class Sequencer {
124
123
  return this.metrics.tracer;
125
124
  }
126
125
 
127
- public getValidatorAddress() {
128
- return this.validatorClient?.getValidatorAddress();
126
+ public getValidatorAddresses() {
127
+ return this.validatorClient?.getValidatorAddresses();
129
128
  }
130
129
 
131
130
  /**
@@ -217,7 +216,7 @@ export class Sequencer {
217
216
  this.metrics.stop();
218
217
  await this.validatorClient?.stop();
219
218
  await this.runningPromise?.stop();
220
- this.slasherClient.stop();
219
+ await this.slasherClient.stop();
221
220
  this.publisher.interrupt();
222
221
  this.setState(SequencerState.STOPPED, 0n, true /** force */);
223
222
  this.l1Metrics.stop();
@@ -271,10 +270,19 @@ export class Sequencer {
271
270
  // If we cannot find a tip archive, assume genesis.
272
271
  const chainTipArchive = chainTip.archive;
273
272
 
274
- const slot = await this.slotForProposal(chainTipArchive.toBuffer(), BigInt(newBlockNumber));
273
+ const { slot } = this.publisher.epochCache.getEpochAndSlotInNextSlot();
275
274
  this.metrics.observeSlotChange(slot, this.publisher.getSenderAddress().toString());
276
- if (!slot) {
277
- this.log.debug(`Cannot propose block ${newBlockNumber}`);
275
+
276
+ const proposerInNextSlot = await this.publisher.epochCache.getProposerAttesterAddressInNextSlot();
277
+ const validatorAddresses = this.validatorClient!.getValidatorAddresses();
278
+
279
+ // If get proposer in next slot is undefined, then there is no proposer set, and it is in free for all (sandbox) so we continue
280
+ // If we calculate a proposer in the next slot, and it is not us, then stop
281
+ if (proposerInNextSlot !== undefined && !validatorAddresses.some(addr => addr.equals(proposerInNextSlot))) {
282
+ this.log.debug(`Cannot propose block ${newBlockNumber}`, {
283
+ us: validatorAddresses,
284
+ proposer: proposerInNextSlot,
285
+ });
278
286
  return;
279
287
  }
280
288
 
@@ -316,14 +324,22 @@ export class Sequencer {
316
324
 
317
325
  let finishedFlushing = false;
318
326
  const pendingTxCount = await this.p2pClient.getPendingTxCount();
327
+ this.log.debug(`Pending tx count: ${pendingTxCount}`);
319
328
  if (pendingTxCount >= this.minTxsPerBlock || this.isFlushing) {
320
329
  // We don't fetch exactly maxTxsPerBlock txs here because we may not need all of them if we hit a limit before,
321
330
  // and also we may need to fetch more if we don't have enough valid txs.
322
331
  const pendingTxs = this.p2pClient.iteratePendingTxs();
323
332
 
324
- await this.buildBlockAndEnqueuePublish(pendingTxs, proposalHeader, newGlobalVariables).catch(err => {
325
- this.log.error(`Error building/enqueuing block`, err, { blockNumber: newBlockNumber, slot });
326
- });
333
+ await this.buildBlockAndEnqueuePublish(pendingTxs, proposalHeader, newGlobalVariables, proposerInNextSlot).catch(
334
+ err => {
335
+ if (err instanceof FormattedViemError) {
336
+ this.log.verbose(`Unable to build/enqueue block ${err.message}`);
337
+ return;
338
+ } else {
339
+ this.log.error(`Error building/enqueuing block`, err, { blockNumber: newBlockNumber, slot });
340
+ }
341
+ },
342
+ );
327
343
  finishedFlushing = true;
328
344
  } else {
329
345
  this.log.verbose(
@@ -374,29 +390,6 @@ export class Sequencer {
374
390
  return this.publisher.getForwarderAddress();
375
391
  }
376
392
 
377
- /**
378
- * Checks if we can propose at the next block and returns the slot number if we can.
379
- * @param tipArchive - The archive of the previous block.
380
- * @param proposalBlockNumber - The block number of the proposal.
381
- * @returns The slot number if we can propose at the next block, otherwise undefined.
382
- */
383
- async slotForProposal(tipArchive: Buffer, proposalBlockNumber: bigint): Promise<bigint | undefined> {
384
- const result = await this.publisher.canProposeAtNextEthBlock(tipArchive);
385
-
386
- if (!result) {
387
- return undefined;
388
- }
389
-
390
- const [slot, blockNumber] = result;
391
-
392
- if (proposalBlockNumber !== blockNumber) {
393
- const msg = `Sequencer block number mismatch. Expected ${proposalBlockNumber} but got ${blockNumber}.`;
394
- this.log.warn(msg);
395
- throw new Error(msg);
396
- }
397
- return slot;
398
- }
399
-
400
393
  /**
401
394
  * Sets the sequencer state and checks if we have enough time left in the slot to transition to the new state.
402
395
  * @param proposedState - The new state to transition to.
@@ -417,175 +410,32 @@ export class Sequencer {
417
410
  this.state = proposedState;
418
411
  }
419
412
 
420
- /**
421
- * Build a block
422
- *
423
- * @param pendingTxs - The pending transactions to construct the block from
424
- * @param newGlobalVariables - The global variables for the new block
425
- * @param opts - Whether to just validate the block as a validator, as opposed to building it as a proposal
426
- */
427
- protected async buildBlock(
428
- pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
429
- newGlobalVariables: GlobalVariables,
430
- opts: { validateOnly?: boolean } = {},
431
- ) {
432
- const blockNumber = newGlobalVariables.blockNumber.toNumber();
433
- const slot = newGlobalVariables.slotNumber.toBigInt();
434
- this.log.debug(`Requesting L1 to L2 messages from contract for block ${blockNumber}`);
435
- const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(blockNumber));
436
- const msgCount = l1ToL2Messages.length;
437
-
438
- this.log.verbose(`Building block ${blockNumber} for slot ${slot}`, {
439
- slot,
440
- blockNumber,
441
- msgCount,
442
- validator: opts.validateOnly,
443
- });
444
-
445
- // Sync to the previous block at least. If we cannot sync to that block because the archiver hasn't caught up,
446
- // we keep retrying until the reexecution deadline. Note that this could only happen when we are a validator,
447
- // for if we are the proposer, then world-state should already be caught up, as we check this earlier.
448
- await retryUntil(
449
- () => this.worldState.syncImmediate(blockNumber - 1, true).then(syncedTo => syncedTo >= blockNumber - 1),
450
- 'sync to previous block',
451
- this.timetable.getValidatorReexecTimeEnd(),
452
- 0.1,
453
- );
454
- this.log.debug(`Synced to previous block ${blockNumber - 1}`);
455
-
456
- // NB: separating the dbs because both should update the state
457
- const publicProcessorDBFork = await this.worldState.fork();
458
- const orchestratorDBFork = await this.worldState.fork();
459
-
460
- const previousBlockHeader =
461
- (await this.l2BlockSource.getBlock(blockNumber - 1))?.header ?? orchestratorDBFork.getInitialHeader();
462
-
463
- try {
464
- const processor = this.publicProcessorFactory.create(publicProcessorDBFork, newGlobalVariables, true);
465
- const blockBuildingTimer = new Timer();
466
- const blockBuilder = this.blockBuilderFactory.create(orchestratorDBFork);
467
- await blockBuilder.startNewBlock(newGlobalVariables, l1ToL2Messages, previousBlockHeader);
468
-
469
- // Deadline for processing depends on whether we're proposing a block
470
- const secondsIntoSlot = this.getSecondsIntoSlot(slot);
471
- const processingEndTimeWithinSlot = opts.validateOnly
472
- ? this.timetable.getValidatorReexecTimeEnd(secondsIntoSlot)
473
- : this.timetable.getBlockProposalExecTimeEnd(secondsIntoSlot);
474
-
475
- // Deadline is only set if enforceTimeTable is enabled.
476
- const deadline = this.enforceTimeTable
477
- ? new Date((this.getSlotStartTimestamp(slot) + processingEndTimeWithinSlot) * 1000)
478
- : undefined;
479
-
480
- this.log.verbose(`Processing pending txs`, {
481
- slot,
482
- slotStart: new Date(this.getSlotStartTimestamp(slot) * 1000),
483
- now: new Date(this.dateProvider.now()),
484
- deadline,
485
- });
486
-
487
- const validator = createValidatorForBlockBuilding(
488
- publicProcessorDBFork,
489
- this.contractDataSource,
490
- newGlobalVariables,
491
- this.txPublicSetupAllowList,
492
- );
493
-
494
- // TODO(#11000): Public processor should just handle processing, one tx at a time. It should be responsibility
495
- // of the sequencer to update world state and iterate over txs. We should refactor this along with unifying the
496
- // publicProcessorFork and orchestratorFork, to avoid doing tree insertions twice when building the block.
497
- const proposerLimits = {
498
- maxTransactions: this.maxTxsPerBlock,
499
- maxBlockSize: this.maxBlockSizeInBytes,
500
- maxBlockGas: this.maxBlockGas,
501
- };
502
- const limits = opts.validateOnly ? { deadline } : { deadline, ...proposerLimits };
503
- const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs]] = await elapsed(() =>
504
- processor.process(pendingTxs, limits, validator),
505
- );
506
-
507
- if (!opts.validateOnly && failedTxs.length > 0) {
508
- const failedTxData = failedTxs.map(fail => fail.tx);
509
- const failedTxHashes = await Tx.getHashes(failedTxData);
510
- this.log.verbose(`Dropping failed txs ${failedTxHashes.join(', ')}`);
511
- await this.p2pClient.deleteTxs(failedTxHashes);
512
- }
513
-
514
- if (
515
- !opts.validateOnly && // We check for minTxCount only if we are proposing a block, not if we are validating it
516
- !this.isFlushing && // And we skip the check when flushing, since we want all pending txs to go out, no matter if too few
517
- this.minTxsPerBlock !== undefined &&
518
- processedTxs.length < this.minTxsPerBlock
519
- ) {
520
- this.log.warn(
521
- `Block ${blockNumber} has too few txs to be proposed (got ${processedTxs.length} but required ${this.minTxsPerBlock})`,
522
- { slot, blockNumber, processedTxCount: processedTxs.length },
523
- );
524
- throw new Error(`Block has too few successful txs to be proposed`);
525
- }
526
-
527
- const start = process.hrtime.bigint();
528
- await blockBuilder.addTxs(processedTxs);
529
- const end = process.hrtime.bigint();
530
- const duration = Number(end - start) / 1_000;
531
- this.metrics.recordBlockBuilderTreeInsertions(duration);
532
-
533
- // All real transactions have been added, set the block as full and pad if needed
534
- const block = await blockBuilder.setBlockCompleted();
535
-
536
- // How much public gas was processed
537
- const publicGas = processedTxs.reduce((acc, tx) => acc.add(tx.gasUsed.publicGas), Gas.empty());
538
-
539
- return {
540
- block,
541
- publicGas,
542
- publicProcessorDuration,
543
- numMsgs: l1ToL2Messages.length,
544
- numTxs: processedTxs.length,
545
- numFailedTxs: failedTxs.length,
546
- blockBuildingTimer,
547
- usedTxs,
548
- };
549
- } finally {
550
- // We create a fresh processor each time to reset any cached state (eg storage writes)
551
- // We wait a bit to close the forks since the processor may still be working on a dangling tx
552
- // which was interrupted due to the processingDeadline being hit.
553
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
554
- setTimeout(async () => {
555
- try {
556
- await publicProcessorDBFork.close();
557
- await orchestratorDBFork.close();
558
- } catch (err) {
559
- // This can happen if the sequencer is stopped before we hit this timeout.
560
- this.log.warn(`Error closing forks for block processing`, err);
561
- }
562
- }, 5000);
413
+ private async dropFailedTxsFromP2P(failedTxs: FailedTx[]) {
414
+ if (failedTxs.length === 0) {
415
+ return;
563
416
  }
417
+ const failedTxData = failedTxs.map(fail => fail.tx);
418
+ const failedTxHashes = await Tx.getHashes(failedTxData);
419
+ this.log.verbose(`Dropping failed txs ${failedTxHashes.join(', ')}`);
420
+ await this.p2pClient.deleteTxs(failedTxHashes);
564
421
  }
565
422
 
566
- /**
567
- * Build a block from a proposal. Used by the validator to re-execute transactions.
568
- *
569
- * @param blockNumber - The block number of the proposal.
570
- * @param header - The header of the proposal.
571
- * @param pendingTxs - The pending transactions to construct the block from.
572
- * @param opts - Whether to just validate the block as a validator, as opposed to building it as a proposal.
573
- */
574
- async buildBlockFromProposal(
575
- blockNumber: Fr,
576
- header: ProposedBlockHeader,
577
- pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
578
- opts: { validateOnly?: boolean } = {},
579
- ) {
580
- const { chainId, version } = await this.globalsBuilder.getGlobalConstantVariables();
581
- const globalVariables = GlobalVariables.from({
582
- ...header,
583
- blockNumber,
584
- timestamp: new Fr(header.timestamp),
585
- chainId,
586
- version,
587
- });
588
- return await this.buildBlock(pendingTxs, globalVariables, opts);
423
+ protected getDefaultBlockBuilderOptions(slot: number): BuildBlockOptions {
424
+ // Deadline for processing depends on whether we're proposing a block
425
+ const secondsIntoSlot = this.getSecondsIntoSlot(slot);
426
+ const processingEndTimeWithinSlot = this.timetable.getBlockProposalExecTimeEnd(secondsIntoSlot);
427
+
428
+ // Deadline is only set if enforceTimeTable is enabled.
429
+ const deadline = this.enforceTimeTable
430
+ ? new Date((this.getSlotStartTimestamp(slot) + processingEndTimeWithinSlot) * 1000)
431
+ : undefined;
432
+ return {
433
+ maxTransactions: this.maxTxsPerBlock,
434
+ maxBlockSize: this.maxBlockSizeInBytes,
435
+ maxBlockGas: this.maxBlockGas,
436
+ txPublicSetupAllowList: this.txPublicSetupAllowList,
437
+ deadline,
438
+ };
589
439
  }
590
440
 
591
441
  /**
@@ -596,6 +446,8 @@ export class Sequencer {
596
446
  *
597
447
  * @param pendingTxs - Iterable of pending transactions to construct the block from
598
448
  * @param proposalHeader - The partial header constructed for the proposal
449
+ * @param newGlobalVariables - The global variables for the new block
450
+ * @param proposerAddress - The address of the proposer
599
451
  */
600
452
  @trackSpan('Sequencer.buildBlockAndEnqueuePublish', (_validTxs, _proposalHeader, newGlobalVariables) => ({
601
453
  [Attributes.BLOCK_NUMBER]: newGlobalVariables.blockNumber.toNumber(),
@@ -604,6 +456,7 @@ export class Sequencer {
604
456
  pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
605
457
  proposalHeader: ProposedBlockHeader,
606
458
  newGlobalVariables: GlobalVariables,
459
+ proposerAddress: EthAddress,
607
460
  ): Promise<void> {
608
461
  await this.publisher.validateBlockForSubmission(proposalHeader);
609
462
 
@@ -615,9 +468,22 @@ export class Sequencer {
615
468
  this.setState(SequencerState.CREATING_BLOCK, slot);
616
469
 
617
470
  try {
618
- const buildBlockRes = await this.buildBlock(pendingTxs, newGlobalVariables);
619
- const { publicGas, block, publicProcessorDuration, numTxs, numMsgs, blockBuildingTimer, usedTxs } = buildBlockRes;
471
+ const blockBuilderOptions = this.getDefaultBlockBuilderOptions(Number(slot));
472
+ const buildBlockRes = await this.blockBuilder.buildBlock(pendingTxs, newGlobalVariables, blockBuilderOptions);
473
+ const { publicGas, block, publicProcessorDuration, numTxs, numMsgs, blockBuildingTimer, usedTxs, failedTxs } =
474
+ buildBlockRes;
620
475
  this.metrics.recordBuiltBlock(workTimer.ms(), publicGas.l2Gas);
476
+ await this.dropFailedTxsFromP2P(failedTxs);
477
+
478
+ const minTxsPerBlock = this.isFlushing ? 0 : this.minTxsPerBlock;
479
+
480
+ if (numTxs < minTxsPerBlock) {
481
+ this.log.warn(
482
+ `Block ${blockNumber} has too few txs to be proposed (got ${numTxs} but required ${minTxsPerBlock})`,
483
+ { slot, blockNumber, numTxs },
484
+ );
485
+ throw new Error(`Block has too few successful txs to be proposed`);
486
+ }
621
487
 
622
488
  // TODO(@PhilWindle) We should probably periodically check for things like another
623
489
  // block being published before ours instead of just waiting on our block
@@ -648,7 +514,7 @@ export class Sequencer {
648
514
 
649
515
  this.log.debug('Collecting attestations');
650
516
  const stopCollectingAttestationsTimer = this.metrics.startCollectingAttestationsTimer();
651
- const attestations = await this.collectAttestations(block, usedTxs);
517
+ const attestations = await this.collectAttestations(block, usedTxs, proposerAddress);
652
518
  if (attestations !== undefined) {
653
519
  this.log.verbose(`Collected ${attestations.length} attestations`, { blockHash, blockNumber });
654
520
  }
@@ -666,7 +532,11 @@ export class Sequencer {
666
532
  [Attributes.BLOCK_ARCHIVE]: block.archive.toString(),
667
533
  [Attributes.BLOCK_TXS_COUNT]: txHashes.length,
668
534
  }))
669
- protected async collectAttestations(block: L2Block, txs: Tx[]): Promise<Signature[] | undefined> {
535
+ protected async collectAttestations(
536
+ block: L2Block,
537
+ txs: Tx[],
538
+ proposerAddress: EthAddress,
539
+ ): Promise<CommitteeAttestation[] | undefined> {
670
540
  // TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962): inefficient to have a round trip in here - this should be cached
671
541
  const committee = await this.publisher.getCurrentEpochCommittee();
672
542
 
@@ -695,6 +565,7 @@ export class Sequencer {
695
565
  block.archive.root,
696
566
  block.header.state,
697
567
  txs,
568
+ proposerAddress,
698
569
  blockProposalOptions,
699
570
  );
700
571
  if (!proposal) {
@@ -728,7 +599,7 @@ export class Sequencer {
728
599
  }))
729
600
  protected async enqueuePublishL2Block(
730
601
  block: L2Block,
731
- attestations?: Signature[],
602
+ attestations?: CommitteeAttestation[],
732
603
  txHashes?: TxHash[],
733
604
  ): Promise<void> {
734
605
  // Publishes new block to the network and awaits the tx to be mined
@@ -823,4 +694,8 @@ export class Sequencer {
823
694
  get maxL2BlockGas(): number | undefined {
824
695
  return this.config.maxL2BlockGas;
825
696
  }
697
+
698
+ public getSlasherClient(): SlasherClient {
699
+ return this.slasherClient;
700
+ }
826
701
  }
@@ -1,5 +1,5 @@
1
1
  import type { EthAddress } from '@aztec/foundation/eth-address';
2
- import { Signature } from '@aztec/foundation/eth-signature';
2
+ import { CommitteeAttestation } from '@aztec/stdlib/block';
3
3
  import type { BlockAttestation } from '@aztec/stdlib/p2p';
4
4
 
5
5
  export enum SequencerState {
@@ -50,19 +50,27 @@ export function sequencerStateToNumber(state: SequencerState): number {
50
50
  *
51
51
  * @todo: perform this logic within the memory attestation store instead?
52
52
  */
53
- export function orderAttestations(attestations: BlockAttestation[], orderAddresses: EthAddress[]): Signature[] {
53
+ export function orderAttestations(
54
+ attestations: BlockAttestation[],
55
+ orderAddresses: EthAddress[],
56
+ ): CommitteeAttestation[] {
54
57
  // Create a map of sender addresses to BlockAttestations
55
- const attestationMap = new Map<string, BlockAttestation>();
58
+ const attestationMap = new Map<string, CommitteeAttestation>();
56
59
 
57
60
  for (const attestation of attestations) {
58
61
  const sender = attestation.getSender();
59
- attestationMap.set(sender.toString(), attestation);
62
+ if (sender) {
63
+ attestationMap.set(
64
+ sender.toString(),
65
+ CommitteeAttestation.fromAddressAndSignature(sender, attestation.signature),
66
+ );
67
+ }
60
68
  }
61
69
 
62
- // Create the ordered array based on the orderAddresses, else return an empty signature
70
+ // Create the ordered array based on the orderAddresses, else return an empty attestation
63
71
  const orderedAttestations = orderAddresses.map(address => {
64
72
  const addressString = address.toString();
65
- return attestationMap.get(addressString)?.signature || Signature.empty();
73
+ return attestationMap.get(addressString) || CommitteeAttestation.fromAddress(address);
66
74
  });
67
75
 
68
76
  return orderedAttestations;
@@ -18,6 +18,7 @@ import type {
18
18
  AllowedElement,
19
19
  ClientProtocolCircuitVerifier,
20
20
  MerkleTreeReadOperations,
21
+ PublicProcessorValidator,
21
22
  } from '@aztec/stdlib/interfaces/server';
22
23
  import { DatabasePublicStateSource, type PublicStateSource } from '@aztec/stdlib/trees';
23
24
  import { GlobalVariables, type Tx, type TxValidator } from '@aztec/stdlib/tx';
@@ -74,10 +75,7 @@ export function createValidatorForBlockBuilding(
74
75
  contractDataSource: ContractDataSource,
75
76
  globalVariables: GlobalVariables,
76
77
  setupAllowList: AllowedElement[],
77
- ): {
78
- preprocessValidator: TxValidator<Tx>;
79
- nullifierCache: NullifierCache;
80
- } {
78
+ ): PublicProcessorValidator {
81
79
  const nullifierCache = new NullifierCache(db);
82
80
  const archiveCache = new ArchiveCache(db);
83
81
  const publicStateSource = new DatabasePublicStateSource(db);
@@ -1,7 +0,0 @@
1
- import type { L1ContractsConfig, L1ReaderConfig } from '@aztec/ethereum';
2
- import type { L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
3
- import { type TelemetryClient } from '@aztec/telemetry-client';
4
- import { SlasherClient } from './slasher_client.js';
5
- import type { SlasherConfig } from './slasher_client.js';
6
- export declare const createSlasherClient: (_config: SlasherConfig & L1ContractsConfig & L1ReaderConfig, l2BlockSource: L2BlockSourceEventEmitter, telemetry?: TelemetryClient) => SlasherClient;
7
- //# sourceMappingURL=factory.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/slasher/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,yBAAyB,CAAC;AAEnF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,eAAO,MAAM,mBAAmB,GAC9B,SAAS,aAAa,GAAG,iBAAiB,GAAG,cAAc,EAC3D,eAAe,yBAAyB,EACxC,YAAW,eAAsC,kBAIlD,CAAC"}
@@ -1,8 +0,0 @@
1
- import { getTelemetryClient } from '@aztec/telemetry-client';
2
- import { SlasherClient } from './slasher_client.js';
3
- export const createSlasherClient = (_config, l2BlockSource, telemetry = getTelemetryClient())=>{
4
- const config = {
5
- ..._config
6
- };
7
- return new SlasherClient(config, l2BlockSource, telemetry);
8
- };
@@ -1,3 +0,0 @@
1
- export * from './slasher_client.js';
2
- export { createSlasherClient } from './factory.js';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/slasher/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC"}
@@ -1,2 +0,0 @@
1
- export * from './slasher_client.js';
2
- export { createSlasherClient } from './factory.js';
@@ -1,75 +0,0 @@
1
- import { type L1ContractsConfig, type L1ReaderConfig, type ViemPublicClient } from '@aztec/ethereum';
2
- import { EthAddress } from '@aztec/foundation/eth-address';
3
- import { SlashFactoryAbi } from '@aztec/l1-artifacts';
4
- import { type L2BlockId, type L2BlockSourceEvent, type L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
5
- import { type TelemetryClient, WithTracer } from '@aztec/telemetry-client';
6
- import { type GetContractReturnType } from 'viem';
7
- /**
8
- * Enum defining the possible states of the Slasher client.
9
- */
10
- export declare enum SlasherClientState {
11
- IDLE = 0,
12
- RUNNING = 1,
13
- STOPPED = 2
14
- }
15
- /**
16
- * The synchronization status of the Slasher client.
17
- */
18
- export interface SlasherSyncState {
19
- /**
20
- * The current state of the slasher client.
21
- */
22
- state: SlasherClientState;
23
- /**
24
- * The block number that the slasher client is synced to.
25
- */
26
- syncedToL2Block: L2BlockId;
27
- }
28
- export interface SlasherConfig {
29
- blockCheckIntervalMS: number;
30
- blockRequestBatchSize: number;
31
- }
32
- /**
33
- * @notice A Hypomeiones slasher client implementation
34
- *
35
- * Hypomeiones: a class of individuals in ancient Sparta who were considered inferior or lesser citizens compared
36
- * to the full Spartan citizens.
37
- *
38
- * The implementation here is less than ideal. It exists, not to be the end all be all, but to show that
39
- * slashing can be done with this mechanism.
40
- *
41
- * The implementation is VERY brute in the sense that it only looks for pruned blocks and then tries to slash
42
- * the full committee of that.
43
- * If it sees a prune, it will mark the full epoch as "to be slashed".
44
- *
45
- * Also, it is not particularly smart around what it should if there were to be multiple slashing events.
46
- *
47
- * A few improvements:
48
- * - Only vote on the proposal if it is possible to reach, e.g., if 6 votes are needed and only 4 slots are left don't vote.
49
- * - Stop voting on a payload once it is processed.
50
- * - Only vote on the proposal if it have not already been executed
51
- * - Caveat, we need to fully decide if it is acceptable to have the same payload address multiple times. In the current
52
- * slash factory that could mean slashing the same committee for the same error multiple times.
53
- * - Decide how to deal with multiple slashing events in the same round.
54
- * - This could be that multiple epochs are pruned in the same round, but with the current naive implementation we could end up
55
- * slashing only the first, because the "lifetime" of the second would have passed after that vote
56
- */
57
- export declare class SlasherClient extends WithTracer {
58
- private config;
59
- private l2BlockSource;
60
- private log;
61
- private slashEvents;
62
- protected slashFactoryContract?: GetContractReturnType<typeof SlashFactoryAbi, ViemPublicClient>;
63
- private slashingAmount;
64
- constructor(config: SlasherConfig & L1ContractsConfig & L1ReaderConfig, l2BlockSource: L2BlockSourceEventEmitter, telemetry?: TelemetryClient, log?: import("@aztec/foundation/log").Logger);
65
- start(): void;
66
- getSlashPayload(slotNumber: bigint): Promise<EthAddress | undefined>;
67
- handleBlockStreamEvent(event: L2BlockSourceEvent): Promise<void>;
68
- /**
69
- * Allows consumers to stop the instance of the slasher client.
70
- * 'ready' will now return 'false' and the running promise that keeps the client synced is interrupted.
71
- */
72
- stop(): void;
73
- private handlePruneL2Blocks;
74
- }
75
- //# sourceMappingURL=slasher_client.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"slasher_client.d.ts","sourceRoot":"","sources":["../../src/slasher/slasher_client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EAEtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EACL,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAE/B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,eAAe,EAAE,UAAU,EAAsB,MAAM,yBAAyB,CAAC;AAE/F,OAAO,EAAE,KAAK,qBAAqB,EAA+D,MAAM,MAAM,CAAC;AAE/G;;GAEG;AACH,oBAAY,kBAAkB;IAC5B,IAAI,IAAA;IACJ,OAAO,IAAA;IACP,OAAO,IAAA;CACR;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,KAAK,EAAE,kBAAkB,CAAC;IAC1B;;OAEG;IACH,eAAe,EAAE,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAQD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,aAAc,SAAQ,UAAU;IAWzC,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,aAAa;IAErB,OAAO,CAAC,GAAG;IAbb,OAAO,CAAC,WAAW,CAAoB;IAEvC,SAAS,CAAC,oBAAoB,CAAC,EAAE,qBAAqB,CAAC,OAAO,eAAe,EAAE,gBAAgB,CAAC,CAAa;IAK7G,OAAO,CAAC,cAAc,CAAc;gBAG1B,MAAM,EAAE,aAAa,GAAG,iBAAiB,GAAG,cAAc,EAC1D,aAAa,EAAE,yBAAyB,EAChD,SAAS,GAAE,eAAsC,EACzC,GAAG,yCAA0B;IAwBhC,KAAK;IAMC,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAgC1E,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAavE;;;OAGG;IACI,IAAI;IAMX,OAAO,CAAC,mBAAmB;CAqB5B"}