@aztec/validator-client 0.0.1-commit.3d8f95d → 0.0.1-commit.3f296a7d2

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 (48) hide show
  1. package/README.md +41 -0
  2. package/dest/block_proposal_handler.d.ts +5 -4
  3. package/dest/block_proposal_handler.d.ts.map +1 -1
  4. package/dest/block_proposal_handler.js +130 -62
  5. package/dest/checkpoint_builder.d.ts +21 -8
  6. package/dest/checkpoint_builder.d.ts.map +1 -1
  7. package/dest/checkpoint_builder.js +124 -46
  8. package/dest/config.d.ts +1 -1
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +22 -1
  11. package/dest/duties/validation_service.d.ts +2 -2
  12. package/dest/duties/validation_service.d.ts.map +1 -1
  13. package/dest/duties/validation_service.js +6 -12
  14. package/dest/factory.d.ts +3 -1
  15. package/dest/factory.d.ts.map +1 -1
  16. package/dest/factory.js +3 -2
  17. package/dest/index.d.ts +1 -2
  18. package/dest/index.d.ts.map +1 -1
  19. package/dest/index.js +0 -1
  20. package/dest/key_store/ha_key_store.js +1 -1
  21. package/dest/metrics.d.ts +9 -1
  22. package/dest/metrics.d.ts.map +1 -1
  23. package/dest/metrics.js +12 -0
  24. package/dest/validator.d.ts +15 -7
  25. package/dest/validator.d.ts.map +1 -1
  26. package/dest/validator.js +109 -43
  27. package/package.json +19 -19
  28. package/src/block_proposal_handler.ts +157 -80
  29. package/src/checkpoint_builder.ts +142 -39
  30. package/src/config.ts +22 -1
  31. package/src/duties/validation_service.ts +12 -11
  32. package/src/factory.ts +4 -0
  33. package/src/index.ts +0 -1
  34. package/src/key_store/ha_key_store.ts +1 -1
  35. package/src/metrics.ts +18 -0
  36. package/src/validator.ts +124 -42
  37. package/dest/tx_validator/index.d.ts +0 -3
  38. package/dest/tx_validator/index.d.ts.map +0 -1
  39. package/dest/tx_validator/index.js +0 -2
  40. package/dest/tx_validator/nullifier_cache.d.ts +0 -14
  41. package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
  42. package/dest/tx_validator/nullifier_cache.js +0 -24
  43. package/dest/tx_validator/tx_validator_factory.d.ts +0 -19
  44. package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
  45. package/dest/tx_validator/tx_validator_factory.js +0 -54
  46. package/src/tx_validator/index.ts +0 -2
  47. package/src/tx_validator/nullifier_cache.ts +0 -30
  48. package/src/tx_validator/tx_validator_factory.ts +0 -154
package/src/validator.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { BlobClientInterface } from '@aztec/blob-client/client';
2
2
  import { type Blob, getBlobsPerL1Block } from '@aztec/blob-lib';
3
3
  import type { EpochCache } from '@aztec/epoch-cache';
4
+ import { validateFeeAssetPriceModifier } from '@aztec/ethereum/contracts';
4
5
  import {
5
6
  BlockNumber,
6
7
  CheckpointNumber,
@@ -23,7 +24,8 @@ import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol }
23
24
  import { OffenseType, WANT_TO_SLASH_EVENT, type Watcher, type WatcherEmitter } from '@aztec/slasher';
24
25
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
25
26
  import type { CommitteeAttestationsAndSigners, L2Block, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
26
- import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
27
+ import { validateCheckpoint } from '@aztec/stdlib/checkpoint';
28
+ import { getEpochAtSlot, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
27
29
  import type {
28
30
  CreateCheckpointProposalLastBlockData,
29
31
  ITxProvider,
@@ -44,8 +46,13 @@ import type { CheckpointHeader } from '@aztec/stdlib/rollup';
44
46
  import type { BlockHeader, CheckpointGlobalVariables, Tx } from '@aztec/stdlib/tx';
45
47
  import { AttestationTimeoutError } from '@aztec/stdlib/validators';
46
48
  import { type TelemetryClient, type Tracer, getTelemetryClient } from '@aztec/telemetry-client';
47
- import { createHASigner } from '@aztec/validator-ha-signer/factory';
48
- import { DutyType, type SigningContext } from '@aztec/validator-ha-signer/types';
49
+ import {
50
+ createHASigner,
51
+ createLocalSignerWithProtection,
52
+ createSignerFromSharedDb,
53
+ } from '@aztec/validator-ha-signer/factory';
54
+ import { DutyType, type SigningContext, type SlashingProtectionDatabase } from '@aztec/validator-ha-signer/types';
55
+ import type { ValidatorHASigner } from '@aztec/validator-ha-signer/validator-ha-signer';
49
56
 
50
57
  import { EventEmitter } from 'events';
51
58
  import type { TypedDataDefinition } from 'viem';
@@ -76,7 +83,6 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
76
83
  private validationService: ValidationService;
77
84
  private metrics: ValidatorMetrics;
78
85
  private log: Logger;
79
-
80
86
  // Whether it has already registered handlers on the p2p client
81
87
  private hasRegisteredHandlers = false;
82
88
 
@@ -88,6 +94,8 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
88
94
 
89
95
  private lastEpochForCommitteeUpdateLoop: EpochNumber | undefined;
90
96
  private epochCacheUpdateLoop: RunningPromise;
97
+ /** Tracks the last epoch in which each attester successfully submitted at least one attestation. */
98
+ private lastAttestedEpochByAttester: Map<string, EpochNumber> = new Map();
91
99
 
92
100
  private proposersOfInvalidBlocks: Set<string> = new Set();
93
101
 
@@ -105,6 +113,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
105
113
  private l1ToL2MessageSource: L1ToL2MessageSource,
106
114
  private config: ValidatorClientFullConfig,
107
115
  private blobClient: BlobClientInterface,
116
+ private slashingProtectionSigner: ValidatorHASigner,
108
117
  private dateProvider: DateProvider = new DateProvider(),
109
118
  telemetry: TelemetryClient = getTelemetryClient(),
110
119
  log = createLogger('validator'),
@@ -158,6 +167,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
158
167
  this.log.trace(`No committee found for slot`);
159
168
  return;
160
169
  }
170
+ this.metrics.setCurrentEpoch(epoch);
161
171
  if (epoch !== this.lastEpochForCommitteeUpdateLoop) {
162
172
  const me = this.getValidatorAddresses();
163
173
  const committeeSet = new Set(committee.map(v => v.toString()));
@@ -191,10 +201,12 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
191
201
  blobClient: BlobClientInterface,
192
202
  dateProvider: DateProvider = new DateProvider(),
193
203
  telemetry: TelemetryClient = getTelemetryClient(),
204
+ slashingProtectionDb?: SlashingProtectionDatabase,
194
205
  ) {
195
206
  const metrics = new ValidatorMetrics(telemetry);
196
207
  const blockProposalValidator = new BlockProposalValidator(epochCache, {
197
208
  txsPermitted: !config.disableTransactions,
209
+ maxTxsPerBlock: config.validateMaxTxsPerBlock,
198
210
  });
199
211
  const blockProposalHandler = new BlockProposalHandler(
200
212
  checkpointsBuilder,
@@ -210,16 +222,34 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
210
222
  telemetry,
211
223
  );
212
224
 
213
- let validatorKeyStore: ExtendedValidatorKeyStore = NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager);
214
- if (config.haSigningEnabled) {
225
+ const nodeKeystoreAdapter = NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager);
226
+ let slashingProtectionSigner: ValidatorHASigner;
227
+ if (slashingProtectionDb) {
228
+ // Shared database mode: use a pre-existing database (e.g. for testing HA setups).
229
+ ({ signer: slashingProtectionSigner } = createSignerFromSharedDb(slashingProtectionDb, config, {
230
+ telemetryClient: telemetry,
231
+ dateProvider,
232
+ }));
233
+ } else if (config.haSigningEnabled) {
234
+ // Multi-node HA mode: use PostgreSQL-backed distributed locking.
215
235
  // If maxStuckDutiesAgeMs is not explicitly set, compute it from Aztec slot duration
216
236
  const haConfig = {
217
237
  ...config,
218
238
  maxStuckDutiesAgeMs: config.maxStuckDutiesAgeMs ?? epochCache.getL1Constants().slotDuration * 2 * 1000,
219
239
  };
220
- const { signer } = await createHASigner(haConfig);
221
- validatorKeyStore = new HAKeyStore(validatorKeyStore, signer);
240
+ ({ signer: slashingProtectionSigner } = await createHASigner(haConfig, {
241
+ telemetryClient: telemetry,
242
+ dateProvider,
243
+ }));
244
+ } else {
245
+ // Single-node mode: use LMDB-backed local signing protection.
246
+ // This prevents double-signing if the node crashes and restarts mid-proposal.
247
+ ({ signer: slashingProtectionSigner } = await createLocalSignerWithProtection(config, {
248
+ telemetryClient: telemetry,
249
+ dateProvider,
250
+ }));
222
251
  }
252
+ const validatorKeyStore: ExtendedValidatorKeyStore = new HAKeyStore(nodeKeystoreAdapter, slashingProtectionSigner);
223
253
 
224
254
  const validator = new ValidatorClient(
225
255
  validatorKeyStore,
@@ -232,6 +262,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
232
262
  l1ToL2MessageSource,
233
263
  config,
234
264
  blobClient,
265
+ slashingProtectionSigner,
235
266
  dateProvider,
236
267
  telemetry,
237
268
  );
@@ -269,6 +300,12 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
269
300
  this.config = { ...this.config, ...config };
270
301
  }
271
302
 
303
+ public reloadKeystore(newManager: KeystoreManager): void {
304
+ const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
305
+ this.keyStore = new HAKeyStore(newAdapter, this.slashingProtectionSigner);
306
+ this.validationService = new ValidationService(this.keyStore, this.log.createChild('validation-service'));
307
+ }
308
+
272
309
  public async start() {
273
310
  if (this.epochCacheUpdateLoop.isRunning()) {
274
311
  this.log.warn(`Validator client already started`);
@@ -352,13 +389,12 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
352
389
  return false;
353
390
  }
354
391
 
355
- // Ignore proposals from ourselves (may happen in HA setups)
392
+ // Log self-proposals from HA peers (same validator key on different nodes)
356
393
  if (this.getValidatorAddresses().some(addr => addr.equals(proposer))) {
357
- this.log.warn(`Ignoring block proposal from self for slot ${slotNumber}`, {
394
+ this.log.verbose(`Processing block proposal from HA peer for slot ${slotNumber}`, {
358
395
  proposer: proposer.toString(),
359
396
  slotNumber,
360
397
  });
361
- return false;
362
398
  }
363
399
 
364
400
  // Check if we're in the committee (for metrics purposes)
@@ -390,9 +426,10 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
390
426
  );
391
427
 
392
428
  if (!validationResult.isValid) {
393
- this.log.warn(`Block proposal validation failed: ${validationResult.reason}`, proposalInfo);
394
-
395
429
  const reason = validationResult.reason || 'unknown';
430
+
431
+ this.log.warn(`Block proposal validation failed: ${reason}`, proposalInfo);
432
+
396
433
  // Classify failure reason: bad proposal vs node issue
397
434
  const badProposalReasons: BlockProposalValidationFailureReason[] = [
398
435
  'invalid_proposal',
@@ -447,49 +484,55 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
447
484
  proposal: CheckpointProposalCore,
448
485
  _proposalSender: PeerId,
449
486
  ): Promise<CheckpointAttestation[] | undefined> {
450
- const slotNumber = proposal.slotNumber;
487
+ const proposalSlotNumber = proposal.slotNumber;
451
488
  const proposer = proposal.getSender();
452
489
 
453
490
  // If escape hatch is open for this slot's epoch, do not attest.
454
- if (await this.epochCache.isEscapeHatchOpenAtSlot(slotNumber)) {
455
- this.log.warn(`Escape hatch open for slot ${slotNumber}, skipping checkpoint attestation handling`);
491
+ if (await this.epochCache.isEscapeHatchOpenAtSlot(proposalSlotNumber)) {
492
+ this.log.warn(`Escape hatch open for slot ${proposalSlotNumber}, skipping checkpoint attestation handling`);
456
493
  return undefined;
457
494
  }
458
495
 
459
496
  // Reject proposals with invalid signatures
460
497
  if (!proposer) {
461
- this.log.warn(`Received checkpoint proposal with invalid signature for slot ${slotNumber}`);
498
+ this.log.warn(`Received checkpoint proposal with invalid signature for proposal slot ${proposalSlotNumber}`);
462
499
  return undefined;
463
500
  }
464
501
 
465
502
  // Ignore proposals from ourselves (may happen in HA setups)
466
503
  if (this.getValidatorAddresses().some(addr => addr.equals(proposer))) {
467
- this.log.warn(`Ignoring block proposal from self for slot ${slotNumber}`, {
504
+ this.log.debug(`Ignoring block proposal from self for slot ${proposalSlotNumber}`, {
468
505
  proposer: proposer.toString(),
469
- slotNumber,
506
+ proposalSlotNumber,
470
507
  });
471
508
  return undefined;
472
509
  }
473
510
 
474
- // Check that I have any address in current committee before attesting
475
- const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
511
+ // Validate fee asset price modifier is within allowed range
512
+ if (!validateFeeAssetPriceModifier(proposal.feeAssetPriceModifier)) {
513
+ this.log.warn(
514
+ `Received checkpoint proposal with invalid feeAssetPriceModifier ${proposal.feeAssetPriceModifier} for slot ${proposalSlotNumber}`,
515
+ );
516
+ return undefined;
517
+ }
518
+
519
+ // Check that I have any address in the committee where this checkpoint will land before attesting
520
+ const inCommittee = await this.epochCache.filterInCommittee(proposalSlotNumber, this.getValidatorAddresses());
476
521
  const partOfCommittee = inCommittee.length > 0;
477
522
 
478
523
  const proposalInfo = {
479
- slotNumber,
524
+ proposalSlotNumber,
480
525
  archive: proposal.archive.toString(),
481
526
  proposer: proposer.toString(),
482
- txCount: proposal.txHashes.length,
483
527
  };
484
- this.log.info(`Received checkpoint proposal for slot ${slotNumber}`, {
528
+ this.log.info(`Received checkpoint proposal for slot ${proposalSlotNumber}`, {
485
529
  ...proposalInfo,
486
- txHashes: proposal.txHashes.map(t => t.toString()),
487
530
  fishermanMode: this.config.fishermanMode || false,
488
531
  });
489
532
 
490
533
  // Validate the checkpoint proposal before attesting (unless skipCheckpointProposalValidation is set)
491
534
  if (this.config.skipCheckpointProposalValidation) {
492
- this.log.warn(`Skipping checkpoint proposal validation for slot ${slotNumber}`, proposalInfo);
535
+ this.log.warn(`Skipping checkpoint proposal validation for slot ${proposalSlotNumber}`, proposalInfo);
493
536
  } else {
494
537
  const validationResult = await this.validateCheckpointProposal(proposal, proposalInfo);
495
538
  if (!validationResult.isValid) {
@@ -511,14 +554,28 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
511
554
  }
512
555
 
513
556
  // Provided all of the above checks pass, we can attest to the proposal
514
- this.log.info(`${partOfCommittee ? 'Attesting to' : 'Validated'} checkpoint proposal for slot ${slotNumber}`, {
515
- ...proposalInfo,
516
- inCommittee: partOfCommittee,
517
- fishermanMode: this.config.fishermanMode || false,
518
- });
557
+ this.log.info(
558
+ `${partOfCommittee ? 'Attesting to' : 'Validated'} checkpoint proposal for slot ${proposalSlotNumber}`,
559
+ {
560
+ ...proposalInfo,
561
+ inCommittee: partOfCommittee,
562
+ fishermanMode: this.config.fishermanMode || false,
563
+ },
564
+ );
519
565
 
520
566
  this.metrics.incSuccessfulAttestations(inCommittee.length);
521
567
 
568
+ // Track epoch participation per attester: count each (attester, epoch) pair at most once
569
+ const proposalEpoch = getEpochAtSlot(proposalSlotNumber, this.epochCache.getL1Constants());
570
+ for (const attester of inCommittee) {
571
+ const key = attester.toString();
572
+ const lastEpoch = this.lastAttestedEpochByAttester.get(key);
573
+ if (lastEpoch === undefined || proposalEpoch > lastEpoch) {
574
+ this.lastAttestedEpochByAttester.set(key, proposalEpoch);
575
+ this.metrics.incAttestedEpochCount(attester);
576
+ }
577
+ }
578
+
522
579
  // Determine which validators should attest
523
580
  let attestors: EthAddress[];
524
581
  if (partOfCommittee) {
@@ -537,7 +594,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
537
594
 
538
595
  if (this.config.fishermanMode) {
539
596
  // bail out early and don't save attestations to the pool in fisherman mode
540
- this.log.info(`Creating checkpoint attestations for slot ${slotNumber}`, {
597
+ this.log.info(`Creating checkpoint attestations for slot ${proposalSlotNumber}`, {
541
598
  ...proposalInfo,
542
599
  attestors: attestors.map(a => a.toString()),
543
600
  });
@@ -595,7 +652,11 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
595
652
  proposalInfo: LogData,
596
653
  ): Promise<{ isValid: true } | { isValid: false; reason: string }> {
597
654
  const slot = proposal.slotNumber;
598
- const timeoutSeconds = 10; // TODO(palla/mbps): This should map to the timetable settings
655
+
656
+ // Timeout block syncing at the start of the next slot
657
+ const config = this.checkpointsBuilder.getConfig();
658
+ const nextSlotTimestampSeconds = Number(getTimestampForSlot(SlotNumber(slot + 1), config));
659
+ const timeoutSeconds = Math.max(1, nextSlotTimestampSeconds - Math.floor(this.dateProvider.now() / 1000));
599
660
 
600
661
  // Wait for last block to sync by archive
601
662
  let lastBlockHeader: BlockHeader | undefined;
@@ -630,6 +691,12 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
630
691
  return { isValid: false, reason: 'no_blocks_for_slot' };
631
692
  }
632
693
 
694
+ // Ensure the last block for this slot matches the archive in the checkpoint proposal
695
+ if (!blocks.at(-1)?.archive.root.equals(proposal.archive)) {
696
+ this.log.warn(`Last block archive mismatch for checkpoint proposal`, proposalInfo);
697
+ return { isValid: false, reason: 'last_block_archive_mismatch' };
698
+ }
699
+
633
700
  this.log.debug(`Found ${blocks.length} blocks for slot ${slot}`, {
634
701
  ...proposalInfo,
635
702
  blockNumbers: blocks.map(b => b.number),
@@ -643,14 +710,11 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
643
710
  // Get L1-to-L2 messages for this checkpoint
644
711
  const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
645
712
 
646
- // Compute the previous checkpoint out hashes for the epoch.
647
- // TODO: There can be a more efficient way to get the previous checkpoint out hashes without having to fetch the
648
- // actual checkpoints and the blocks/txs in them.
713
+ // Collect the out hashes of all the checkpoints before this one in the same epoch
649
714
  const epoch = getEpochAtSlot(slot, this.epochCache.getL1Constants());
650
- const previousCheckpoints = (await this.blockSource.getCheckpointsForEpoch(epoch))
651
- .filter(b => b.number < checkpointNumber)
652
- .sort((a, b) => a.number - b.number);
653
- const previousCheckpointOutHashes = previousCheckpoints.map(c => c.getCheckpointOutHash());
715
+ const previousCheckpointOutHashes = (await this.blockSource.getCheckpointsDataForEpoch(epoch))
716
+ .filter(c => c.checkpointNumber < checkpointNumber)
717
+ .map(c => c.checkpointOutHash);
654
718
 
655
719
  // Fork world state at the block before the first block
656
720
  const parentBlockNumber = BlockNumber(firstBlock.number - 1);
@@ -661,6 +725,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
661
725
  const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(
662
726
  checkpointNumber,
663
727
  constants,
728
+ proposal.feeAssetPriceModifier,
664
729
  l1ToL2Messages,
665
730
  previousCheckpointOutHashes,
666
731
  fork,
@@ -707,6 +772,20 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
707
772
  return { isValid: false, reason: 'out_hash_mismatch' };
708
773
  }
709
774
 
775
+ // Final round of validations on the checkpoint, just in case.
776
+ try {
777
+ validateCheckpoint(computedCheckpoint, {
778
+ rollupManaLimit: this.checkpointsBuilder.getConfig().rollupManaLimit,
779
+ maxDABlockGas: this.config.validateMaxDABlockGas,
780
+ maxL2BlockGas: this.config.validateMaxL2BlockGas,
781
+ maxTxsPerBlock: this.config.validateMaxTxsPerBlock,
782
+ maxTxsPerCheckpoint: this.config.validateMaxTxsPerCheckpoint,
783
+ });
784
+ } catch (err) {
785
+ this.log.warn(`Checkpoint validation failed: ${err}`, proposalInfo);
786
+ return { isValid: false, reason: 'checkpoint_validation_failed' };
787
+ }
788
+
710
789
  this.log.verbose(`Checkpoint proposal validation successful for slot ${slot}`, proposalInfo);
711
790
  return { isValid: true };
712
791
  } finally {
@@ -723,6 +802,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
723
802
  chainId: gv.chainId,
724
803
  version: gv.version,
725
804
  slotNumber: gv.slotNumber,
805
+ timestamp: gv.timestamp,
726
806
  coinbase: gv.coinbase,
727
807
  feeRecipient: gv.feeRecipient,
728
808
  gasFees: gv.gasFees,
@@ -732,7 +812,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
732
812
  /**
733
813
  * Uploads blobs for a checkpoint to the filestore (fire and forget).
734
814
  */
735
- private async uploadBlobsForCheckpoint(proposal: CheckpointProposalCore, proposalInfo: LogData): Promise<void> {
815
+ protected async uploadBlobsForCheckpoint(proposal: CheckpointProposalCore, proposalInfo: LogData): Promise<void> {
736
816
  try {
737
817
  const lastBlockHeader = await this.blockSource.getBlockHeaderByArchive(proposal.archive);
738
818
  if (!lastBlockHeader) {
@@ -747,7 +827,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
747
827
  }
748
828
 
749
829
  const blobFields = blocks.flatMap(b => b.toBlobFields());
750
- const blobs: Blob[] = getBlobsPerL1Block(blobFields);
830
+ const blobs: Blob[] = await getBlobsPerL1Block(blobFields);
751
831
  await this.blobClient.sendBlobsToFilestore(blobs);
752
832
  this.log.debug(`Uploaded ${blobs.length} blobs to filestore for checkpoint at slot ${proposal.slotNumber}`, {
753
833
  ...proposalInfo,
@@ -876,6 +956,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
876
956
  async createCheckpointProposal(
877
957
  checkpointHeader: CheckpointHeader,
878
958
  archive: Fr,
959
+ feeAssetPriceModifier: bigint,
879
960
  lastBlockInfo: CreateCheckpointProposalLastBlockData | undefined,
880
961
  proposerAddress: EthAddress | undefined,
881
962
  options: CheckpointProposalOptions = {},
@@ -897,6 +978,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
897
978
  const newProposal = await this.validationService.createCheckpointProposal(
898
979
  checkpointHeader,
899
980
  archive,
981
+ feeAssetPriceModifier,
900
982
  lastBlockInfo,
901
983
  proposerAddress,
902
984
  options,
@@ -1,3 +0,0 @@
1
- export * from './nullifier_cache.js';
2
- export * from './tx_validator_factory.js';
3
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90eF92YWxpZGF0b3IvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxzQkFBc0IsQ0FBQztBQUNyQyxjQUFjLDJCQUEyQixDQUFDIn0=
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tx_validator/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC"}
@@ -1,2 +0,0 @@
1
- export * from './nullifier_cache.js';
2
- export * from './tx_validator_factory.js';
@@ -1,14 +0,0 @@
1
- import type { NullifierSource } from '@aztec/p2p';
2
- import type { MerkleTreeReadOperations } from '@aztec/stdlib/interfaces/server';
3
- /**
4
- * Implements a nullifier source by checking a DB and an in-memory collection.
5
- * Intended for validating transactions as they are added to a block.
6
- */
7
- export declare class NullifierCache implements NullifierSource {
8
- private db;
9
- nullifiers: Set<string>;
10
- constructor(db: MerkleTreeReadOperations);
11
- nullifiersExist(nullifiers: Buffer[]): Promise<boolean[]>;
12
- addNullifiers(nullifiers: Buffer[]): void;
13
- }
14
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibnVsbGlmaWVyX2NhY2hlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdHhfdmFsaWRhdG9yL251bGxpZmllcl9jYWNoZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxlQUFlLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDbEQsT0FBTyxLQUFLLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUdoRjs7O0dBR0c7QUFDSCxxQkFBYSxjQUFlLFlBQVcsZUFBZTtJQUd4QyxPQUFPLENBQUMsRUFBRTtJQUZ0QixVQUFVLEVBQUUsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBRXhCLFlBQW9CLEVBQUUsRUFBRSx3QkFBd0IsRUFFL0M7SUFFWSxlQUFlLENBQUMsVUFBVSxFQUFFLE1BQU0sRUFBRSxHQUFHLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQU9yRTtJQUVNLGFBQWEsQ0FBQyxVQUFVLEVBQUUsTUFBTSxFQUFFLFFBSXhDO0NBQ0YifQ==
@@ -1 +0,0 @@
1
- {"version":3,"file":"nullifier_cache.d.ts","sourceRoot":"","sources":["../../src/tx_validator/nullifier_cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAGhF;;;GAGG;AACH,qBAAa,cAAe,YAAW,eAAe;IAGxC,OAAO,CAAC,EAAE;IAFtB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAExB,YAAoB,EAAE,EAAE,wBAAwB,EAE/C;IAEY,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAOrE;IAEM,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,QAIxC;CACF"}
@@ -1,24 +0,0 @@
1
- import { MerkleTreeId } from '@aztec/stdlib/trees';
2
- /**
3
- * Implements a nullifier source by checking a DB and an in-memory collection.
4
- * Intended for validating transactions as they are added to a block.
5
- */ export class NullifierCache {
6
- db;
7
- nullifiers;
8
- constructor(db){
9
- this.db = db;
10
- this.nullifiers = new Set();
11
- }
12
- async nullifiersExist(nullifiers) {
13
- const cacheResults = nullifiers.map((n)=>this.nullifiers.has(n.toString()));
14
- const toCheckDb = nullifiers.filter((_n, index)=>!cacheResults[index]);
15
- const dbHits = await this.db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, toCheckDb);
16
- let dbIndex = 0;
17
- return nullifiers.map((_n, index)=>cacheResults[index] || dbHits[dbIndex++] !== undefined);
18
- }
19
- addNullifiers(nullifiers) {
20
- for (const nullifier of nullifiers){
21
- this.nullifiers.add(nullifier.toString());
22
- }
23
- }
24
- }
@@ -1,19 +0,0 @@
1
- import { BlockNumber } from '@aztec/foundation/branded-types';
2
- import type { LoggerBindings } from '@aztec/foundation/log';
3
- import type { ContractDataSource } from '@aztec/stdlib/contract';
4
- import type { GasFees } from '@aztec/stdlib/gas';
5
- import type { AllowedElement, ClientProtocolCircuitVerifier, MerkleTreeReadOperations, PublicProcessorValidator } from '@aztec/stdlib/interfaces/server';
6
- import { GlobalVariables, type Tx, type TxValidator } from '@aztec/stdlib/tx';
7
- import type { UInt64 } from '@aztec/stdlib/types';
8
- export declare function createValidatorForAcceptingTxs(db: MerkleTreeReadOperations, contractDataSource: ContractDataSource, verifier: ClientProtocolCircuitVerifier | undefined, { l1ChainId, rollupVersion, setupAllowList, gasFees, skipFeeEnforcement, timestamp, blockNumber, txsPermitted }: {
9
- l1ChainId: number;
10
- rollupVersion: number;
11
- setupAllowList: AllowedElement[];
12
- gasFees: GasFees;
13
- skipFeeEnforcement?: boolean;
14
- timestamp: UInt64;
15
- blockNumber: BlockNumber;
16
- txsPermitted: boolean;
17
- }, bindings?: LoggerBindings): TxValidator<Tx>;
18
- export declare function createValidatorForBlockBuilding(db: MerkleTreeReadOperations, contractDataSource: ContractDataSource, globalVariables: GlobalVariables, setupAllowList: AllowedElement[], bindings?: LoggerBindings): PublicProcessorValidator;
19
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHhfdmFsaWRhdG9yX2ZhY3RvcnkuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90eF92YWxpZGF0b3IvdHhfdmFsaWRhdG9yX2ZhY3RvcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRTlELE9BQU8sS0FBSyxFQUFFLGNBQWMsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBaUI1RCxPQUFPLEtBQUssRUFBRSxrQkFBa0IsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ2pFLE9BQU8sS0FBSyxFQUFFLE9BQU8sRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQ2pELE9BQU8sS0FBSyxFQUNWLGNBQWMsRUFDZCw2QkFBNkIsRUFDN0Isd0JBQXdCLEVBQ3hCLHdCQUF3QixFQUN6QixNQUFNLGlDQUFpQyxDQUFDO0FBRXpDLE9BQU8sRUFBRSxlQUFlLEVBQUUsS0FBSyxFQUFFLEVBQUUsS0FBSyxXQUFXLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUM5RSxPQUFPLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUlsRCx3QkFBZ0IsOEJBQThCLENBQzVDLEVBQUUsRUFBRSx3QkFBd0IsRUFDNUIsa0JBQWtCLEVBQUUsa0JBQWtCLEVBQ3RDLFFBQVEsRUFBRSw2QkFBNkIsR0FBRyxTQUFTLEVBQ25ELEVBQ0UsU0FBUyxFQUNULGFBQWEsRUFDYixjQUFjLEVBQ2QsT0FBTyxFQUNQLGtCQUFrQixFQUNsQixTQUFTLEVBQ1QsV0FBVyxFQUNYLFlBQVksRUFDYixFQUFFO0lBQ0QsU0FBUyxFQUFFLE1BQU0sQ0FBQztJQUNsQixhQUFhLEVBQUUsTUFBTSxDQUFDO0lBQ3RCLGNBQWMsRUFBRSxjQUFjLEVBQUUsQ0FBQztJQUNqQyxPQUFPLEVBQUUsT0FBTyxDQUFDO0lBQ2pCLGtCQUFrQixDQUFDLEVBQUUsT0FBTyxDQUFDO0lBQzdCLFNBQVMsRUFBRSxNQUFNLENBQUM7SUFDbEIsV0FBVyxFQUFFLFdBQVcsQ0FBQztJQUN6QixZQUFZLEVBQUUsT0FBTyxDQUFDO0NBQ3ZCLEVBQ0QsUUFBUSxDQUFDLEVBQUUsY0FBYyxHQUN4QixXQUFXLENBQUMsRUFBRSxDQUFDLENBcUNqQjtBQUVELHdCQUFnQiwrQkFBK0IsQ0FDN0MsRUFBRSxFQUFFLHdCQUF3QixFQUM1QixrQkFBa0IsRUFBRSxrQkFBa0IsRUFDdEMsZUFBZSxFQUFFLGVBQWUsRUFDaEMsY0FBYyxFQUFFLGNBQWMsRUFBRSxFQUNoQyxRQUFRLENBQUMsRUFBRSxjQUFjLEdBQ3hCLHdCQUF3QixDQWlCMUIifQ==
@@ -1 +0,0 @@
1
- {"version":3,"file":"tx_validator_factory.d.ts","sourceRoot":"","sources":["../../src/tx_validator/tx_validator_factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAE9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAiB5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EACV,cAAc,EACd,6BAA6B,EAC7B,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAIlD,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,wBAAwB,EAC5B,kBAAkB,EAAE,kBAAkB,EACtC,QAAQ,EAAE,6BAA6B,GAAG,SAAS,EACnD,EACE,SAAS,EACT,aAAa,EACb,cAAc,EACd,OAAO,EACP,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,YAAY,EACb,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,cAAc,EAAE,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;CACvB,EACD,QAAQ,CAAC,EAAE,cAAc,GACxB,WAAW,CAAC,EAAE,CAAC,CAqCjB;AAED,wBAAgB,+BAA+B,CAC7C,EAAE,EAAE,wBAAwB,EAC5B,kBAAkB,EAAE,kBAAkB,EACtC,eAAe,EAAE,eAAe,EAChC,cAAc,EAAE,cAAc,EAAE,EAChC,QAAQ,CAAC,EAAE,cAAc,GACxB,wBAAwB,CAiB1B"}
@@ -1,54 +0,0 @@
1
- import { Fr } from '@aztec/foundation/curves/bn254';
2
- import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
3
- import { AggregateTxValidator, ArchiveCache, BlockHeaderTxValidator, DataTxValidator, DoubleSpendTxValidator, GasTxValidator, MetadataTxValidator, PhasesTxValidator, SizeTxValidator, TimestampTxValidator, TxPermittedValidator, TxProofValidator } from '@aztec/p2p';
4
- import { ProtocolContractAddress, protocolContractsHash } from '@aztec/protocol-contracts';
5
- import { DatabasePublicStateSource } from '@aztec/stdlib/trees';
6
- import { NullifierCache } from './nullifier_cache.js';
7
- export function createValidatorForAcceptingTxs(db, contractDataSource, verifier, { l1ChainId, rollupVersion, setupAllowList, gasFees, skipFeeEnforcement, timestamp, blockNumber, txsPermitted }, bindings) {
8
- const validators = [
9
- new TxPermittedValidator(txsPermitted, bindings),
10
- new SizeTxValidator(bindings),
11
- new DataTxValidator(bindings),
12
- new MetadataTxValidator({
13
- l1ChainId: new Fr(l1ChainId),
14
- rollupVersion: new Fr(rollupVersion),
15
- protocolContractsHash,
16
- vkTreeRoot: getVKTreeRoot()
17
- }, bindings),
18
- new TimestampTxValidator({
19
- timestamp,
20
- blockNumber
21
- }, bindings),
22
- new DoubleSpendTxValidator(new NullifierCache(db), bindings),
23
- new PhasesTxValidator(contractDataSource, setupAllowList, timestamp, bindings),
24
- new BlockHeaderTxValidator(new ArchiveCache(db), bindings)
25
- ];
26
- if (!skipFeeEnforcement) {
27
- validators.push(new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, gasFees, bindings));
28
- }
29
- if (verifier) {
30
- validators.push(new TxProofValidator(verifier, bindings));
31
- }
32
- return new AggregateTxValidator(...validators);
33
- }
34
- export function createValidatorForBlockBuilding(db, contractDataSource, globalVariables, setupAllowList, bindings) {
35
- const nullifierCache = new NullifierCache(db);
36
- const archiveCache = new ArchiveCache(db);
37
- const publicStateSource = new DatabasePublicStateSource(db);
38
- return {
39
- preprocessValidator: preprocessValidator(nullifierCache, archiveCache, publicStateSource, contractDataSource, globalVariables, setupAllowList, bindings),
40
- nullifierCache
41
- };
42
- }
43
- function preprocessValidator(nullifierCache, archiveCache, publicStateSource, contractDataSource, globalVariables, setupAllowList, bindings) {
44
- // We don't include the TxProofValidator nor the DataTxValidator here because they are already checked by the time we get to block building.
45
- return new AggregateTxValidator(new MetadataTxValidator({
46
- l1ChainId: globalVariables.chainId,
47
- rollupVersion: globalVariables.version,
48
- protocolContractsHash,
49
- vkTreeRoot: getVKTreeRoot()
50
- }, bindings), new TimestampTxValidator({
51
- timestamp: globalVariables.timestamp,
52
- blockNumber: globalVariables.blockNumber
53
- }, bindings), new DoubleSpendTxValidator(nullifierCache, bindings), new PhasesTxValidator(contractDataSource, setupAllowList, globalVariables.timestamp, bindings), new GasTxValidator(publicStateSource, ProtocolContractAddress.FeeJuice, globalVariables.gasFees, bindings), new BlockHeaderTxValidator(archiveCache, bindings));
54
- }
@@ -1,2 +0,0 @@
1
- export * from './nullifier_cache.js';
2
- export * from './tx_validator_factory.js';
@@ -1,30 +0,0 @@
1
- import type { NullifierSource } from '@aztec/p2p';
2
- import type { MerkleTreeReadOperations } from '@aztec/stdlib/interfaces/server';
3
- import { MerkleTreeId } from '@aztec/stdlib/trees';
4
-
5
- /**
6
- * Implements a nullifier 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 NullifierCache implements NullifierSource {
10
- nullifiers: Set<string>;
11
-
12
- constructor(private db: MerkleTreeReadOperations) {
13
- this.nullifiers = new Set();
14
- }
15
-
16
- public async nullifiersExist(nullifiers: Buffer[]): Promise<boolean[]> {
17
- const cacheResults = nullifiers.map(n => this.nullifiers.has(n.toString()));
18
- const toCheckDb = nullifiers.filter((_n, index) => !cacheResults[index]);
19
- const dbHits = await this.db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, toCheckDb);
20
-
21
- let dbIndex = 0;
22
- return nullifiers.map((_n, index) => cacheResults[index] || dbHits[dbIndex++] !== undefined);
23
- }
24
-
25
- public addNullifiers(nullifiers: Buffer[]) {
26
- for (const nullifier of nullifiers) {
27
- this.nullifiers.add(nullifier.toString());
28
- }
29
- }
30
- }