@aztec/validator-client 3.0.0-nightly.20250921 → 3.0.0-nightly.20250923

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.
@@ -12,7 +12,7 @@ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
12
12
  import type { CommitteeAttestationsAndSigners, L2BlockSource } from '@aztec/stdlib/block';
13
13
  import type { IFullNodeBlockBuilder, Validator, ValidatorClientFullConfig } from '@aztec/stdlib/interfaces/server';
14
14
  import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
15
- import type { BlockAttestation, BlockProposal, BlockProposalOptions } from '@aztec/stdlib/p2p';
15
+ import { type BlockAttestation, type BlockProposal, type BlockProposalOptions } from '@aztec/stdlib/p2p';
16
16
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
17
17
  import { type StateReference, type Tx } from '@aztec/stdlib/tx';
18
18
  import { type TelemetryClient, type Tracer } from '@aztec/telemetry-client';
@@ -37,6 +37,7 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
37
37
  readonly tracer: Tracer;
38
38
  private validationService;
39
39
  private metrics;
40
+ private hasRegisteredHandlers;
40
41
  private previousProposal?;
41
42
  private lastEpochForCommitteeUpdateLoop;
42
43
  private epochCacheUpdateLoop;
@@ -54,7 +55,8 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
54
55
  updateConfig(config: Partial<ValidatorClientFullConfig>): void;
55
56
  start(): Promise<void>;
56
57
  stop(): Promise<void>;
57
- registerBlockProposalHandler(): void;
58
+ /** Register handlers on the p2p client */
59
+ registerHandlers(): Promise<void>;
58
60
  attestToProposal(proposal: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation[] | undefined>;
59
61
  private getReexecutionDeadline;
60
62
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
package/dest/validator.js CHANGED
@@ -10,6 +10,7 @@ import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
10
10
  import { computeInHashFromL1ToL2Messages } from '@aztec/prover-client/helpers';
11
11
  import { OffenseType, WANT_TO_SLASH_EVENT } from '@aztec/slasher';
12
12
  import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
13
+ import { ConsensusPayload } from '@aztec/stdlib/p2p';
13
14
  import { GlobalVariables } from '@aztec/stdlib/tx';
14
15
  import { AttestationTimeoutError, ReExFailedTxsError, ReExStateMismatchError, ReExTimeoutError, TransactionsNotAvailableError } from '@aztec/stdlib/validators';
15
16
  import { getTelemetryClient } from '@aztec/telemetry-client';
@@ -36,6 +37,8 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
36
37
  tracer;
37
38
  validationService;
38
39
  metrics;
40
+ // Whether it has already registered handlers on the p2p client
41
+ hasRegisteredHandlers;
39
42
  // Used to check if we are sending the same proposal twice
40
43
  previousProposal;
41
44
  lastEpochForCommitteeUpdateLoop;
@@ -43,7 +46,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
43
46
  blockProposalValidator;
44
47
  proposersOfInvalidBlocks;
45
48
  constructor(blockBuilder, keyStore, epochCache, p2pClient, blockSource, l1ToL2MessageSource, txProvider, config, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
46
- super(), this.blockBuilder = blockBuilder, this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockSource = blockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.txProvider = txProvider, this.config = config, this.dateProvider = dateProvider, this.log = log, this.proposersOfInvalidBlocks = new Set();
49
+ super(), this.blockBuilder = blockBuilder, this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockSource = blockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.txProvider = txProvider, this.config = config, this.dateProvider = dateProvider, this.log = log, this.hasRegisteredHandlers = false, this.proposersOfInvalidBlocks = new Set();
47
50
  this.tracer = telemetry.getTracer('Validator');
48
51
  this.metrics = new ValidatorMetrics(telemetry);
49
52
  this.validationService = new ValidationService(keyStore);
@@ -126,9 +129,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
126
129
  this.log.warn(`Validator client already started`);
127
130
  return;
128
131
  }
129
- this.registerBlockProposalHandler();
130
- // Sync the committee from the smart contract
131
- // https://github.com/AztecProtocol/aztec-packages/issues/7962
132
+ await this.registerHandlers();
132
133
  const myAddresses = this.getValidatorAddresses();
133
134
  const inCommittee = await this.epochCache.filterInCommittee('now', myAddresses);
134
135
  if (inCommittee.length > 0) {
@@ -137,16 +138,20 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
137
138
  this.log.info(`Started validator with addresses: ${myAddresses.map((a)=>a.toString()).join(', ')}`);
138
139
  }
139
140
  this.epochCacheUpdateLoop.start();
140
- this.p2pClient.registerThisValidatorAddresses(myAddresses);
141
- await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
142
141
  return Promise.resolve();
143
142
  }
144
143
  async stop() {
145
144
  await this.epochCacheUpdateLoop.stop();
146
145
  }
147
- registerBlockProposalHandler() {
148
- const handler = (block, proposalSender)=>this.attestToProposal(block, proposalSender);
149
- this.p2pClient.registerBlockProposalHandler(handler);
146
+ /** Register handlers on the p2p client */ async registerHandlers() {
147
+ if (!this.hasRegisteredHandlers) {
148
+ this.hasRegisteredHandlers = true;
149
+ const handler = (block, proposalSender)=>this.attestToProposal(block, proposalSender);
150
+ this.p2pClient.registerBlockProposalHandler(handler);
151
+ const myAddresses = this.getValidatorAddresses();
152
+ this.p2pClient.registerThisValidatorAddresses(myAddresses);
153
+ await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
154
+ }
150
155
  }
151
156
  async attestToProposal(proposal, proposalSender) {
152
157
  const slotNumber = proposal.slotNumber.toBigInt();
@@ -155,19 +160,21 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
155
160
  // Check that I have any address in current committee before attesting
156
161
  const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
157
162
  const partOfCommittee = inCommittee.length > 0;
163
+ const incFailedAttestation = partOfCommittee ? (reason)=>this.metrics.incFailedAttestations(1, reason) : ()=>{};
158
164
  const proposalInfo = {
159
165
  ...proposal.toBlockInfo(),
160
166
  proposer: proposer.toString()
161
167
  };
162
168
  this.log.info(`Received proposal for slot ${slotNumber}`, {
163
169
  ...proposalInfo,
164
- txHashes: proposal.txHashes.map((txHash)=>txHash.toString())
170
+ txHashes: proposal.txHashes.map((t)=>t.toString())
165
171
  });
166
172
  // Collect txs from the proposal. Note that we do this before checking if we have an address in the
167
173
  // current committee, since we want to collect txs anyway to facilitate propagation.
174
+ const config = this.blockBuilder.getConfig();
168
175
  const { txs, missingTxs } = await this.txProvider.getTxsForBlockProposal(proposal, {
169
176
  pinnedPeer: proposalSender,
170
- deadline: this.getReexecutionDeadline(proposal, this.blockBuilder.getConfig())
177
+ deadline: this.getReexecutionDeadline(proposal, config)
171
178
  });
172
179
  // Check that I have any address in current committee before attesting
173
180
  if (!partOfCommittee) {
@@ -179,9 +186,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
179
186
  const invalidProposal = await this.blockProposalValidator.validate(proposal);
180
187
  if (invalidProposal) {
181
188
  this.log.warn(`Proposal is not valid, skipping attestation`, proposalInfo);
182
- if (partOfCommittee) {
183
- this.metrics.incFailedAttestations(1, 'invalid_proposal');
184
- }
189
+ incFailedAttestation('invalid_proposal');
185
190
  return undefined;
186
191
  }
187
192
  // Check that the parent proposal is a block we know, otherwise reexecution would fail.
@@ -190,7 +195,6 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
190
195
  // would not be rebroadcasted. But it also means that nodes that have not fully synced would
191
196
  // not rebroadcast the proposal.
192
197
  if (blockNumber > INITIAL_L2_BLOCK_NUM) {
193
- const config = this.blockBuilder.getConfig();
194
198
  const deadline = this.getReexecutionDeadline(proposal, config);
195
199
  const currentTime = this.dateProvider.now();
196
200
  const timeoutDurationMs = deadline.getTime() - currentTime;
@@ -204,9 +208,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
204
208
  }, 'Force Archiver Sync', timeoutDurationMs / 1000, 0.5);
205
209
  if (parentBlock === undefined) {
206
210
  this.log.warn(`Parent block for ${blockNumber} not found, skipping attestation`, proposalInfo);
207
- if (partOfCommittee) {
208
- this.metrics.incFailedAttestations(1, 'parent_block_not_found');
209
- }
211
+ incFailedAttestation('parent_block_not_found');
210
212
  return undefined;
211
213
  }
212
214
  if (!proposal.payload.header.lastArchiveRoot.equals(parentBlock.archive.root)) {
@@ -215,9 +217,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
215
217
  parentBlockArchiveRoot: parentBlock.archive.root.toString(),
216
218
  ...proposalInfo
217
219
  });
218
- if (partOfCommittee) {
219
- this.metrics.incFailedAttestations(1, 'parent_block_does_not_match');
220
- }
220
+ incFailedAttestation('parent_block_does_not_match');
221
221
  return undefined;
222
222
  }
223
223
  }
@@ -232,9 +232,15 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
232
232
  computedInHash: computedInHash.toString(),
233
233
  ...proposalInfo
234
234
  });
235
- if (partOfCommittee) {
236
- this.metrics.incFailedAttestations(1, 'in_hash_mismatch');
237
- }
235
+ incFailedAttestation('in_hash_mismatch');
236
+ return undefined;
237
+ }
238
+ // Check that this block number does not exist already, otherwise the proposer could signal
239
+ // an arbitrary block number in the past, though this would most likely fail on the rollup contract.
240
+ const existingBlock = await this.blockSource.getBlockHeader(blockNumber);
241
+ if (existingBlock) {
242
+ this.log.warn(`Block number ${blockNumber} already exists, skipping attestation`, proposalInfo);
243
+ incFailedAttestation('block_number_already_exists');
238
244
  return undefined;
239
245
  }
240
246
  // Check that all of the transactions in the proposal are available in the tx pool before attesting
@@ -243,9 +249,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
243
249
  ...proposalInfo,
244
250
  missingTxs
245
251
  });
246
- if (partOfCommittee) {
247
- this.metrics.incFailedAttestations(1, 'TransactionsNotAvailableError');
248
- }
252
+ incFailedAttestation('TransactionsNotAvailableError');
249
253
  return undefined;
250
254
  }
251
255
  // Try re-executing the transactions in the proposal
@@ -256,7 +260,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
256
260
  await this.reExecuteTransactions(proposal, txs, l1ToL2Messages);
257
261
  }
258
262
  } catch (error) {
259
- this.metrics.incFailedAttestations(1, error instanceof Error ? error.name : 'unknown');
263
+ incFailedAttestation(error instanceof Error ? error.name : 'unknown');
260
264
  this.log.error(`Error reexecuting txs while processing block proposal`, error, proposalInfo);
261
265
  if (error instanceof ReExStateMismatchError && this.config.slashBroadcastedInvalidBlockPenalty > 0n) {
262
266
  this.log.warn(`Slashing proposer for invalid block proposal`, proposalInfo);
@@ -290,8 +294,12 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
290
294
  // Use the sequencer's block building logic to re-execute the transactions
291
295
  const timer = new Timer();
292
296
  const config = this.blockBuilder.getConfig();
297
+ // We source most global variables from the proposal
293
298
  const globalVariables = GlobalVariables.from({
294
- ...proposal.payload.header,
299
+ slotNumber: proposal.payload.header.slotNumber,
300
+ coinbase: proposal.payload.header.coinbase,
301
+ feeRecipient: proposal.payload.header.feeRecipient,
302
+ gasFees: proposal.payload.header.gasFees,
295
303
  blockNumber: proposal.blockNumber,
296
304
  timestamp: header.timestamp,
297
305
  chainId: new Fr(config.l1ChainId),
@@ -310,8 +318,14 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
310
318
  this.metrics.recordFailedReexecution(proposal);
311
319
  throw new ReExTimeoutError();
312
320
  }
313
- // This function will throw an error if state updates do not match
314
- if (!block.archive.root.equals(proposal.archive)) {
321
+ // Throw a ReExStateMismatchError error if state updates do not match.
322
+ // Note that we check the entire proposal payload here, since it could be inconsistent within itself,
323
+ // as in the archive root not being actually derived by its other tree roots.
324
+ if (!ConsensusPayload.fromBlock(block).equals(proposal.payload)) {
325
+ this.log.warn(`Re-execution state mismatch for slot ${proposal.slotNumber.toBigInt()}`, {
326
+ expected: ConsensusPayload.fromBlock(block).toInspect(),
327
+ actual: proposal.payload.toInspect()
328
+ });
315
329
  this.metrics.recordFailedReexecution(proposal);
316
330
  throw new ReExStateMismatchError(proposal.archive, block.archive.root, proposal.payload.stateReference, block.header.state);
317
331
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/validator-client",
3
- "version": "3.0.0-nightly.20250921",
3
+ "version": "3.0.0-nightly.20250923",
4
4
  "main": "dest/index.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -64,16 +64,16 @@
64
64
  ]
65
65
  },
66
66
  "dependencies": {
67
- "@aztec/constants": "3.0.0-nightly.20250921",
68
- "@aztec/epoch-cache": "3.0.0-nightly.20250921",
69
- "@aztec/ethereum": "3.0.0-nightly.20250921",
70
- "@aztec/foundation": "3.0.0-nightly.20250921",
71
- "@aztec/node-keystore": "3.0.0-nightly.20250921",
72
- "@aztec/p2p": "3.0.0-nightly.20250921",
73
- "@aztec/prover-client": "3.0.0-nightly.20250921",
74
- "@aztec/slasher": "3.0.0-nightly.20250921",
75
- "@aztec/stdlib": "3.0.0-nightly.20250921",
76
- "@aztec/telemetry-client": "3.0.0-nightly.20250921",
67
+ "@aztec/constants": "3.0.0-nightly.20250923",
68
+ "@aztec/epoch-cache": "3.0.0-nightly.20250923",
69
+ "@aztec/ethereum": "3.0.0-nightly.20250923",
70
+ "@aztec/foundation": "3.0.0-nightly.20250923",
71
+ "@aztec/node-keystore": "3.0.0-nightly.20250923",
72
+ "@aztec/p2p": "3.0.0-nightly.20250923",
73
+ "@aztec/prover-client": "3.0.0-nightly.20250923",
74
+ "@aztec/slasher": "3.0.0-nightly.20250923",
75
+ "@aztec/stdlib": "3.0.0-nightly.20250923",
76
+ "@aztec/telemetry-client": "3.0.0-nightly.20250923",
77
77
  "koa": "^2.16.1",
78
78
  "koa-router": "^12.0.0",
79
79
  "tslib": "^2.4.0",
package/src/validator.ts CHANGED
@@ -25,7 +25,12 @@ import type { CommitteeAttestationsAndSigners, L2BlockSource } from '@aztec/stdl
25
25
  import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
26
26
  import type { IFullNodeBlockBuilder, Validator, ValidatorClientFullConfig } from '@aztec/stdlib/interfaces/server';
27
27
  import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
28
- import type { BlockAttestation, BlockProposal, BlockProposalOptions } from '@aztec/stdlib/p2p';
28
+ import {
29
+ type BlockAttestation,
30
+ type BlockProposal,
31
+ type BlockProposalOptions,
32
+ ConsensusPayload,
33
+ } from '@aztec/stdlib/p2p';
29
34
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
30
35
  import { GlobalVariables, type StateReference, type Tx } from '@aztec/stdlib/tx';
31
36
  import {
@@ -57,6 +62,9 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
57
62
  private validationService: ValidationService;
58
63
  private metrics: ValidatorMetrics;
59
64
 
65
+ // Whether it has already registered handlers on the p2p client
66
+ private hasRegisteredHandlers = false;
67
+
60
68
  // Used to check if we are sending the same proposal twice
61
69
  private previousProposal?: BlockProposal;
62
70
 
@@ -207,12 +215,9 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
207
215
  return;
208
216
  }
209
217
 
210
- this.registerBlockProposalHandler();
218
+ await this.registerHandlers();
211
219
 
212
- // Sync the committee from the smart contract
213
- // https://github.com/AztecProtocol/aztec-packages/issues/7962
214
220
  const myAddresses = this.getValidatorAddresses();
215
-
216
221
  const inCommittee = await this.epochCache.filterInCommittee('now', myAddresses);
217
222
  if (inCommittee.length > 0) {
218
223
  this.log.info(
@@ -225,9 +230,6 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
225
230
  }
226
231
  this.epochCacheUpdateLoop.start();
227
232
 
228
- this.p2pClient.registerThisValidatorAddresses(myAddresses);
229
- await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
230
-
231
233
  return Promise.resolve();
232
234
  }
233
235
 
@@ -235,10 +237,20 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
235
237
  await this.epochCacheUpdateLoop.stop();
236
238
  }
237
239
 
238
- public registerBlockProposalHandler() {
239
- const handler = (block: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation[] | undefined> =>
240
- this.attestToProposal(block, proposalSender);
241
- this.p2pClient.registerBlockProposalHandler(handler);
240
+ /** Register handlers on the p2p client */
241
+ public async registerHandlers() {
242
+ if (!this.hasRegisteredHandlers) {
243
+ this.hasRegisteredHandlers = true;
244
+
245
+ const handler = (block: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation[] | undefined> =>
246
+ this.attestToProposal(block, proposalSender);
247
+ this.p2pClient.registerBlockProposalHandler(handler);
248
+
249
+ const myAddresses = this.getValidatorAddresses();
250
+ this.p2pClient.registerThisValidatorAddresses(myAddresses);
251
+
252
+ await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
253
+ }
242
254
  }
243
255
 
244
256
  async attestToProposal(proposal: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation[] | undefined> {
@@ -249,22 +261,22 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
249
261
  // Check that I have any address in current committee before attesting
250
262
  const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
251
263
  const partOfCommittee = inCommittee.length > 0;
264
+ const incFailedAttestation = partOfCommittee
265
+ ? (reason: string) => this.metrics.incFailedAttestations(1, reason)
266
+ : () => {};
252
267
 
253
- const proposalInfo = {
254
- ...proposal.toBlockInfo(),
255
- proposer: proposer.toString(),
256
- };
257
-
268
+ const proposalInfo = { ...proposal.toBlockInfo(), proposer: proposer.toString() };
258
269
  this.log.info(`Received proposal for slot ${slotNumber}`, {
259
270
  ...proposalInfo,
260
- txHashes: proposal.txHashes.map(txHash => txHash.toString()),
271
+ txHashes: proposal.txHashes.map(t => t.toString()),
261
272
  });
262
273
 
263
274
  // Collect txs from the proposal. Note that we do this before checking if we have an address in the
264
275
  // current committee, since we want to collect txs anyway to facilitate propagation.
276
+ const config = this.blockBuilder.getConfig();
265
277
  const { txs, missingTxs } = await this.txProvider.getTxsForBlockProposal(proposal, {
266
278
  pinnedPeer: proposalSender,
267
- deadline: this.getReexecutionDeadline(proposal, this.blockBuilder.getConfig()),
279
+ deadline: this.getReexecutionDeadline(proposal, config),
268
280
  });
269
281
 
270
282
  // Check that I have any address in current committee before attesting
@@ -278,9 +290,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
278
290
  const invalidProposal = await this.blockProposalValidator.validate(proposal);
279
291
  if (invalidProposal) {
280
292
  this.log.warn(`Proposal is not valid, skipping attestation`, proposalInfo);
281
- if (partOfCommittee) {
282
- this.metrics.incFailedAttestations(1, 'invalid_proposal');
283
- }
293
+ incFailedAttestation('invalid_proposal');
284
294
  return undefined;
285
295
  }
286
296
 
@@ -290,7 +300,6 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
290
300
  // would not be rebroadcasted. But it also means that nodes that have not fully synced would
291
301
  // not rebroadcast the proposal.
292
302
  if (blockNumber > INITIAL_L2_BLOCK_NUM) {
293
- const config = this.blockBuilder.getConfig();
294
303
  const deadline = this.getReexecutionDeadline(proposal, config);
295
304
  const currentTime = this.dateProvider.now();
296
305
  const timeoutDurationMs = deadline.getTime() - currentTime;
@@ -313,9 +322,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
313
322
 
314
323
  if (parentBlock === undefined) {
315
324
  this.log.warn(`Parent block for ${blockNumber} not found, skipping attestation`, proposalInfo);
316
- if (partOfCommittee) {
317
- this.metrics.incFailedAttestations(1, 'parent_block_not_found');
318
- }
325
+ incFailedAttestation('parent_block_not_found');
319
326
  return undefined;
320
327
  }
321
328
 
@@ -325,9 +332,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
325
332
  parentBlockArchiveRoot: parentBlock.archive.root.toString(),
326
333
  ...proposalInfo,
327
334
  });
328
- if (partOfCommittee) {
329
- this.metrics.incFailedAttestations(1, 'parent_block_does_not_match');
330
- }
335
+ incFailedAttestation('parent_block_does_not_match');
331
336
  return undefined;
332
337
  }
333
338
  }
@@ -343,18 +348,23 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
343
348
  computedInHash: computedInHash.toString(),
344
349
  ...proposalInfo,
345
350
  });
346
- if (partOfCommittee) {
347
- this.metrics.incFailedAttestations(1, 'in_hash_mismatch');
348
- }
351
+ incFailedAttestation('in_hash_mismatch');
352
+ return undefined;
353
+ }
354
+
355
+ // Check that this block number does not exist already, otherwise the proposer could signal
356
+ // an arbitrary block number in the past, though this would most likely fail on the rollup contract.
357
+ const existingBlock = await this.blockSource.getBlockHeader(blockNumber);
358
+ if (existingBlock) {
359
+ this.log.warn(`Block number ${blockNumber} already exists, skipping attestation`, proposalInfo);
360
+ incFailedAttestation('block_number_already_exists');
349
361
  return undefined;
350
362
  }
351
363
 
352
364
  // Check that all of the transactions in the proposal are available in the tx pool before attesting
353
365
  if (missingTxs.length > 0) {
354
366
  this.log.warn(`Missing ${missingTxs.length} txs to attest to proposal`, { ...proposalInfo, missingTxs });
355
- if (partOfCommittee) {
356
- this.metrics.incFailedAttestations(1, 'TransactionsNotAvailableError');
357
- }
367
+ incFailedAttestation('TransactionsNotAvailableError');
358
368
  return undefined;
359
369
  }
360
370
 
@@ -366,7 +376,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
366
376
  await this.reExecuteTransactions(proposal, txs, l1ToL2Messages);
367
377
  }
368
378
  } catch (error: any) {
369
- this.metrics.incFailedAttestations(1, error instanceof Error ? error.name : 'unknown');
379
+ incFailedAttestation(error instanceof Error ? error.name : 'unknown');
370
380
  this.log.error(`Error reexecuting txs while processing block proposal`, error, proposalInfo);
371
381
  if (error instanceof ReExStateMismatchError && this.config.slashBroadcastedInvalidBlockPenalty > 0n) {
372
382
  this.log.warn(`Slashing proposer for invalid block proposal`, proposalInfo);
@@ -410,10 +420,15 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
410
420
  // Use the sequencer's block building logic to re-execute the transactions
411
421
  const timer = new Timer();
412
422
  const config = this.blockBuilder.getConfig();
423
+
424
+ // We source most global variables from the proposal
413
425
  const globalVariables = GlobalVariables.from({
414
- ...proposal.payload.header,
415
- blockNumber: proposal.blockNumber,
416
- timestamp: header.timestamp,
426
+ slotNumber: proposal.payload.header.slotNumber, // checked in the block proposal validator
427
+ coinbase: proposal.payload.header.coinbase, // set arbitrarily by the proposer
428
+ feeRecipient: proposal.payload.header.feeRecipient, // set arbitrarily by the proposer
429
+ gasFees: proposal.payload.header.gasFees, // validated by the rollup contract
430
+ blockNumber: proposal.blockNumber, // checked blockNumber-1 exists in archiver but blockNumber doesnt
431
+ timestamp: header.timestamp, // checked in the rollup contract against the slot number
417
432
  chainId: new Fr(config.l1ChainId),
418
433
  version: new Fr(config.rollupVersion),
419
434
  });
@@ -435,8 +450,14 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
435
450
  throw new ReExTimeoutError();
436
451
  }
437
452
 
438
- // This function will throw an error if state updates do not match
439
- if (!block.archive.root.equals(proposal.archive)) {
453
+ // Throw a ReExStateMismatchError error if state updates do not match.
454
+ // Note that we check the entire proposal payload here, since it could be inconsistent within itself,
455
+ // as in the archive root not being actually derived by its other tree roots.
456
+ if (!ConsensusPayload.fromBlock(block).equals(proposal.payload)) {
457
+ this.log.warn(`Re-execution state mismatch for slot ${proposal.slotNumber.toBigInt()}`, {
458
+ expected: ConsensusPayload.fromBlock(block).toInspect(),
459
+ actual: proposal.payload.toInspect(),
460
+ });
440
461
  this.metrics.recordFailedReexecution(proposal);
441
462
  throw new ReExStateMismatchError(
442
463
  proposal.archive,