@aztec/validator-client 0.0.1-commit.3469e52 → 0.0.1-commit.3895657bc

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/README.md +64 -19
  2. package/dest/block_proposal_handler.d.ts +7 -9
  3. package/dest/block_proposal_handler.d.ts.map +1 -1
  4. package/dest/block_proposal_handler.js +71 -81
  5. package/dest/checkpoint_builder.d.ts +22 -13
  6. package/dest/checkpoint_builder.d.ts.map +1 -1
  7. package/dest/checkpoint_builder.js +107 -39
  8. package/dest/config.d.ts +1 -1
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +30 -7
  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 +5 -11
  14. package/dest/factory.d.ts +1 -1
  15. package/dest/factory.d.ts.map +1 -1
  16. package/dest/factory.js +2 -1
  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.d.ts +1 -1
  21. package/dest/key_store/ha_key_store.d.ts.map +1 -1
  22. package/dest/key_store/ha_key_store.js +2 -2
  23. package/dest/metrics.d.ts +12 -3
  24. package/dest/metrics.d.ts.map +1 -1
  25. package/dest/metrics.js +46 -5
  26. package/dest/validator.d.ts +40 -14
  27. package/dest/validator.d.ts.map +1 -1
  28. package/dest/validator.js +212 -56
  29. package/package.json +19 -17
  30. package/src/block_proposal_handler.ts +87 -109
  31. package/src/checkpoint_builder.ts +146 -40
  32. package/src/config.ts +30 -7
  33. package/src/duties/validation_service.ts +11 -10
  34. package/src/factory.ts +1 -0
  35. package/src/index.ts +0 -1
  36. package/src/key_store/ha_key_store.ts +2 -2
  37. package/src/metrics.ts +63 -6
  38. package/src/validator.ts +262 -68
  39. package/dest/tx_validator/index.d.ts +0 -3
  40. package/dest/tx_validator/index.d.ts.map +0 -1
  41. package/dest/tx_validator/index.js +0 -2
  42. package/dest/tx_validator/nullifier_cache.d.ts +0 -14
  43. package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
  44. package/dest/tx_validator/nullifier_cache.js +0 -24
  45. package/dest/tx_validator/tx_validator_factory.d.ts +0 -18
  46. package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
  47. package/dest/tx_validator/tx_validator_factory.js +0 -54
  48. package/src/tx_validator/index.ts +0 -2
  49. package/src/tx_validator/nullifier_cache.ts +0 -30
  50. package/src/tx_validator/tx_validator_factory.ts +0 -135
@@ -1,10 +1,12 @@
1
+ import { NUM_CHECKPOINT_END_MARKER_FIELDS, getNumBlockEndBlobFields } from '@aztec/blob-lib/encoding';
2
+ import { BLOBS_PER_CHECKPOINT, FIELDS_PER_BLOB, MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT } from '@aztec/constants';
1
3
  import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
2
- import { merge, pick } from '@aztec/foundation/collection';
4
+ import { merge, pick, sum } from '@aztec/foundation/collection';
3
5
  import { Fr } from '@aztec/foundation/curves/bn254';
4
- import { createLogger } from '@aztec/foundation/log';
6
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
5
7
  import { bufferToHex } from '@aztec/foundation/string';
6
- import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
7
- import { getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
8
+ import { DateProvider, elapsed } from '@aztec/foundation/timer';
9
+ import { createTxValidatorForBlockBuilding, getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
8
10
  import { LightweightCheckpointBuilder } from '@aztec/prover-client/light';
9
11
  import {
10
12
  GuardedMerkleTreeOperations,
@@ -12,7 +14,7 @@ import {
12
14
  PublicProcessor,
13
15
  createPublicTxSimulatorForBlockBuilding,
14
16
  } from '@aztec/simulator/server';
15
- import { L2BlockNew } from '@aztec/stdlib/block';
17
+ import { L2Block } from '@aztec/stdlib/block';
16
18
  import { Checkpoint } from '@aztec/stdlib/checkpoint';
17
19
  import type { ContractDataSource } from '@aztec/stdlib/contract';
18
20
  import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
@@ -24,30 +26,25 @@ import {
24
26
  type ICheckpointBlockBuilder,
25
27
  type ICheckpointsBuilder,
26
28
  type MerkleTreeWriteOperations,
29
+ NoValidTxsError,
27
30
  type PublicProcessorLimits,
28
31
  type WorldStateSynchronizer,
29
32
  } from '@aztec/stdlib/interfaces/server';
33
+ import { type DebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
30
34
  import { MerkleTreeId } from '@aztec/stdlib/trees';
31
35
  import { type CheckpointGlobalVariables, GlobalVariables, StateReference, Tx } from '@aztec/stdlib/tx';
32
36
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
33
37
 
34
- import { createValidatorForBlockBuilding } from './tx_validator/tx_validator_factory.js';
35
-
36
38
  // Re-export for backward compatibility
37
39
  export type { BuildBlockInCheckpointResult } from '@aztec/stdlib/interfaces/server';
38
40
 
39
- const log = createLogger('checkpoint-builder');
40
-
41
- /** Result of building a block within a checkpoint. Extends the base interface with timer. */
42
- export interface BuildBlockInCheckpointResultWithTimer extends BuildBlockInCheckpointResult {
43
- blockBuildingTimer: Timer;
44
- }
45
-
46
41
  /**
47
42
  * Builder for a single checkpoint. Handles building blocks within the checkpoint
48
43
  * and completing it.
49
44
  */
50
45
  export class CheckpointBuilder implements ICheckpointBlockBuilder {
46
+ private log: Logger;
47
+
51
48
  constructor(
52
49
  private checkpointBuilder: LightweightCheckpointBuilder,
53
50
  private fork: MerkleTreeWriteOperations,
@@ -55,7 +52,14 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
55
52
  private contractDataSource: ContractDataSource,
56
53
  private dateProvider: DateProvider,
57
54
  private telemetryClient: TelemetryClient,
58
- ) {}
55
+ bindings?: LoggerBindings,
56
+ private debugLogStore: DebugLogStore = new NullDebugLogStore(),
57
+ ) {
58
+ this.log = createLogger('checkpoint-builder', {
59
+ ...bindings,
60
+ instanceId: `checkpoint-${checkpointBuilder.checkpointNumber}`,
61
+ });
62
+ }
59
63
 
60
64
  getConstantData(): CheckpointGlobalVariables {
61
65
  return this.checkpointBuilder.constants;
@@ -63,17 +67,17 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
63
67
 
64
68
  /**
65
69
  * Builds a single block within this checkpoint.
70
+ * Automatically caps gas and blob field limits based on checkpoint-level budgets and prior blocks.
66
71
  */
67
72
  async buildBlock(
68
73
  pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
69
74
  blockNumber: BlockNumber,
70
75
  timestamp: bigint,
71
- opts: PublicProcessorLimits & { expectedEndState?: StateReference },
72
- ): Promise<BuildBlockInCheckpointResultWithTimer> {
73
- const blockBuildingTimer = new Timer();
76
+ opts: PublicProcessorLimits & { expectedEndState?: StateReference } = {},
77
+ ): Promise<BuildBlockInCheckpointResult> {
74
78
  const slot = this.checkpointBuilder.constants.slotNumber;
75
79
 
76
- log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
80
+ this.log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
77
81
  slot,
78
82
  blockNumber,
79
83
  ...opts,
@@ -93,37 +97,47 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
93
97
  });
94
98
  const { processor, validator } = await this.makeBlockBuilderDeps(globalVariables, this.fork);
95
99
 
96
- const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs, _, usedTxBlobFields]] = await elapsed(() =>
97
- processor.process(pendingTxs, opts, validator),
100
+ // Cap gas limits amd available blob fields by remaining checkpoint-level budgets
101
+ const cappedOpts: PublicProcessorLimits & { expectedEndState?: StateReference } = {
102
+ ...opts,
103
+ ...this.capLimitsByCheckpointBudgets(opts),
104
+ };
105
+
106
+ const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs]] = await elapsed(() =>
107
+ processor.process(pendingTxs, cappedOpts, validator),
98
108
  );
99
109
 
110
+ // Throw if we didn't collect a single valid tx and we're not allowed to build empty blocks
111
+ // (only the first block in a checkpoint can be empty)
112
+ if (processedTxs.length === 0 && this.checkpointBuilder.getBlockCount() > 0) {
113
+ throw new NoValidTxsError(failedTxs);
114
+ }
115
+
100
116
  // Add block to checkpoint
101
- const block = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
117
+ const { block } = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
102
118
  expectedEndState: opts.expectedEndState,
103
119
  });
104
120
 
105
- // How much public gas was processed
106
- const publicGas = processedTxs.reduce((acc, tx) => acc.add(tx.gasUsed.publicGas), Gas.empty());
121
+ this.log.debug('Built block within checkpoint', {
122
+ header: block.header.toInspect(),
123
+ processedTxs: processedTxs.map(tx => tx.hash.toString()),
124
+ failedTxs: failedTxs.map(tx => tx.tx.txHash.toString()),
125
+ });
107
126
 
108
- const res = {
127
+ return {
109
128
  block,
110
- publicGas,
111
129
  publicProcessorDuration,
112
130
  numTxs: processedTxs.length,
113
131
  failedTxs,
114
- blockBuildingTimer,
115
132
  usedTxs,
116
- usedTxBlobFields,
117
133
  };
118
- log.debug('Built block within checkpoint', res.block.header);
119
- return res;
120
134
  }
121
135
 
122
136
  /** Completes the checkpoint and returns it. */
123
137
  async completeCheckpoint(): Promise<Checkpoint> {
124
138
  const checkpoint = await this.checkpointBuilder.completeCheckpoint();
125
139
 
126
- log.verbose(`Completed checkpoint ${checkpoint.number}`, {
140
+ this.log.verbose(`Completed checkpoint ${checkpoint.number}`, {
127
141
  checkpointNumber: checkpoint.number,
128
142
  numBlocks: checkpoint.blocks.length,
129
143
  archiveRoot: checkpoint.archive.root.toString(),
@@ -137,16 +151,79 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
137
151
  return this.checkpointBuilder.clone().completeCheckpoint();
138
152
  }
139
153
 
154
+ /**
155
+ * Caps per-block gas and blob field limits by remaining checkpoint-level budgets.
156
+ * Computes remaining L2 gas (mana), DA gas, and blob fields from blocks already added to the checkpoint,
157
+ * then returns opts with maxBlockGas and maxBlobFields capped accordingly.
158
+ */
159
+ protected capLimitsByCheckpointBudgets(
160
+ opts: PublicProcessorLimits,
161
+ ): Pick<PublicProcessorLimits, 'maxBlockGas' | 'maxBlobFields' | 'maxTransactions'> {
162
+ const existingBlocks = this.checkpointBuilder.getBlocks();
163
+
164
+ // Remaining L2 gas (mana)
165
+ // IMPORTANT: This assumes mana is computed solely based on L2 gas used in transactions.
166
+ // This may change in the future.
167
+ const usedMana = sum(existingBlocks.map(b => b.header.totalManaUsed.toNumber()));
168
+ const remainingMana = this.config.rollupManaLimit - usedMana;
169
+
170
+ // Remaining DA gas
171
+ const usedDAGas = sum(existingBlocks.map(b => b.computeDAGasUsed())) ?? 0;
172
+ const remainingDAGas = MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT - usedDAGas;
173
+
174
+ // Remaining blob fields (block blob fields include both tx data and block-end overhead)
175
+ const usedBlobFields = sum(existingBlocks.map(b => b.toBlobFields().length));
176
+ const totalBlobCapacity = BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB - NUM_CHECKPOINT_END_MARKER_FIELDS;
177
+ const isFirstBlock = existingBlocks.length === 0;
178
+ const blockEndOverhead = getNumBlockEndBlobFields(isFirstBlock);
179
+ const maxBlobFieldsForTxs = totalBlobCapacity - usedBlobFields - blockEndOverhead;
180
+
181
+ // Cap L2 gas by remaining checkpoint mana
182
+ const cappedL2Gas = Math.min(opts.maxBlockGas?.l2Gas ?? remainingMana, remainingMana);
183
+
184
+ // Cap DA gas by remaining checkpoint DA gas budget
185
+ const cappedDAGas = Math.min(opts.maxBlockGas?.daGas ?? remainingDAGas, remainingDAGas);
186
+
187
+ // Cap blob fields by remaining checkpoint blob capacity
188
+ const cappedBlobFields =
189
+ opts.maxBlobFields !== undefined ? Math.min(opts.maxBlobFields, maxBlobFieldsForTxs) : maxBlobFieldsForTxs;
190
+
191
+ // Cap transaction count by remaining checkpoint tx budget
192
+ let cappedMaxTransactions: number | undefined;
193
+ if (this.config.maxTxsPerCheckpoint !== undefined) {
194
+ const usedTxs = sum(existingBlocks.map(b => b.body.txEffects.length));
195
+ const remainingTxs = Math.max(0, this.config.maxTxsPerCheckpoint - usedTxs);
196
+ cappedMaxTransactions =
197
+ opts.maxTransactions !== undefined ? Math.min(opts.maxTransactions, remainingTxs) : remainingTxs;
198
+ } else {
199
+ cappedMaxTransactions = opts.maxTransactions;
200
+ }
201
+
202
+ return {
203
+ maxBlockGas: new Gas(cappedDAGas, cappedL2Gas),
204
+ maxBlobFields: cappedBlobFields,
205
+ maxTransactions: cappedMaxTransactions,
206
+ };
207
+ }
208
+
140
209
  protected async makeBlockBuilderDeps(globalVariables: GlobalVariables, fork: MerkleTreeWriteOperations) {
141
- const txPublicSetupAllowList = this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
142
- const contractsDB = new PublicContractsDB(this.contractDataSource);
210
+ const txPublicSetupAllowList = [
211
+ ...(await getDefaultAllowedSetupFunctions()),
212
+ ...(this.config.txPublicSetupAllowListExtend ?? []),
213
+ ];
214
+ const contractsDB = new PublicContractsDB(this.contractDataSource, this.log.getBindings());
143
215
  const guardedFork = new GuardedMerkleTreeOperations(fork);
144
216
 
217
+ const collectDebugLogs = this.debugLogStore.isEnabled;
218
+
219
+ const bindings = this.log.getBindings();
145
220
  const publicTxSimulator = createPublicTxSimulatorForBlockBuilding(
146
221
  guardedFork,
147
222
  contractsDB,
148
223
  globalVariables,
149
224
  this.telemetryClient,
225
+ bindings,
226
+ collectDebugLogs,
150
227
  );
151
228
 
152
229
  const processor = new PublicProcessor(
@@ -156,15 +233,17 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
156
233
  publicTxSimulator,
157
234
  this.dateProvider,
158
235
  this.telemetryClient,
159
- undefined,
236
+ createLogger('simulator:public-processor', bindings),
160
237
  this.config,
238
+ this.debugLogStore,
161
239
  );
162
240
 
163
- const validator = createValidatorForBlockBuilding(
241
+ const validator = createTxValidatorForBlockBuilding(
164
242
  fork,
165
243
  this.contractDataSource,
166
244
  globalVariables,
167
245
  txPublicSetupAllowList,
246
+ this.log.getBindings(),
168
247
  );
169
248
 
170
249
  return {
@@ -176,13 +255,18 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
176
255
 
177
256
  /** Factory for creating checkpoint builders. */
178
257
  export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
258
+ private log: Logger;
259
+
179
260
  constructor(
180
261
  private config: FullNodeBlockBuilderConfig & Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration'>,
181
262
  private worldState: WorldStateSynchronizer,
182
263
  private contractDataSource: ContractDataSource,
183
264
  private dateProvider: DateProvider,
184
265
  private telemetryClient: TelemetryClient = getTelemetryClient(),
185
- ) {}
266
+ private debugLogStore: DebugLogStore = new NullDebugLogStore(),
267
+ ) {
268
+ this.log = createLogger('checkpoint-builder');
269
+ }
186
270
 
187
271
  public getConfig(): FullNodeBlockBuilderConfig {
188
272
  return this.config;
@@ -198,19 +282,22 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
198
282
  async startCheckpoint(
199
283
  checkpointNumber: CheckpointNumber,
200
284
  constants: CheckpointGlobalVariables,
285
+ feeAssetPriceModifier: bigint,
201
286
  l1ToL2Messages: Fr[],
202
287
  previousCheckpointOutHashes: Fr[],
203
288
  fork: MerkleTreeWriteOperations,
289
+ bindings?: LoggerBindings,
204
290
  ): Promise<CheckpointBuilder> {
205
291
  const stateReference = await fork.getStateReference();
206
292
  const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
207
293
 
208
- log.verbose(`Building new checkpoint ${checkpointNumber}`, {
294
+ this.log.verbose(`Building new checkpoint ${checkpointNumber}`, {
209
295
  checkpointNumber,
210
296
  msgCount: l1ToL2Messages.length,
211
297
  initialStateReference: stateReference.toInspect(),
212
298
  initialArchiveRoot: bufferToHex(archiveTree.root),
213
299
  constants,
300
+ feeAssetPriceModifier,
214
301
  });
215
302
 
216
303
  const lightweightBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
@@ -219,6 +306,8 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
219
306
  l1ToL2Messages,
220
307
  previousCheckpointOutHashes,
221
308
  fork,
309
+ bindings,
310
+ feeAssetPriceModifier,
222
311
  );
223
312
 
224
313
  return new CheckpointBuilder(
@@ -228,6 +317,8 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
228
317
  this.contractDataSource,
229
318
  this.dateProvider,
230
319
  this.telemetryClient,
320
+ bindings,
321
+ this.debugLogStore,
231
322
  );
232
323
  }
233
324
 
@@ -237,34 +328,47 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
237
328
  async openCheckpoint(
238
329
  checkpointNumber: CheckpointNumber,
239
330
  constants: CheckpointGlobalVariables,
331
+ feeAssetPriceModifier: bigint,
240
332
  l1ToL2Messages: Fr[],
241
333
  previousCheckpointOutHashes: Fr[],
242
334
  fork: MerkleTreeWriteOperations,
243
- existingBlocks: L2BlockNew[] = [],
335
+ existingBlocks: L2Block[] = [],
336
+ bindings?: LoggerBindings,
244
337
  ): Promise<CheckpointBuilder> {
245
338
  const stateReference = await fork.getStateReference();
246
339
  const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
247
340
 
248
341
  if (existingBlocks.length === 0) {
249
- return this.startCheckpoint(checkpointNumber, constants, l1ToL2Messages, previousCheckpointOutHashes, fork);
342
+ return this.startCheckpoint(
343
+ checkpointNumber,
344
+ constants,
345
+ feeAssetPriceModifier,
346
+ l1ToL2Messages,
347
+ previousCheckpointOutHashes,
348
+ fork,
349
+ bindings,
350
+ );
250
351
  }
251
352
 
252
- log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
353
+ this.log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
253
354
  checkpointNumber,
254
355
  msgCount: l1ToL2Messages.length,
255
356
  existingBlockCount: existingBlocks.length,
256
357
  initialStateReference: stateReference.toInspect(),
257
358
  initialArchiveRoot: bufferToHex(archiveTree.root),
258
359
  constants,
360
+ feeAssetPriceModifier,
259
361
  });
260
362
 
261
363
  const lightweightBuilder = await LightweightCheckpointBuilder.resumeCheckpoint(
262
364
  checkpointNumber,
263
365
  constants,
366
+ feeAssetPriceModifier,
264
367
  l1ToL2Messages,
265
368
  previousCheckpointOutHashes,
266
369
  fork,
267
370
  existingBlocks,
371
+ bindings,
268
372
  );
269
373
 
270
374
  return new CheckpointBuilder(
@@ -274,6 +378,8 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
274
378
  this.contractDataSource,
275
379
  this.dateProvider,
276
380
  this.telemetryClient,
381
+ bindings,
382
+ this.debugLogStore,
277
383
  );
278
384
  }
279
385
 
package/src/config.ts CHANGED
@@ -6,8 +6,8 @@ import {
6
6
  secretValueConfigHelper,
7
7
  } from '@aztec/foundation/config';
8
8
  import { EthAddress } from '@aztec/foundation/eth-address';
9
+ import { localSignerConfigMappings, validatorHASignerConfigMappings } from '@aztec/stdlib/ha-signing';
9
10
  import type { ValidatorClientConfig } from '@aztec/stdlib/interfaces/server';
10
- import { validatorHASignerConfigMappings } from '@aztec/validator-ha-signer/config';
11
11
 
12
12
  export type { ValidatorClientConfig };
13
13
 
@@ -65,16 +65,39 @@ export const validatorClientConfigMappings: ConfigMappingsType<ValidatorClientCo
65
65
  'Whether to run in fisherman mode: validates all proposals and attestations but does not broadcast attestations or participate in consensus.',
66
66
  ...booleanConfigHelper(false),
67
67
  },
68
- // TODO(palla/mbps): Change default to false once checkpoint validation is stable
69
68
  skipCheckpointProposalValidation: {
70
- description: 'Skip checkpoint proposal validation and always attest (default: true)',
71
- defaultValue: true,
69
+ description: 'Skip checkpoint proposal validation and always attest (default: false)',
70
+ defaultValue: false,
72
71
  },
73
- // TODO(palla/mbps): Change default to false once block sync is stable
74
72
  skipPushProposedBlocksToArchiver: {
75
- description: 'Skip pushing re-executed blocks to archiver (default: true)',
76
- defaultValue: true,
73
+ description: 'Skip pushing re-executed blocks to archiver (default: false)',
74
+ defaultValue: false,
75
+ },
76
+ attestToEquivocatedProposals: {
77
+ description: 'Agree to attest to equivocated checkpoint proposals (for testing purposes only)',
78
+ ...booleanConfigHelper(false),
79
+ },
80
+ validateMaxL2BlockGas: {
81
+ env: 'VALIDATOR_MAX_L2_BLOCK_GAS',
82
+ description: 'Maximum L2 block gas for validation. Proposals exceeding this limit are rejected.',
83
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
84
+ },
85
+ validateMaxDABlockGas: {
86
+ env: 'VALIDATOR_MAX_DA_BLOCK_GAS',
87
+ description: 'Maximum DA block gas for validation. Proposals exceeding this limit are rejected.',
88
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
89
+ },
90
+ validateMaxTxsPerBlock: {
91
+ env: 'VALIDATOR_MAX_TX_PER_BLOCK',
92
+ description: 'Maximum transactions per block for validation. Proposals exceeding this limit are rejected.',
93
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
94
+ },
95
+ validateMaxTxsPerCheckpoint: {
96
+ env: 'VALIDATOR_MAX_TX_PER_CHECKPOINT',
97
+ description: 'Maximum transactions per checkpoint for validation. Proposals exceeding this limit are rejected.',
98
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
77
99
  },
100
+ ...localSignerConfigMappings,
78
101
  ...validatorHASignerConfigMappings,
79
102
  };
80
103
 
@@ -95,6 +95,7 @@ export class ValidationService {
95
95
  public createCheckpointProposal(
96
96
  checkpointHeader: CheckpointHeader,
97
97
  archive: Fr,
98
+ feeAssetPriceModifier: bigint,
98
99
  lastBlockInfo: CreateCheckpointProposalLastBlockData | undefined,
99
100
  proposerAttesterAddress: EthAddress | undefined,
100
101
  options: CheckpointProposalOptions,
@@ -119,7 +120,13 @@ export class ValidationService {
119
120
  txs: options.publishFullTxs ? lastBlockInfo.txs : undefined,
120
121
  };
121
122
 
122
- return CheckpointProposal.createProposalFromSigner(checkpointHeader, archive, lastBlock, payloadSigner);
123
+ return CheckpointProposal.createProposalFromSigner(
124
+ checkpointHeader,
125
+ archive,
126
+ feeAssetPriceModifier,
127
+ lastBlock,
128
+ payloadSigner,
129
+ );
123
130
  }
124
131
 
125
132
  /**
@@ -137,22 +144,16 @@ export class ValidationService {
137
144
  attestors: EthAddress[],
138
145
  ): Promise<CheckpointAttestation[]> {
139
146
  // Create the attestation payload from the checkpoint proposal
140
- const payload = new ConsensusPayload(proposal.checkpointHeader, proposal.archive);
147
+ const payload = new ConsensusPayload(proposal.checkpointHeader, proposal.archive, proposal.feeAssetPriceModifier);
141
148
  const buf = Buffer32.fromBuffer(
142
149
  keccak256(payload.getPayloadToSign(SignatureDomainSeparator.checkpointAttestation)),
143
150
  );
144
151
 
145
152
  // TODO(spy/ha): Use checkpointNumber instead of blockNumber once CheckpointHeader includes it.
146
- // Currently using lastBlock.blockNumber as a proxy for checkpoint identification in HA signing.
153
+ // CheckpointProposalCore doesn't have lastBlock info, so use 0 as a proxy.
147
154
  // blockNumber is NOT used for the primary key so it's safe to use here.
148
155
  // See CheckpointHeader TODO and SigningContext types documentation.
149
- let blockNumber: BlockNumber;
150
- try {
151
- blockNumber = proposal.blockNumber;
152
- } catch {
153
- // Checkpoint proposal may not have lastBlock, use 0 as fallback
154
- blockNumber = BlockNumber(0);
155
- }
156
+ const blockNumber = BlockNumber(0);
156
157
  const context: SigningContext = {
157
158
  slot: proposal.slotNumber,
158
159
  blockNumber,
package/src/factory.ts CHANGED
@@ -29,6 +29,7 @@ export function createBlockProposalHandler(
29
29
  const metrics = new ValidatorMetrics(deps.telemetry);
30
30
  const blockProposalValidator = new BlockProposalValidator(deps.epochCache, {
31
31
  txsPermitted: !config.disableTransactions,
32
+ maxTxsPerBlock: config.validateMaxTxsPerBlock,
32
33
  });
33
34
  return new BlockProposalHandler(
34
35
  deps.checkpointsBuilder,
package/src/index.ts CHANGED
@@ -4,4 +4,3 @@ export * from './config.js';
4
4
  export * from './factory.js';
5
5
  export * from './validator.js';
6
6
  export * from './key_store/index.js';
7
- export * from './tx_validator/index.js';
@@ -256,8 +256,8 @@ export class HAKeyStore implements ExtendedValidatorKeyStore {
256
256
  /**
257
257
  * Start the high-availability key store
258
258
  */
259
- public start(): Promise<void> {
260
- return Promise.resolve(this.haSigner.start());
259
+ public async start() {
260
+ await this.haSigner.start();
261
261
  }
262
262
 
263
263
  /**
package/src/metrics.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { EpochNumber } from '@aztec/foundation/branded-types';
2
+ import type { EthAddress } from '@aztec/foundation/eth-address';
1
3
  import type { BlockProposal } from '@aztec/stdlib/p2p';
2
4
  import {
3
5
  Attributes,
@@ -6,13 +8,18 @@ import {
6
8
  Metrics,
7
9
  type TelemetryClient,
8
10
  type UpDownCounter,
11
+ createUpDownCounterWithDefault,
9
12
  } from '@aztec/telemetry-client';
10
13
 
14
+ import type { BlockProposalValidationFailureReason } from './block_proposal_handler.js';
15
+
11
16
  export class ValidatorMetrics {
12
17
  private failedReexecutionCounter: UpDownCounter;
13
18
  private successfulAttestationsCount: UpDownCounter;
14
19
  private failedAttestationsBadProposalCount: UpDownCounter;
15
20
  private failedAttestationsNodeIssueCount: UpDownCounter;
21
+ private currentEpoch: Gauge;
22
+ private attestedEpochCount: UpDownCounter;
16
23
 
17
24
  private reexMana: Histogram;
18
25
  private reexTx: Histogram;
@@ -21,18 +28,50 @@ export class ValidatorMetrics {
21
28
  constructor(telemetryClient: TelemetryClient) {
22
29
  const meter = telemetryClient.getMeter('Validator');
23
30
 
24
- this.failedReexecutionCounter = meter.createUpDownCounter(Metrics.VALIDATOR_FAILED_REEXECUTION_COUNT);
31
+ this.failedReexecutionCounter = createUpDownCounterWithDefault(meter, Metrics.VALIDATOR_FAILED_REEXECUTION_COUNT, {
32
+ [Attributes.STATUS]: ['failed'],
33
+ });
25
34
 
26
- this.successfulAttestationsCount = meter.createUpDownCounter(Metrics.VALIDATOR_ATTESTATION_SUCCESS_COUNT);
35
+ this.successfulAttestationsCount = createUpDownCounterWithDefault(
36
+ meter,
37
+ Metrics.VALIDATOR_ATTESTATION_SUCCESS_COUNT,
38
+ );
27
39
 
28
- this.failedAttestationsBadProposalCount = meter.createUpDownCounter(
40
+ this.failedAttestationsBadProposalCount = createUpDownCounterWithDefault(
41
+ meter,
29
42
  Metrics.VALIDATOR_ATTESTATION_FAILED_BAD_PROPOSAL_COUNT,
43
+ {
44
+ [Attributes.ERROR_TYPE]: [
45
+ 'invalid_proposal',
46
+ 'state_mismatch',
47
+ 'failed_txs',
48
+ 'in_hash_mismatch',
49
+ 'parent_block_wrong_slot',
50
+ ],
51
+ [Attributes.IS_COMMITTEE_MEMBER]: [true, false],
52
+ },
30
53
  );
31
54
 
32
- this.failedAttestationsNodeIssueCount = meter.createUpDownCounter(
55
+ this.failedAttestationsNodeIssueCount = createUpDownCounterWithDefault(
56
+ meter,
33
57
  Metrics.VALIDATOR_ATTESTATION_FAILED_NODE_ISSUE_COUNT,
58
+ {
59
+ [Attributes.ERROR_TYPE]: [
60
+ 'parent_block_not_found',
61
+ 'global_variables_mismatch',
62
+ 'block_number_already_exists',
63
+ 'txs_not_available',
64
+ 'timeout',
65
+ 'unknown_error',
66
+ ],
67
+ [Attributes.IS_COMMITTEE_MEMBER]: [true, false],
68
+ },
34
69
  );
35
70
 
71
+ this.currentEpoch = meter.createGauge(Metrics.VALIDATOR_CURRENT_EPOCH);
72
+
73
+ this.attestedEpochCount = createUpDownCounterWithDefault(meter, Metrics.VALIDATOR_ATTESTED_EPOCH_COUNT);
74
+
36
75
  this.reexMana = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_MANA);
37
76
 
38
77
  this.reexTx = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_TX_COUNT);
@@ -58,17 +97,35 @@ export class ValidatorMetrics {
58
97
  this.successfulAttestationsCount.add(num);
59
98
  }
60
99
 
61
- public incFailedAttestationsBadProposal(num: number, reason: string, inCommittee: boolean) {
100
+ public incFailedAttestationsBadProposal(
101
+ num: number,
102
+ reason: BlockProposalValidationFailureReason,
103
+ inCommittee: boolean,
104
+ ) {
62
105
  this.failedAttestationsBadProposalCount.add(num, {
63
106
  [Attributes.ERROR_TYPE]: reason,
64
107
  [Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
65
108
  });
66
109
  }
67
110
 
68
- public incFailedAttestationsNodeIssue(num: number, reason: string, inCommittee: boolean) {
111
+ public incFailedAttestationsNodeIssue(
112
+ num: number,
113
+ reason: BlockProposalValidationFailureReason,
114
+ inCommittee: boolean,
115
+ ) {
69
116
  this.failedAttestationsNodeIssueCount.add(num, {
70
117
  [Attributes.ERROR_TYPE]: reason,
71
118
  [Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
72
119
  });
73
120
  }
121
+
122
+ /** Update the gauge tracking the current epoch number (proxy for total epochs elapsed). */
123
+ public setCurrentEpoch(epoch: EpochNumber) {
124
+ this.currentEpoch.record(Number(epoch));
125
+ }
126
+
127
+ /** Increment the count of epochs in which the given attester submitted at least one attestation. */
128
+ public incAttestedEpochCount(attester: EthAddress) {
129
+ this.attestedEpochCount.add(1, { [Attributes.ATTESTER_ADDRESS]: attester.toString() });
130
+ }
74
131
  }