@aztec/validator-client 0.0.1-commit.b655e406 → 0.0.1-commit.c7c42ec

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 (37) 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 +3 -1
  11. package/dest/factory.d.ts.map +1 -1
  12. package/dest/factory.js +1 -1
  13. package/dest/index.d.ts +1 -1
  14. package/dest/key_store/index.d.ts +1 -1
  15. package/dest/key_store/interface.d.ts +1 -1
  16. package/dest/key_store/local_key_store.d.ts +1 -1
  17. package/dest/key_store/local_key_store.d.ts.map +1 -1
  18. package/dest/key_store/local_key_store.js +1 -1
  19. package/dest/key_store/node_keystore_adapter.d.ts +1 -1
  20. package/dest/key_store/node_keystore_adapter.d.ts.map +1 -1
  21. package/dest/key_store/web3signer_key_store.d.ts +1 -7
  22. package/dest/key_store/web3signer_key_store.d.ts.map +1 -1
  23. package/dest/key_store/web3signer_key_store.js +1 -1
  24. package/dest/metrics.d.ts +1 -1
  25. package/dest/metrics.d.ts.map +1 -1
  26. package/dest/validator.d.ts +12 -8
  27. package/dest/validator.d.ts.map +1 -1
  28. package/dest/validator.js +100 -28
  29. package/package.json +16 -14
  30. package/src/block_proposal_handler.ts +26 -23
  31. package/src/config.ts +6 -0
  32. package/src/duties/validation_service.ts +7 -10
  33. package/src/factory.ts +3 -0
  34. package/src/key_store/local_key_store.ts +1 -1
  35. package/src/key_store/node_keystore_adapter.ts +1 -1
  36. package/src/key_store/web3signer_key_store.ts +1 -1
  37. package/src/validator.ts +118 -41
package/dest/validator.js CHANGED
@@ -1,3 +1,10 @@
1
+ function _ts_decorate(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ }
7
+ import { getBlobsPerL1Block } from '@aztec/blob-lib';
1
8
  import { createLogger } from '@aztec/foundation/log';
2
9
  import { RunningPromise } from '@aztec/foundation/running-promise';
3
10
  import { sleep } from '@aztec/foundation/sleep';
@@ -5,7 +12,7 @@ import { DateProvider } from '@aztec/foundation/timer';
5
12
  import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
6
13
  import { OffenseType, WANT_TO_SLASH_EVENT } from '@aztec/slasher';
7
14
  import { AttestationTimeoutError } from '@aztec/stdlib/validators';
8
- import { getTelemetryClient } from '@aztec/telemetry-client';
15
+ import { Attributes, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
9
16
  import { EventEmitter } from 'events';
10
17
  import { BlockProposalHandler } from './block_proposal_handler.js';
11
18
  import { ValidationService } from './duties/validation_service.js';
@@ -27,11 +34,12 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
27
34
  p2pClient;
28
35
  blockProposalHandler;
29
36
  config;
37
+ fileStoreBlobUploadClient;
30
38
  dateProvider;
31
- log;
32
39
  tracer;
33
40
  validationService;
34
41
  metrics;
42
+ log;
35
43
  // Whether it has already registered handlers on the p2p client
36
44
  hasRegisteredHandlers;
37
45
  // Used to check if we are sending the same proposal twice
@@ -39,13 +47,15 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
39
47
  lastEpochForCommitteeUpdateLoop;
40
48
  epochCacheUpdateLoop;
41
49
  proposersOfInvalidBlocks;
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();
50
+ constructor(keyStore, epochCache, p2pClient, blockProposalHandler, config, fileStoreBlobUploadClient, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
51
+ super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockProposalHandler = blockProposalHandler, this.config = config, this.fileStoreBlobUploadClient = fileStoreBlobUploadClient, this.dateProvider = dateProvider, this.hasRegisteredHandlers = false, this.proposersOfInvalidBlocks = new Set();
52
+ // Create child logger with fisherman prefix if in fisherman mode
53
+ this.log = config.fishermanMode ? log.createChild('[FISHERMAN]') : log;
44
54
  this.tracer = telemetry.getTracer('Validator');
45
55
  this.metrics = new ValidatorMetrics(telemetry);
46
- this.validationService = new ValidationService(keyStore, log.createChild('validation-service'));
56
+ this.validationService = new ValidationService(keyStore, this.log.createChild('validation-service'));
47
57
  // Refresh epoch cache every second to trigger alert if participation in committee changes
48
- this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), log, 1000);
58
+ this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), this.log, 1000);
49
59
  const myAddresses = this.getValidatorAddresses();
50
60
  this.log.verbose(`Initialized validator with addresses: ${myAddresses.map((a)=>a.toString()).join(', ')}`);
51
61
  }
@@ -92,13 +102,13 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
92
102
  this.log.error(`Error updating epoch committee`, err);
93
103
  }
94
104
  }
95
- static new(config, blockBuilder, epochCache, p2pClient, blockSource, l1ToL2MessageSource, txProvider, keyStoreManager, dateProvider = new DateProvider(), telemetry = getTelemetryClient()) {
105
+ static new(config, blockBuilder, epochCache, p2pClient, blockSource, l1ToL2MessageSource, txProvider, keyStoreManager, fileStoreBlobUploadClient, dateProvider = new DateProvider(), telemetry = getTelemetryClient()) {
96
106
  const metrics = new ValidatorMetrics(telemetry);
97
107
  const blockProposalValidator = new BlockProposalValidator(epochCache, {
98
108
  txsPermitted: !config.disableTransactions
99
109
  });
100
110
  const blockProposalHandler = new BlockProposalHandler(blockBuilder, blockSource, l1ToL2MessageSource, txProvider, blockProposalValidator, config, metrics, dateProvider, telemetry);
101
- const validator = new ValidatorClient(NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager), epochCache, p2pClient, blockProposalHandler, config, dateProvider, telemetry);
111
+ const validator = new ValidatorClient(NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager), epochCache, p2pClient, blockProposalHandler, config, fileStoreBlobUploadClient, dateProvider, telemetry);
102
112
  return validator;
103
113
  }
104
114
  getValidatorAddresses() {
@@ -137,10 +147,9 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
137
147
  await this.registerHandlers();
138
148
  const myAddresses = this.getValidatorAddresses();
139
149
  const inCommittee = await this.epochCache.filterInCommittee('now', myAddresses);
150
+ this.log.info(`Started validator with addresses: ${myAddresses.map((a)=>a.toString()).join(', ')}`);
140
151
  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(', ')}`);
152
+ this.log.info(`Addresses in current validator committee: ${inCommittee.map((a)=>a.toString()).join(', ')}`);
144
153
  }
145
154
  this.epochCacheUpdateLoop.start();
146
155
  return Promise.resolve();
@@ -160,7 +169,7 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
160
169
  }
161
170
  }
162
171
  async attestToProposal(proposal, proposalSender) {
163
- const slotNumber = proposal.slotNumber.toBigInt();
172
+ const slotNumber = proposal.slotNumber;
164
173
  const proposer = proposal.getSender();
165
174
  // Reject proposals with invalid signatures
166
175
  if (!proposer) {
@@ -176,12 +185,14 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
176
185
  };
177
186
  this.log.info(`Received proposal for slot ${slotNumber}`, {
178
187
  ...proposalInfo,
179
- txHashes: proposal.txHashes.map((t)=>t.toString())
188
+ txHashes: proposal.txHashes.map((t)=>t.toString()),
189
+ fishermanMode: this.config.fishermanMode || false
180
190
  });
181
191
  // Reexecute txs if we are part of the committee so we can attest, or if slashing is enabled so we can slash
182
192
  // 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;
193
+ // In fisherman mode, we always reexecute to validate proposals.
194
+ const { validatorReexecute, slashBroadcastedInvalidBlockPenalty, alwaysReexecuteBlockProposals, fishermanMode } = this.config;
195
+ const shouldReexecute = fishermanMode || slashBroadcastedInvalidBlockPenalty > 0n && validatorReexecute || partOfCommittee && validatorReexecute || alwaysReexecuteBlockProposals || this.fileStoreBlobUploadClient;
185
196
  const validationResult = await this.blockProposalHandler.handleBlockProposal(proposal, proposalSender, !!shouldReexecute);
186
197
  if (!validationResult.isValid) {
187
198
  this.log.warn(`Proposal validation failed: ${validationResult.reason}`, proposalInfo);
@@ -208,15 +219,55 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
208
219
  return undefined;
209
220
  }
210
221
  // Check that I have any address in current committee before attesting
211
- if (!partOfCommittee) {
222
+ // In fisherman mode, we still create attestations for validation even if not in committee
223
+ if (!partOfCommittee && !this.config.fishermanMode) {
212
224
  this.log.verbose(`No validator in the current committee, skipping attestation`, proposalInfo);
213
225
  return undefined;
214
226
  }
215
227
  // Provided all of the above checks pass, we can attest to the proposal
216
- this.log.info(`Attesting to proposal for slot ${slotNumber}`, proposalInfo);
228
+ this.log.info(`${partOfCommittee ? 'Attesting to' : 'Validated'} proposal for slot ${slotNumber}`, {
229
+ ...proposalInfo,
230
+ inCommittee: partOfCommittee,
231
+ fishermanMode: this.config.fishermanMode || false
232
+ });
217
233
  this.metrics.incSuccessfulAttestations(inCommittee.length);
234
+ // Upload blobs to filestore after successful re-execution (fire-and-forget)
235
+ if (validationResult.reexecutionResult?.block && this.fileStoreBlobUploadClient) {
236
+ void Promise.resolve().then(async ()=>{
237
+ try {
238
+ const blobFields = validationResult.reexecutionResult.block.getCheckpointBlobFields();
239
+ const blobs = getBlobsPerL1Block(blobFields);
240
+ await this.fileStoreBlobUploadClient.saveBlobs(blobs, true);
241
+ this.log.debug(`Uploaded ${blobs.length} blobs to filestore from re-execution`, proposalInfo);
242
+ } catch (err) {
243
+ this.log.warn(`Failed to upload blobs from re-execution`, err);
244
+ }
245
+ });
246
+ }
218
247
  // If the above function does not throw an error, then we can attest to the proposal
219
- return this.createBlockAttestationsFromProposal(proposal, inCommittee);
248
+ // Determine which validators should attest
249
+ let attestors;
250
+ if (partOfCommittee) {
251
+ attestors = inCommittee;
252
+ } else if (this.config.fishermanMode) {
253
+ // In fisherman mode, create attestations for validation purposes even if not in committee. These won't be broadcast.
254
+ attestors = this.getValidatorAddresses();
255
+ } else {
256
+ attestors = [];
257
+ }
258
+ // Only create attestations if we have attestors
259
+ if (attestors.length === 0) {
260
+ return undefined;
261
+ }
262
+ if (this.config.fishermanMode) {
263
+ // bail out early and don't save attestations to the pool in fisherman mode
264
+ this.log.info(`Creating attestations for proposal for slot ${slotNumber}`, {
265
+ ...proposalInfo,
266
+ attestors: attestors.map((a)=>a.toString())
267
+ });
268
+ return undefined;
269
+ }
270
+ return this.createBlockAttestationsFromProposal(proposal, attestors);
220
271
  }
221
272
  slashInvalidBlock(proposal) {
222
273
  const proposer = proposal.getSender();
@@ -236,22 +287,30 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
236
287
  validator: proposer,
237
288
  amount: this.config.slashBroadcastedInvalidBlockPenalty,
238
289
  offenseType: OffenseType.BROADCASTED_INVALID_BLOCK_PROPOSAL,
239
- epochOrSlot: proposal.slotNumber.toBigInt()
290
+ epochOrSlot: BigInt(proposal.slotNumber)
240
291
  }
241
292
  ]);
242
293
  }
243
- async createBlockProposal(blockNumber, header, archive, stateReference, txs, proposerAddress, options) {
244
- if (this.previousProposal?.slotNumber.equals(header.slotNumber)) {
245
- this.log.verbose(`Already made a proposal for the same slot, skipping proposal`);
246
- return Promise.resolve(undefined);
247
- }
248
- const newProposal = await this.validationService.createBlockProposal(header, archive, stateReference, txs, proposerAddress, {
294
+ // TODO(palla/mbps): Block proposal should not require a checkpoint proposal
295
+ async createBlockProposal(blockNumber, header, archive, txs, proposerAddress, options) {
296
+ // TODO(palla/mbps): Prevent double proposals properly
297
+ // if (this.previousProposal?.slotNumber === header.slotNumber) {
298
+ // this.log.verbose(`Already made a proposal for the same slot, skipping proposal`);
299
+ // return Promise.resolve(undefined);
300
+ // }
301
+ this.log.info(`Assembling block proposal for block ${blockNumber} slot ${header.slotNumber}`);
302
+ const newProposal = await this.validationService.createBlockProposal(header, archive, txs, proposerAddress, {
249
303
  ...options,
250
304
  broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal
251
305
  });
252
306
  this.previousProposal = newProposal;
253
307
  return newProposal;
254
308
  }
309
+ // TODO(palla/mbps): Effectively create a checkpoint proposal different from a block proposal
310
+ createCheckpointProposal(header, archive, txs, proposerAddress, options) {
311
+ this.log.info(`Assembling checkpoint proposal for slot ${header.slotNumber}`);
312
+ return this.createBlockProposal(0, header, archive, txs, proposerAddress, options);
313
+ }
255
314
  async broadcastBlockProposal(proposal) {
256
315
  await this.p2pClient.broadcastProposal(proposal);
257
316
  }
@@ -259,16 +318,23 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
259
318
  return await this.validationService.signAttestationsAndSigners(attestationsAndSigners, proposer);
260
319
  }
261
320
  async collectOwnAttestations(proposal) {
262
- const slot = proposal.payload.header.slotNumber.toBigInt();
321
+ const slot = proposal.payload.header.slotNumber;
263
322
  const inCommittee = await this.epochCache.filterInCommittee(slot, this.getValidatorAddresses());
264
323
  this.log.debug(`Collecting ${inCommittee.length} self-attestations for slot ${slot}`, {
265
324
  inCommittee
266
325
  });
267
- return this.createBlockAttestationsFromProposal(proposal, inCommittee);
326
+ const attestations = await this.createBlockAttestationsFromProposal(proposal, inCommittee);
327
+ // We broadcast our own attestations to our peers so, in case our block does not get mined on L1,
328
+ // other nodes can see that our validators did attest to this block proposal, and do not slash us
329
+ // due to inactivity for missed attestations.
330
+ void this.p2pClient.broadcastAttestations(attestations).catch((err)=>{
331
+ this.log.error(`Failed to broadcast self-attestations for slot ${slot}`, err);
332
+ });
333
+ return attestations;
268
334
  }
269
335
  async collectAttestations(proposal, required, deadline) {
270
336
  // Wait and poll the p2pClient's attestation pool for this block until we have enough attestations
271
- const slot = proposal.payload.header.slotNumber.toBigInt();
337
+ const slot = proposal.payload.header.slotNumber;
272
338
  this.log.debug(`Collecting ${required} attestations for slot ${slot} with deadline ${deadline.toISOString()}`);
273
339
  if (+deadline < this.dateProvider.now()) {
274
340
  this.log.error(`Deadline ${deadline.toISOString()} for collecting ${required} attestations for slot ${slot} is in the past`);
@@ -341,3 +407,9 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
341
407
  return authResponse.toBuffer();
342
408
  }
343
409
  }
410
+ _ts_decorate([
411
+ trackSpan('validator.attestToProposal', (proposal, proposalSender)=>({
412
+ [Attributes.BLOCK_HASH]: proposal.payload.header.hash.toString(),
413
+ [Attributes.PEER_ID]: proposalSender.toString()
414
+ }))
415
+ ], ValidatorClient.prototype, "attestToProposal", null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/validator-client",
3
- "version": "0.0.1-commit.b655e406",
3
+ "version": "0.0.1-commit.c7c42ec",
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,27 @@
64
64
  ]
65
65
  },
66
66
  "dependencies": {
67
- "@aztec/constants": "0.0.1-commit.b655e406",
68
- "@aztec/epoch-cache": "0.0.1-commit.b655e406",
69
- "@aztec/ethereum": "0.0.1-commit.b655e406",
70
- "@aztec/foundation": "0.0.1-commit.b655e406",
71
- "@aztec/node-keystore": "0.0.1-commit.b655e406",
72
- "@aztec/p2p": "0.0.1-commit.b655e406",
73
- "@aztec/prover-client": "0.0.1-commit.b655e406",
74
- "@aztec/slasher": "0.0.1-commit.b655e406",
75
- "@aztec/stdlib": "0.0.1-commit.b655e406",
76
- "@aztec/telemetry-client": "0.0.1-commit.b655e406",
67
+ "@aztec/blob-client": "0.0.1-commit.c7c42ec",
68
+ "@aztec/blob-lib": "0.0.1-commit.c7c42ec",
69
+ "@aztec/constants": "0.0.1-commit.c7c42ec",
70
+ "@aztec/epoch-cache": "0.0.1-commit.c7c42ec",
71
+ "@aztec/ethereum": "0.0.1-commit.c7c42ec",
72
+ "@aztec/foundation": "0.0.1-commit.c7c42ec",
73
+ "@aztec/node-keystore": "0.0.1-commit.c7c42ec",
74
+ "@aztec/p2p": "0.0.1-commit.c7c42ec",
75
+ "@aztec/slasher": "0.0.1-commit.c7c42ec",
76
+ "@aztec/stdlib": "0.0.1-commit.c7c42ec",
77
+ "@aztec/telemetry-client": "0.0.1-commit.c7c42ec",
77
78
  "koa": "^2.16.1",
78
79
  "koa-router": "^13.1.1",
79
80
  "tslib": "^2.4.0",
80
- "viem": "npm:@spalladino/viem@2.38.2-eip7594.0"
81
+ "viem": "npm:@aztec/viem@2.38.2"
81
82
  },
82
83
  "devDependencies": {
83
84
  "@jest/globals": "^30.0.0",
84
85
  "@types/jest": "^30.0.0",
85
86
  "@types/node": "^22.15.17",
87
+ "@typescript/native-preview": "7.0.0-dev.20251126.1",
86
88
  "jest": "^30.0.0",
87
89
  "jest-mock-extended": "^4.0.0",
88
90
  "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,
package/src/factory.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { FileStoreBlobClient } from '@aztec/blob-client/filestore';
1
2
  import type { EpochCache } from '@aztec/epoch-cache';
2
3
  import type { DateProvider } from '@aztec/foundation/timer';
3
4
  import type { KeystoreManager } from '@aztec/node-keystore';
@@ -51,6 +52,7 @@ export function createValidatorClient(
51
52
  dateProvider: DateProvider;
52
53
  epochCache: EpochCache;
53
54
  keyStoreManager: KeystoreManager | undefined;
55
+ fileStoreBlobUploadClient?: FileStoreBlobClient;
54
56
  },
55
57
  ) {
56
58
  if (config.disableValidator || !deps.keyStoreManager) {
@@ -67,6 +69,7 @@ export function createValidatorClient(
67
69
  deps.l1ToL2MessageSource,
68
70
  txProvider,
69
71
  deps.keyStoreManager,
72
+ deps.fileStoreBlobUploadClient,
70
73
  deps.dateProvider,
71
74
  deps.telemetry,
72
75
  );
@@ -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,5 +1,5 @@
1
1
  import type { Buffer32 } from '@aztec/foundation/buffer';
2
- import { normalizeSignature } from '@aztec/foundation/crypto';
2
+ import { normalizeSignature } from '@aztec/foundation/crypto/secp256k1-signer';
3
3
  import { EthAddress } from '@aztec/foundation/eth-address';
4
4
  import { Signature } from '@aztec/foundation/eth-signature';
5
5