@aztec/validator-client 3.0.0-devnet.2 → 3.0.0-devnet.2-patch.1

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 (36) hide show
  1. package/dest/block_proposal_handler.d.ts +7 -6
  2. package/dest/block_proposal_handler.d.ts.map +1 -1
  3. package/dest/block_proposal_handler.js +17 -13
  4. package/dest/config.d.ts +1 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +5 -0
  7. package/dest/duties/validation_service.d.ts +4 -4
  8. package/dest/duties/validation_service.d.ts.map +1 -1
  9. package/dest/duties/validation_service.js +7 -8
  10. package/dest/factory.d.ts +1 -1
  11. package/dest/index.d.ts +1 -1
  12. package/dest/key_store/index.d.ts +1 -1
  13. package/dest/key_store/interface.d.ts +1 -1
  14. package/dest/key_store/local_key_store.d.ts +1 -1
  15. package/dest/key_store/local_key_store.d.ts.map +1 -1
  16. package/dest/key_store/local_key_store.js +1 -1
  17. package/dest/key_store/node_keystore_adapter.d.ts +1 -1
  18. package/dest/key_store/node_keystore_adapter.d.ts.map +1 -1
  19. package/dest/key_store/web3signer_key_store.d.ts +1 -7
  20. package/dest/key_store/web3signer_key_store.d.ts.map +1 -1
  21. package/dest/key_store/web3signer_key_store.js +7 -8
  22. package/dest/metrics.d.ts +3 -3
  23. package/dest/metrics.d.ts.map +1 -1
  24. package/dest/metrics.js +6 -4
  25. package/dest/validator.d.ts +7 -6
  26. package/dest/validator.d.ts.map +1 -1
  27. package/dest/validator.js +72 -38
  28. package/package.json +14 -14
  29. package/src/block_proposal_handler.ts +26 -23
  30. package/src/config.ts +6 -0
  31. package/src/duties/validation_service.ts +7 -10
  32. package/src/key_store/local_key_store.ts +1 -1
  33. package/src/key_store/node_keystore_adapter.ts +1 -1
  34. package/src/key_store/web3signer_key_store.ts +7 -10
  35. package/src/metrics.ts +4 -2
  36. package/src/validator.ts +87 -52
package/dest/validator.js CHANGED
@@ -28,10 +28,10 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
28
28
  blockProposalHandler;
29
29
  config;
30
30
  dateProvider;
31
- log;
32
31
  tracer;
33
32
  validationService;
34
33
  metrics;
34
+ log;
35
35
  // Whether it has already registered handlers on the p2p client
36
36
  hasRegisteredHandlers;
37
37
  // Used to check if we are sending the same proposal twice
@@ -40,12 +40,14 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
40
40
  epochCacheUpdateLoop;
41
41
  proposersOfInvalidBlocks;
42
42
  constructor(keyStore, epochCache, p2pClient, blockProposalHandler, config, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
43
- super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockProposalHandler = blockProposalHandler, this.config = config, this.dateProvider = dateProvider, this.log = log, this.hasRegisteredHandlers = false, this.proposersOfInvalidBlocks = new Set();
43
+ super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockProposalHandler = blockProposalHandler, this.config = config, this.dateProvider = dateProvider, this.hasRegisteredHandlers = false, this.proposersOfInvalidBlocks = new Set();
44
+ // Create child logger with fisherman prefix if in fisherman mode
45
+ this.log = config.fishermanMode ? log.createChild('[FISHERMAN]') : log;
44
46
  this.tracer = telemetry.getTracer('Validator');
45
47
  this.metrics = new ValidatorMetrics(telemetry);
46
- this.validationService = new ValidationService(keyStore, log.createChild('validation-service'));
48
+ this.validationService = new ValidationService(keyStore, this.log.createChild('validation-service'));
47
49
  // Refresh epoch cache every second to trigger alert if participation in committee changes
48
- this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), log, 1000);
50
+ this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), this.log, 1000);
49
51
  const myAddresses = this.getValidatorAddresses();
50
52
  this.log.verbose(`Initialized validator with addresses: ${myAddresses.map((a)=>a.toString()).join(', ')}`);
51
53
  }
@@ -137,10 +139,9 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
137
139
  await this.registerHandlers();
138
140
  const myAddresses = this.getValidatorAddresses();
139
141
  const inCommittee = await this.epochCache.filterInCommittee('now', myAddresses);
142
+ this.log.info(`Started validator with addresses: ${myAddresses.map((a)=>a.toString()).join(', ')}`);
140
143
  if (inCommittee.length > 0) {
141
- this.log.info(`Started validator with addresses in current validator committee: ${inCommittee.map((a)=>a.toString()).join(', ')}`);
142
- } else {
143
- this.log.info(`Started validator with addresses: ${myAddresses.map((a)=>a.toString()).join(', ')}`);
144
+ this.log.info(`Addresses in current validator committee: ${inCommittee.map((a)=>a.toString()).join(', ')}`);
144
145
  }
145
146
  this.epochCacheUpdateLoop.start();
146
147
  return Promise.resolve();
@@ -160,7 +161,7 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
160
161
  }
161
162
  }
162
163
  async attestToProposal(proposal, proposalSender) {
163
- const slotNumber = proposal.slotNumber.toBigInt();
164
+ const slotNumber = proposal.slotNumber;
164
165
  const proposer = proposal.getSender();
165
166
  // Reject proposals with invalid signatures
166
167
  if (!proposer) {
@@ -176,32 +177,31 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
176
177
  };
177
178
  this.log.info(`Received proposal for slot ${slotNumber}`, {
178
179
  ...proposalInfo,
179
- txHashes: proposal.txHashes.map((t)=>t.toString())
180
+ txHashes: proposal.txHashes.map((t)=>t.toString()),
181
+ fishermanMode: this.config.fishermanMode || false
180
182
  });
181
183
  // Reexecute txs if we are part of the committee so we can attest, or if slashing is enabled so we can slash
182
184
  // invalid proposals even when not in the committee, or if we are configured to always reexecute for monitoring purposes.
183
- const { validatorReexecute, slashBroadcastedInvalidBlockPenalty, alwaysReexecuteBlockProposals } = this.config;
184
- const shouldReexecute = slashBroadcastedInvalidBlockPenalty > 0n && validatorReexecute || partOfCommittee && validatorReexecute || alwaysReexecuteBlockProposals;
185
+ // In fisherman mode, we always reexecute to validate proposals.
186
+ const { validatorReexecute, slashBroadcastedInvalidBlockPenalty, alwaysReexecuteBlockProposals, fishermanMode } = this.config;
187
+ const shouldReexecute = fishermanMode || slashBroadcastedInvalidBlockPenalty > 0n && validatorReexecute || partOfCommittee && validatorReexecute || alwaysReexecuteBlockProposals;
185
188
  const validationResult = await this.blockProposalHandler.handleBlockProposal(proposal, proposalSender, !!shouldReexecute);
186
189
  if (!validationResult.isValid) {
187
190
  this.log.warn(`Proposal validation failed: ${validationResult.reason}`, proposalInfo);
188
- // Only track attestation failure metrics if we're actually in the committee
189
- if (partOfCommittee) {
190
- const reason = validationResult.reason || 'unknown';
191
- // Classify failure reason: bad proposal vs node issue
192
- const badProposalReasons = [
193
- 'invalid_proposal',
194
- 'state_mismatch',
195
- 'failed_txs',
196
- 'in_hash_mismatch',
197
- 'parent_block_wrong_slot'
198
- ];
199
- if (badProposalReasons.includes(reason)) {
200
- this.metrics.incFailedAttestationsBadProposal(1, reason);
201
- } else {
202
- // Node issues: parent_block_not_found, block_number_already_exists, txs_not_available, timeout, unknown_error
203
- this.metrics.incFailedAttestationsNodeIssue(1, reason);
204
- }
191
+ const reason = validationResult.reason || 'unknown';
192
+ // Classify failure reason: bad proposal vs node issue
193
+ const badProposalReasons = [
194
+ 'invalid_proposal',
195
+ 'state_mismatch',
196
+ 'failed_txs',
197
+ 'in_hash_mismatch',
198
+ 'parent_block_wrong_slot'
199
+ ];
200
+ if (badProposalReasons.includes(reason)) {
201
+ this.metrics.incFailedAttestationsBadProposal(1, reason, partOfCommittee);
202
+ } else {
203
+ // Node issues so we can't attest
204
+ this.metrics.incFailedAttestationsNodeIssue(1, reason, partOfCommittee);
205
205
  }
206
206
  // Slash invalid block proposals (can happen even when not in committee)
207
207
  if (validationResult.reason && SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT.includes(validationResult.reason) && slashBroadcastedInvalidBlockPenalty > 0n) {
@@ -211,15 +211,42 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
211
211
  return undefined;
212
212
  }
213
213
  // Check that I have any address in current committee before attesting
214
- if (!partOfCommittee) {
214
+ // In fisherman mode, we still create attestations for validation even if not in committee
215
+ if (!partOfCommittee && !this.config.fishermanMode) {
215
216
  this.log.verbose(`No validator in the current committee, skipping attestation`, proposalInfo);
216
217
  return undefined;
217
218
  }
218
219
  // Provided all of the above checks pass, we can attest to the proposal
219
- this.log.info(`Attesting to proposal for slot ${slotNumber}`, proposalInfo);
220
+ this.log.info(`${partOfCommittee ? 'Attesting to' : 'Validated'} proposal for slot ${slotNumber}`, {
221
+ ...proposalInfo,
222
+ inCommittee: partOfCommittee,
223
+ fishermanMode: this.config.fishermanMode || false
224
+ });
220
225
  this.metrics.incSuccessfulAttestations(inCommittee.length);
221
226
  // If the above function does not throw an error, then we can attest to the proposal
222
- return this.createBlockAttestationsFromProposal(proposal, inCommittee);
227
+ // Determine which validators should attest
228
+ let attestors;
229
+ if (partOfCommittee) {
230
+ attestors = inCommittee;
231
+ } else if (this.config.fishermanMode) {
232
+ // In fisherman mode, create attestations for validation purposes even if not in committee. These won't be broadcast.
233
+ attestors = this.getValidatorAddresses();
234
+ } else {
235
+ attestors = [];
236
+ }
237
+ // Only create attestations if we have attestors
238
+ if (attestors.length === 0) {
239
+ return undefined;
240
+ }
241
+ if (this.config.fishermanMode) {
242
+ // bail out early and don't save attestations to the pool in fisherman mode
243
+ this.log.info(`Creating attestations for proposal for slot ${slotNumber}`, {
244
+ ...proposalInfo,
245
+ attestors: attestors.map((a)=>a.toString())
246
+ });
247
+ return undefined;
248
+ }
249
+ return this.createBlockAttestationsFromProposal(proposal, attestors);
223
250
  }
224
251
  slashInvalidBlock(proposal) {
225
252
  const proposer = proposal.getSender();
@@ -239,16 +266,16 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
239
266
  validator: proposer,
240
267
  amount: this.config.slashBroadcastedInvalidBlockPenalty,
241
268
  offenseType: OffenseType.BROADCASTED_INVALID_BLOCK_PROPOSAL,
242
- epochOrSlot: proposal.slotNumber.toBigInt()
269
+ epochOrSlot: BigInt(proposal.slotNumber)
243
270
  }
244
271
  ]);
245
272
  }
246
- async createBlockProposal(blockNumber, header, archive, stateReference, txs, proposerAddress, options) {
247
- if (this.previousProposal?.slotNumber.equals(header.slotNumber)) {
273
+ async createBlockProposal(blockNumber, header, archive, txs, proposerAddress, options) {
274
+ if (this.previousProposal?.slotNumber === header.slotNumber) {
248
275
  this.log.verbose(`Already made a proposal for the same slot, skipping proposal`);
249
276
  return Promise.resolve(undefined);
250
277
  }
251
- const newProposal = await this.validationService.createBlockProposal(header, archive, stateReference, txs, proposerAddress, {
278
+ const newProposal = await this.validationService.createBlockProposal(header, archive, txs, proposerAddress, {
252
279
  ...options,
253
280
  broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal
254
281
  });
@@ -262,16 +289,23 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
262
289
  return await this.validationService.signAttestationsAndSigners(attestationsAndSigners, proposer);
263
290
  }
264
291
  async collectOwnAttestations(proposal) {
265
- const slot = proposal.payload.header.slotNumber.toBigInt();
292
+ const slot = proposal.payload.header.slotNumber;
266
293
  const inCommittee = await this.epochCache.filterInCommittee(slot, this.getValidatorAddresses());
267
294
  this.log.debug(`Collecting ${inCommittee.length} self-attestations for slot ${slot}`, {
268
295
  inCommittee
269
296
  });
270
- return this.createBlockAttestationsFromProposal(proposal, inCommittee);
297
+ const attestations = await this.createBlockAttestationsFromProposal(proposal, inCommittee);
298
+ // We broadcast our own attestations to our peers so, in case our block does not get mined on L1,
299
+ // other nodes can see that our validators did attest to this block proposal, and do not slash us
300
+ // due to inactivity for missed attestations.
301
+ void this.p2pClient.broadcastAttestations(attestations).catch((err)=>{
302
+ this.log.error(`Failed to broadcast self-attestations for slot ${slot}`, err);
303
+ });
304
+ return attestations;
271
305
  }
272
306
  async collectAttestations(proposal, required, deadline) {
273
307
  // Wait and poll the p2pClient's attestation pool for this block until we have enough attestations
274
- const slot = proposal.payload.header.slotNumber.toBigInt();
308
+ const slot = proposal.payload.header.slotNumber;
275
309
  this.log.debug(`Collecting ${required} attestations for slot ${slot} with deadline ${deadline.toISOString()}`);
276
310
  if (+deadline < this.dateProvider.now()) {
277
311
  this.log.error(`Deadline ${deadline.toISOString()} for collecting ${required} attestations for slot ${slot} is in the past`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/validator-client",
3
- "version": "3.0.0-devnet.2",
3
+ "version": "3.0.0-devnet.2-patch.1",
4
4
  "main": "dest/index.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -18,8 +18,8 @@
18
18
  },
19
19
  "scripts": {
20
20
  "start": "node --no-warnings ./dest/bin",
21
- "build": "yarn clean && tsc -b",
22
- "build:dev": "tsc -b --watch",
21
+ "build": "yarn clean && ../scripts/tsc.sh",
22
+ "build:dev": "../scripts/tsc.sh --watch",
23
23
  "clean": "rm -rf ./dest .tsbuildinfo",
24
24
  "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
25
25
  },
@@ -64,25 +64,25 @@
64
64
  ]
65
65
  },
66
66
  "dependencies": {
67
- "@aztec/constants": "3.0.0-devnet.2",
68
- "@aztec/epoch-cache": "3.0.0-devnet.2",
69
- "@aztec/ethereum": "3.0.0-devnet.2",
70
- "@aztec/foundation": "3.0.0-devnet.2",
71
- "@aztec/node-keystore": "3.0.0-devnet.2",
72
- "@aztec/p2p": "3.0.0-devnet.2",
73
- "@aztec/prover-client": "3.0.0-devnet.2",
74
- "@aztec/slasher": "3.0.0-devnet.2",
75
- "@aztec/stdlib": "3.0.0-devnet.2",
76
- "@aztec/telemetry-client": "3.0.0-devnet.2",
67
+ "@aztec/constants": "3.0.0-devnet.2-patch.1",
68
+ "@aztec/epoch-cache": "3.0.0-devnet.2-patch.1",
69
+ "@aztec/ethereum": "3.0.0-devnet.2-patch.1",
70
+ "@aztec/foundation": "3.0.0-devnet.2-patch.1",
71
+ "@aztec/node-keystore": "3.0.0-devnet.2-patch.1",
72
+ "@aztec/p2p": "3.0.0-devnet.2-patch.1",
73
+ "@aztec/slasher": "3.0.0-devnet.2-patch.1",
74
+ "@aztec/stdlib": "3.0.0-devnet.2-patch.1",
75
+ "@aztec/telemetry-client": "3.0.0-devnet.2-patch.1",
77
76
  "koa": "^2.16.1",
78
77
  "koa-router": "^13.1.1",
79
78
  "tslib": "^2.4.0",
80
- "viem": "npm:@spalladino/viem@2.38.2-eip7594.0"
79
+ "viem": "npm:@aztec/viem@2.38.2"
81
80
  },
82
81
  "devDependencies": {
83
82
  "@jest/globals": "^30.0.0",
84
83
  "@types/jest": "^30.0.0",
85
84
  "@types/node": "^22.15.17",
85
+ "@typescript/native-preview": "7.0.0-dev.20251126.1",
86
86
  "jest": "^30.0.0",
87
87
  "jest-mock-extended": "^4.0.0",
88
88
  "ts-node": "^10.9.1",
@@ -1,17 +1,17 @@
1
1
  import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
+ import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
3
+ import { Fr } from '@aztec/foundation/curves/bn254';
2
4
  import { TimeoutError } from '@aztec/foundation/error';
3
- import { Fr } from '@aztec/foundation/fields';
4
5
  import { createLogger } from '@aztec/foundation/log';
5
6
  import { retryUntil } from '@aztec/foundation/retry';
6
7
  import { DateProvider, Timer } from '@aztec/foundation/timer';
7
8
  import type { P2P, PeerId } from '@aztec/p2p';
8
9
  import { TxProvider } from '@aztec/p2p';
9
10
  import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
10
- import { computeInHashFromL1ToL2Messages } from '@aztec/prover-client/helpers';
11
11
  import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
12
12
  import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
13
13
  import type { IFullNodeBlockBuilder, ValidatorClientFullConfig } from '@aztec/stdlib/interfaces/server';
14
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
14
+ import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
15
15
  import { type BlockProposal, ConsensusPayload } from '@aztec/stdlib/p2p';
16
16
  import { BlockHeader, type FailedTx, GlobalVariables, type Tx } from '@aztec/stdlib/tx';
17
17
  import {
@@ -45,14 +45,14 @@ type ReexecuteTransactionsResult = {
45
45
 
46
46
  export type BlockProposalValidationSuccessResult = {
47
47
  isValid: true;
48
- blockNumber: number;
48
+ blockNumber: BlockNumber;
49
49
  reexecutionResult?: ReexecuteTransactionsResult;
50
50
  };
51
51
 
52
52
  export type BlockProposalValidationFailureResult = {
53
53
  isValid: false;
54
54
  reason: BlockProposalValidationFailureReason;
55
- blockNumber?: number;
55
+ blockNumber?: BlockNumber;
56
56
  reexecutionResult?: ReexecuteTransactionsResult;
57
57
  };
58
58
 
@@ -73,6 +73,9 @@ export class BlockProposalHandler {
73
73
  telemetry: TelemetryClient = getTelemetryClient(),
74
74
  private log = createLogger('validator:block-proposal-handler'),
75
75
  ) {
76
+ if (config.fishermanMode) {
77
+ this.log = this.log.createChild('[FISHERMAN]');
78
+ }
76
79
  this.tracer = telemetry.getTracer('BlockProposalHandler');
77
80
  }
78
81
 
@@ -81,14 +84,14 @@ export class BlockProposalHandler {
81
84
  try {
82
85
  const result = await this.handleBlockProposal(proposal, proposalSender, true);
83
86
  if (result.isValid) {
84
- this.log.info(`Non-validator reexecution completed for slot ${proposal.slotNumber.toBigInt()}`, {
87
+ this.log.info(`Non-validator reexecution completed for slot ${proposal.slotNumber}`, {
85
88
  blockNumber: result.blockNumber,
86
89
  reexecutionTimeMs: result.reexecutionResult?.reexecutionTimeMs,
87
90
  totalManaUsed: result.reexecutionResult?.totalManaUsed,
88
91
  numTxs: result.reexecutionResult?.block?.body?.txEffects?.length ?? 0,
89
92
  });
90
93
  } else {
91
- this.log.warn(`Non-validator reexecution failed for slot ${proposal.slotNumber.toBigInt()}`, {
94
+ this.log.warn(`Non-validator reexecution failed for slot ${proposal.slotNumber}`, {
92
95
  blockNumber: result.blockNumber,
93
96
  reason: result.reason,
94
97
  });
@@ -108,7 +111,7 @@ export class BlockProposalHandler {
108
111
  proposalSender: PeerId,
109
112
  shouldReexecute: boolean,
110
113
  ): Promise<BlockProposalValidationResult> {
111
- const slotNumber = proposal.slotNumber.toBigInt();
114
+ const slotNumber = proposal.slotNumber;
112
115
  const proposer = proposal.getSender();
113
116
  const config = this.blockBuilder.getConfig();
114
117
 
@@ -150,7 +153,10 @@ export class BlockProposalHandler {
150
153
  }
151
154
 
152
155
  // Compute the block number based on the parent block
153
- const blockNumber = parentBlockHeader === 'genesis' ? INITIAL_L2_BLOCK_NUM : parentBlockHeader.getBlockNumber() + 1;
156
+ const blockNumber =
157
+ parentBlockHeader === 'genesis'
158
+ ? BlockNumber(INITIAL_L2_BLOCK_NUM)
159
+ : BlockNumber(parentBlockHeader.getBlockNumber() + 1);
154
160
 
155
161
  // Check that this block number does not exist already
156
162
  const existingBlock = await this.blockSource.getBlockHeader(blockNumber);
@@ -167,8 +173,10 @@ export class BlockProposalHandler {
167
173
  });
168
174
 
169
175
  // Check that I have the same set of l1ToL2Messages as the proposal
170
- const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockNumber);
171
- const computedInHash = await computeInHashFromL1ToL2Messages(l1ToL2Messages);
176
+ const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(
177
+ CheckpointNumber.fromBlockNumber(blockNumber),
178
+ );
179
+ const computedInHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
172
180
  const proposalInHash = proposal.payload.header.contentCommitment.inHash;
173
181
  if (!computedInHash.equals(proposalInHash)) {
174
182
  this.log.warn(`L1 to L2 messages in hash mismatch, skipping processing`, {
@@ -204,7 +212,7 @@ export class BlockProposalHandler {
204
212
 
205
213
  private async getParentBlock(proposal: BlockProposal): Promise<'genesis' | BlockHeader | undefined> {
206
214
  const parentArchive = proposal.payload.header.lastArchiveRoot;
207
- const slot = proposal.slotNumber.toBigInt();
215
+ const slot = proposal.slotNumber;
208
216
  const config = this.blockBuilder.getConfig();
209
217
  const { genesisArchiveRoot } = await this.blockSource.getGenesisValues();
210
218
 
@@ -239,8 +247,8 @@ export class BlockProposalHandler {
239
247
  }
240
248
  }
241
249
 
242
- private getReexecutionDeadline(slot: bigint, config: { l1GenesisTime: bigint; slotDuration: number }): Date {
243
- const nextSlotTimestampSeconds = Number(getTimestampForSlot(slot + 1n, config));
250
+ private getReexecutionDeadline(slot: SlotNumber, config: { l1GenesisTime: bigint; slotDuration: number }): Date {
251
+ const nextSlotTimestampSeconds = Number(getTimestampForSlot(SlotNumber(slot + 1), config));
244
252
  const msNeededForPropagationAndPublishing = this.config.validatorReexecuteDeadlineMs;
245
253
  return new Date(nextSlotTimestampSeconds * 1000 - msNeededForPropagationAndPublishing);
246
254
  }
@@ -259,7 +267,7 @@ export class BlockProposalHandler {
259
267
 
260
268
  async reexecuteTransactions(
261
269
  proposal: BlockProposal,
262
- blockNumber: number,
270
+ blockNumber: BlockNumber,
263
271
  txs: Tx[],
264
272
  l1ToL2Messages: Fr[],
265
273
  ): Promise<ReexecuteTransactionsResult> {
@@ -290,11 +298,11 @@ export class BlockProposalHandler {
290
298
  });
291
299
 
292
300
  const { block, failedTxs } = await this.blockBuilder.buildBlock(txs, l1ToL2Messages, globalVariables, {
293
- deadline: this.getReexecutionDeadline(proposal.payload.header.slotNumber.toBigInt(), config),
301
+ deadline: this.getReexecutionDeadline(proposal.payload.header.slotNumber, config),
294
302
  });
295
303
 
296
304
  const numFailedTxs = failedTxs.length;
297
- const slot = proposal.slotNumber.toBigInt();
305
+ const slot = proposal.slotNumber;
298
306
  this.log.verbose(`Transaction re-execution complete for slot ${slot}`, {
299
307
  numFailedTxs,
300
308
  numProposalTxs: txHashes.length,
@@ -320,12 +328,7 @@ export class BlockProposalHandler {
320
328
  actual: proposal.payload.toInspect(),
321
329
  });
322
330
  this.metrics?.recordFailedReexecution(proposal);
323
- throw new ReExStateMismatchError(
324
- proposal.archive,
325
- block.archive.root,
326
- proposal.payload.stateReference,
327
- block.header.state,
328
- );
331
+ throw new ReExStateMismatchError(proposal.archive, block.archive.root);
329
332
  }
330
333
 
331
334
  const reexecutionTimeMs = timer.ms();
package/src/config.ts CHANGED
@@ -64,6 +64,12 @@ export const validatorClientConfigMappings: ConfigMappingsType<ValidatorClientCo
64
64
  'Whether to always reexecute block proposals, even for non-validator nodes (useful for monitoring network status).',
65
65
  ...booleanConfigHelper(false),
66
66
  },
67
+ fishermanMode: {
68
+ env: 'FISHERMAN_MODE',
69
+ description:
70
+ 'Whether to run in fisherman mode: validates all proposals and attestations but does not broadcast attestations or participate in consensus.',
71
+ ...booleanConfigHelper(false),
72
+ },
67
73
  };
68
74
 
69
75
  /**
@@ -1,10 +1,9 @@
1
1
  import { Buffer32 } from '@aztec/foundation/buffer';
2
- import { keccak256 } from '@aztec/foundation/crypto';
2
+ import { keccak256 } from '@aztec/foundation/crypto/keccak';
3
+ import { Fr } from '@aztec/foundation/curves/bn254';
3
4
  import type { EthAddress } from '@aztec/foundation/eth-address';
4
5
  import type { Signature } from '@aztec/foundation/eth-signature';
5
- import { Fr } from '@aztec/foundation/fields';
6
6
  import { createLogger } from '@aztec/foundation/log';
7
- import { unfreeze } from '@aztec/foundation/types';
8
7
  import type { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
9
8
  import {
10
9
  BlockAttestation,
@@ -14,8 +13,7 @@ import {
14
13
  SignatureDomainSeparator,
15
14
  } from '@aztec/stdlib/p2p';
16
15
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
17
- import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
18
- import { StateReference, type Tx } from '@aztec/stdlib/tx';
16
+ import type { Tx } from '@aztec/stdlib/tx';
19
17
 
20
18
  import type { ValidatorKeyStore } from '../key_store/interface.js';
21
19
 
@@ -38,7 +36,6 @@ export class ValidationService {
38
36
  async createBlockProposal(
39
37
  header: CheckpointHeader,
40
38
  archive: Fr,
41
- stateReference: StateReference,
42
39
  txs: Tx[],
43
40
  proposerAttesterAddress: EthAddress | undefined,
44
41
  options: BlockProposalOptions,
@@ -54,14 +51,14 @@ export class ValidationService {
54
51
  // TODO: check if this is calculated earlier / can not be recomputed
55
52
  const txHashes = await Promise.all(txs.map(tx => tx.getTxHash()));
56
53
 
57
- // For testing: corrupt the state reference to trigger state_mismatch validation failure
54
+ // For testing: change the new archive to trigger state_mismatch validation failure
58
55
  if (options.broadcastInvalidBlockProposal) {
59
- unfreeze(stateReference.partial).noteHashTree = AppendOnlyTreeSnapshot.random();
60
- this.log.warn(`Creating INVALID block proposal for slot ${header.slotNumber.toBigInt()}`);
56
+ archive = Fr.random();
57
+ this.log.warn(`Creating INVALID block proposal for slot ${header.slotNumber}`);
61
58
  }
62
59
 
63
60
  return BlockProposal.createProposalFromSigner(
64
- new ConsensusPayload(header, archive, stateReference),
61
+ new ConsensusPayload(header, archive),
65
62
  txHashes,
66
63
  options.publishFullTxs ? txs : undefined,
67
64
  payloadSigner,
@@ -1,5 +1,5 @@
1
1
  import { Buffer32 } from '@aztec/foundation/buffer';
2
- import { Secp256k1Signer } from '@aztec/foundation/crypto';
2
+ import { Secp256k1Signer } from '@aztec/foundation/crypto/secp256k1-signer';
3
3
  import type { EthAddress } from '@aztec/foundation/eth-address';
4
4
  import type { Signature } from '@aztec/foundation/eth-signature';
5
5
 
@@ -1,4 +1,4 @@
1
- import type { EthSigner } from '@aztec/ethereum';
1
+ import type { EthSigner } from '@aztec/ethereum/eth-signer';
2
2
  import type { Buffer32 } from '@aztec/foundation/buffer';
3
3
  import { EthAddress } from '@aztec/foundation/eth-address';
4
4
  import type { Signature } from '@aztec/foundation/eth-signature';
@@ -1,4 +1,5 @@
1
1
  import type { Buffer32 } from '@aztec/foundation/buffer';
2
+ import { normalizeSignature } from '@aztec/foundation/crypto/secp256k1-signer';
2
3
  import { EthAddress } from '@aztec/foundation/eth-address';
3
4
  import { Signature } from '@aztec/foundation/eth-signature';
4
5
 
@@ -45,11 +46,8 @@ export class Web3SignerKeyStore implements ValidatorKeyStore {
45
46
  * @param typedData - The complete EIP-712 typed data structure (domain, types, primaryType, message)
46
47
  * @return signatures
47
48
  */
48
- public async signTypedData(typedData: TypedDataDefinition): Promise<Signature[]> {
49
- const signatures = await Promise.all(
50
- this.addresses.map(address => this.makeJsonRpcSignTypedDataRequest(address, typedData)),
51
- );
52
- return signatures;
49
+ public signTypedData(typedData: TypedDataDefinition): Promise<Signature[]> {
50
+ return Promise.all(this.addresses.map(address => this.makeJsonRpcSignTypedDataRequest(address, typedData)));
53
51
  }
54
52
 
55
53
  /**
@@ -73,9 +71,8 @@ export class Web3SignerKeyStore implements ValidatorKeyStore {
73
71
  * @param message - The message to sign
74
72
  * @return signatures
75
73
  */
76
- public async signMessage(message: Buffer32): Promise<Signature[]> {
77
- const signatures = await Promise.all(this.addresses.map(address => this.makeJsonRpcSignRequest(address, message)));
78
- return signatures;
74
+ public signMessage(message: Buffer32): Promise<Signature[]> {
75
+ return Promise.all(this.addresses.map(address => this.makeJsonRpcSignRequest(address, message)));
79
76
  }
80
77
 
81
78
  /**
@@ -144,7 +141,7 @@ export class Web3SignerKeyStore implements ValidatorKeyStore {
144
141
  }
145
142
 
146
143
  // Parse the signature from the hex string
147
- return Signature.fromString(signatureHex as `0x${string}`);
144
+ return normalizeSignature(Signature.fromString(signatureHex as `0x${string}`));
148
145
  }
149
146
 
150
147
  private async makeJsonRpcSignTypedDataRequest(
@@ -190,6 +187,6 @@ export class Web3SignerKeyStore implements ValidatorKeyStore {
190
187
  signatureHex = '0x' + signatureHex;
191
188
  }
192
189
 
193
- return Signature.fromString(signatureHex as `0x${string}`);
190
+ return normalizeSignature(Signature.fromString(signatureHex as `0x${string}`));
194
191
  }
195
192
  }
package/src/metrics.ts CHANGED
@@ -85,15 +85,17 @@ export class ValidatorMetrics {
85
85
  this.successfulAttestationsCount.add(num);
86
86
  }
87
87
 
88
- public incFailedAttestationsBadProposal(num: number, reason: string) {
88
+ public incFailedAttestationsBadProposal(num: number, reason: string, inCommittee: boolean) {
89
89
  this.failedAttestationsBadProposalCount.add(num, {
90
90
  [Attributes.ERROR_TYPE]: reason,
91
+ [Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
91
92
  });
92
93
  }
93
94
 
94
- public incFailedAttestationsNodeIssue(num: number, reason: string) {
95
+ public incFailedAttestationsNodeIssue(num: number, reason: string, inCommittee: boolean) {
95
96
  this.failedAttestationsNodeIssueCount.add(num, {
96
97
  [Attributes.ERROR_TYPE]: reason,
98
+ [Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
97
99
  });
98
100
  }
99
101
  }