@aztec/validator-client 0.0.1-commit.18ccd8f0 → 0.0.1-commit.1a421b1a1

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 +63 -18
  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 +122 -62
  5. package/dest/checkpoint_builder.d.ts +17 -11
  6. package/dest/checkpoint_builder.d.ts.map +1 -1
  7. package/dest/checkpoint_builder.js +93 -32
  8. package/dest/config.d.ts +1 -1
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +26 -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 +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 +3 -3
  23. package/dest/metrics.d.ts +9 -1
  24. package/dest/metrics.d.ts.map +1 -1
  25. package/dest/metrics.js +12 -0
  26. package/dest/validator.d.ts +35 -8
  27. package/dest/validator.d.ts.map +1 -1
  28. package/dest/validator.js +196 -34
  29. package/package.json +19 -19
  30. package/src/block_proposal_handler.ts +149 -80
  31. package/src/checkpoint_builder.ts +105 -25
  32. package/src/config.ts +26 -1
  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 +3 -3
  37. package/src/metrics.ts +18 -0
  38. package/src/validator.ts +246 -40
  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 -19
  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 -154
@@ -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
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,
@@ -24,23 +26,18 @@ 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
- /** Result of building a block within a checkpoint. Extends the base interface with timer. */
40
- export interface BuildBlockInCheckpointResultWithTimer extends BuildBlockInCheckpointResult {
41
- blockBuildingTimer: Timer;
42
- }
43
-
44
41
  /**
45
42
  * Builder for a single checkpoint. Handles building blocks within the checkpoint
46
43
  * and completing it.
@@ -56,6 +53,7 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
56
53
  private dateProvider: DateProvider,
57
54
  private telemetryClient: TelemetryClient,
58
55
  bindings?: LoggerBindings,
56
+ private debugLogStore: DebugLogStore = new NullDebugLogStore(),
59
57
  ) {
60
58
  this.log = createLogger('checkpoint-builder', {
61
59
  ...bindings,
@@ -69,14 +67,14 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
69
67
 
70
68
  /**
71
69
  * Builds a single block within this checkpoint.
70
+ * Automatically caps gas and blob field limits based on checkpoint-level budgets and prior blocks.
72
71
  */
73
72
  async buildBlock(
74
73
  pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
75
74
  blockNumber: BlockNumber,
76
75
  timestamp: bigint,
77
76
  opts: PublicProcessorLimits & { expectedEndState?: StateReference } = {},
78
- ): Promise<BuildBlockInCheckpointResultWithTimer> {
79
- const blockBuildingTimer = new Timer();
77
+ ): Promise<BuildBlockInCheckpointResult> {
80
78
  const slot = this.checkpointBuilder.constants.slotNumber;
81
79
 
82
80
  this.log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
@@ -99,30 +97,40 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
99
97
  });
100
98
  const { processor, validator } = await this.makeBlockBuilderDeps(globalVariables, this.fork);
101
99
 
102
- const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs, _, usedTxBlobFields]] = await elapsed(() =>
103
- 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),
104
108
  );
105
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
+
106
116
  // Add block to checkpoint
107
- const block = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
117
+ const { block } = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
108
118
  expectedEndState: opts.expectedEndState,
109
119
  });
110
120
 
111
- // How much public gas was processed
112
- 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
+ });
113
126
 
114
- const res = {
127
+ return {
115
128
  block,
116
- publicGas,
117
129
  publicProcessorDuration,
118
130
  numTxs: processedTxs.length,
119
131
  failedTxs,
120
- blockBuildingTimer,
121
132
  usedTxs,
122
- usedTxBlobFields,
123
133
  };
124
- this.log.debug('Built block within checkpoint', res.block.header);
125
- return res;
126
134
  }
127
135
 
128
136
  /** Completes the checkpoint and returns it. */
@@ -143,11 +151,71 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
143
151
  return this.checkpointBuilder.clone().completeCheckpoint();
144
152
  }
145
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
+
146
209
  protected async makeBlockBuilderDeps(globalVariables: GlobalVariables, fork: MerkleTreeWriteOperations) {
147
- const txPublicSetupAllowList = this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
210
+ const txPublicSetupAllowList = [
211
+ ...(await getDefaultAllowedSetupFunctions()),
212
+ ...(this.config.txPublicSetupAllowListExtend ?? []),
213
+ ];
148
214
  const contractsDB = new PublicContractsDB(this.contractDataSource, this.log.getBindings());
149
215
  const guardedFork = new GuardedMerkleTreeOperations(fork);
150
216
 
217
+ const collectDebugLogs = this.debugLogStore.isEnabled;
218
+
151
219
  const bindings = this.log.getBindings();
152
220
  const publicTxSimulator = createPublicTxSimulatorForBlockBuilding(
153
221
  guardedFork,
@@ -155,6 +223,7 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
155
223
  globalVariables,
156
224
  this.telemetryClient,
157
225
  bindings,
226
+ collectDebugLogs,
158
227
  );
159
228
 
160
229
  const processor = new PublicProcessor(
@@ -166,9 +235,10 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
166
235
  this.telemetryClient,
167
236
  createLogger('simulator:public-processor', bindings),
168
237
  this.config,
238
+ this.debugLogStore,
169
239
  );
170
240
 
171
- const validator = createValidatorForBlockBuilding(
241
+ const validator = createTxValidatorForBlockBuilding(
172
242
  fork,
173
243
  this.contractDataSource,
174
244
  globalVariables,
@@ -193,6 +263,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
193
263
  private contractDataSource: ContractDataSource,
194
264
  private dateProvider: DateProvider,
195
265
  private telemetryClient: TelemetryClient = getTelemetryClient(),
266
+ private debugLogStore: DebugLogStore = new NullDebugLogStore(),
196
267
  ) {
197
268
  this.log = createLogger('checkpoint-builder');
198
269
  }
@@ -211,6 +282,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
211
282
  async startCheckpoint(
212
283
  checkpointNumber: CheckpointNumber,
213
284
  constants: CheckpointGlobalVariables,
285
+ feeAssetPriceModifier: bigint,
214
286
  l1ToL2Messages: Fr[],
215
287
  previousCheckpointOutHashes: Fr[],
216
288
  fork: MerkleTreeWriteOperations,
@@ -225,6 +297,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
225
297
  initialStateReference: stateReference.toInspect(),
226
298
  initialArchiveRoot: bufferToHex(archiveTree.root),
227
299
  constants,
300
+ feeAssetPriceModifier,
228
301
  });
229
302
 
230
303
  const lightweightBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
@@ -234,6 +307,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
234
307
  previousCheckpointOutHashes,
235
308
  fork,
236
309
  bindings,
310
+ feeAssetPriceModifier,
237
311
  );
238
312
 
239
313
  return new CheckpointBuilder(
@@ -244,6 +318,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
244
318
  this.dateProvider,
245
319
  this.telemetryClient,
246
320
  bindings,
321
+ this.debugLogStore,
247
322
  );
248
323
  }
249
324
 
@@ -253,6 +328,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
253
328
  async openCheckpoint(
254
329
  checkpointNumber: CheckpointNumber,
255
330
  constants: CheckpointGlobalVariables,
331
+ feeAssetPriceModifier: bigint,
256
332
  l1ToL2Messages: Fr[],
257
333
  previousCheckpointOutHashes: Fr[],
258
334
  fork: MerkleTreeWriteOperations,
@@ -266,6 +342,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
266
342
  return this.startCheckpoint(
267
343
  checkpointNumber,
268
344
  constants,
345
+ feeAssetPriceModifier,
269
346
  l1ToL2Messages,
270
347
  previousCheckpointOutHashes,
271
348
  fork,
@@ -280,11 +357,13 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
280
357
  initialStateReference: stateReference.toInspect(),
281
358
  initialArchiveRoot: bufferToHex(archiveTree.root),
282
359
  constants,
360
+ feeAssetPriceModifier,
283
361
  });
284
362
 
285
363
  const lightweightBuilder = await LightweightCheckpointBuilder.resumeCheckpoint(
286
364
  checkpointNumber,
287
365
  constants,
366
+ feeAssetPriceModifier,
288
367
  l1ToL2Messages,
289
368
  previousCheckpointOutHashes,
290
369
  fork,
@@ -300,6 +379,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
300
379
  this.dateProvider,
301
380
  this.telemetryClient,
302
381
  bindings,
382
+ this.debugLogStore,
303
383
  );
304
384
  }
305
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
 
@@ -73,6 +73,31 @@ export const validatorClientConfigMappings: ConfigMappingsType<ValidatorClientCo
73
73
  description: 'Skip pushing re-executed blocks to archiver (default: false)',
74
74
  defaultValue: false,
75
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),
99
+ },
100
+ ...localSignerConfigMappings,
76
101
  ...validatorHASignerConfigMappings,
77
102
  };
78
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';
@@ -240,7 +240,7 @@ export class HAKeyStore implements ExtendedValidatorKeyStore {
240
240
  }
241
241
 
242
242
  if (error instanceof SlashingProtectionError) {
243
- this.log.warn(`Duty already signed by another node with different payload`, {
243
+ this.log.info(`Duty already signed by another node with different payload`, {
244
244
  dutyType: context.dutyType,
245
245
  slot: context.slot,
246
246
  existingMessageHash: error.existingMessageHash,
@@ -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,
@@ -16,6 +18,8 @@ export class ValidatorMetrics {
16
18
  private successfulAttestationsCount: UpDownCounter;
17
19
  private failedAttestationsBadProposalCount: UpDownCounter;
18
20
  private failedAttestationsNodeIssueCount: UpDownCounter;
21
+ private currentEpoch: Gauge;
22
+ private attestedEpochCount: UpDownCounter;
19
23
 
20
24
  private reexMana: Histogram;
21
25
  private reexTx: Histogram;
@@ -64,6 +68,10 @@ export class ValidatorMetrics {
64
68
  },
65
69
  );
66
70
 
71
+ this.currentEpoch = meter.createGauge(Metrics.VALIDATOR_CURRENT_EPOCH);
72
+
73
+ this.attestedEpochCount = createUpDownCounterWithDefault(meter, Metrics.VALIDATOR_ATTESTED_EPOCH_COUNT);
74
+
67
75
  this.reexMana = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_MANA);
68
76
 
69
77
  this.reexTx = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_TX_COUNT);
@@ -110,4 +118,14 @@ export class ValidatorMetrics {
110
118
  [Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
111
119
  });
112
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
+ }
113
131
  }