@aztec/validator-client 0.0.1-commit.1a99e26c → 0.0.1-commit.1bb068fb5

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.
package/dest/validator.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { getBlobsPerL1Block } from '@aztec/blob-lib';
2
- import { BlockNumber } from '@aztec/foundation/branded-types';
2
+ import { validateFeeAssetPriceModifier } from '@aztec/ethereum/contracts';
3
+ import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
3
4
  import { TimeoutError } from '@aztec/foundation/error';
4
5
  import { createLogger } from '@aztec/foundation/log';
5
6
  import { retryUntil } from '@aztec/foundation/retry';
@@ -8,7 +9,7 @@ import { sleep } from '@aztec/foundation/sleep';
8
9
  import { DateProvider } from '@aztec/foundation/timer';
9
10
  import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
10
11
  import { OffenseType, WANT_TO_SLASH_EVENT } from '@aztec/slasher';
11
- import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
12
+ import { getEpochAtSlot, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
12
13
  import { accumulateCheckpointOutHashes } from '@aztec/stdlib/messaging';
13
14
  import { AttestationTimeoutError } from '@aztec/stdlib/validators';
14
15
  import { getTelemetryClient } from '@aztec/telemetry-client';
@@ -48,11 +49,12 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
48
49
  log;
49
50
  // Whether it has already registered handlers on the p2p client
50
51
  hasRegisteredHandlers;
51
- // Used to check if we are sending the same proposal twice
52
- previousProposal;
52
+ /** Tracks the last block proposal we created, to detect duplicate proposal attempts. */ lastProposedBlock;
53
+ /** Tracks the last checkpoint proposal we created. */ lastProposedCheckpoint;
53
54
  lastEpochForCommitteeUpdateLoop;
54
55
  epochCacheUpdateLoop;
55
56
  proposersOfInvalidBlocks;
57
+ /** Tracks the last checkpoint proposal we attested to, to prevent equivocation. */ lastAttestedProposal;
56
58
  constructor(keyStore, epochCache, p2pClient, blockProposalHandler, blockSource, checkpointsBuilder, worldState, l1ToL2MessageSource, config, blobClient, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
57
59
  super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockProposalHandler = blockProposalHandler, this.blockSource = blockSource, this.checkpointsBuilder = checkpointsBuilder, this.worldState = worldState, this.l1ToL2MessageSource = l1ToL2MessageSource, this.config = config, this.blobClient = blobClient, this.dateProvider = dateProvider, this.hasRegisteredHandlers = false, this.proposersOfInvalidBlocks = new Set();
58
60
  // Create child logger with fisherman prefix if in fisherman mode
@@ -183,6 +185,14 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
183
185
  // and processed separately via the block handler above.
184
186
  const checkpointHandler = (checkpoint, proposalSender)=>this.attestToCheckpointProposal(checkpoint, proposalSender);
185
187
  this.p2pClient.registerCheckpointProposalHandler(checkpointHandler);
188
+ // Duplicate proposal handler - triggers slashing for equivocation
189
+ this.p2pClient.registerDuplicateProposalCallback((info)=>{
190
+ this.handleDuplicateProposal(info);
191
+ });
192
+ // Duplicate attestation handler - triggers slashing for attestation equivocation
193
+ this.p2pClient.registerDuplicateAttestationCallback((info)=>{
194
+ this.handleDuplicateAttestation(info);
195
+ });
186
196
  const myAddresses = this.getValidatorAddresses();
187
197
  this.p2pClient.registerThisValidatorAddresses(myAddresses);
188
198
  await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
@@ -203,6 +213,14 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
203
213
  this.log.warn(`Received block proposal with invalid signature for slot ${slotNumber}`);
204
214
  return false;
205
215
  }
216
+ // Ignore proposals from ourselves (may happen in HA setups)
217
+ if (this.getValidatorAddresses().some((addr)=>addr.equals(proposer))) {
218
+ this.log.warn(`Ignoring block proposal from self for slot ${slotNumber}`, {
219
+ proposer: proposer.toString(),
220
+ slotNumber
221
+ });
222
+ return false;
223
+ }
206
224
  // Check if we're in the committee (for metrics purposes)
207
225
  const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
208
226
  const partOfCommittee = inCommittee.length > 0;
@@ -274,6 +292,19 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
274
292
  this.log.warn(`Received checkpoint proposal with invalid signature for slot ${slotNumber}`);
275
293
  return undefined;
276
294
  }
295
+ // Ignore proposals from ourselves (may happen in HA setups)
296
+ if (this.getValidatorAddresses().some((addr)=>addr.equals(proposer))) {
297
+ this.log.warn(`Ignoring block proposal from self for slot ${slotNumber}`, {
298
+ proposer: proposer.toString(),
299
+ slotNumber
300
+ });
301
+ return undefined;
302
+ }
303
+ // Validate fee asset price modifier is within allowed range
304
+ if (!validateFeeAssetPriceModifier(proposal.feeAssetPriceModifier)) {
305
+ this.log.warn(`Received checkpoint proposal with invalid feeAssetPriceModifier ${proposal.feeAssetPriceModifier} for slot ${slotNumber}`);
306
+ return undefined;
307
+ }
277
308
  // Check that I have any address in current committee before attesting
278
309
  const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
279
310
  const partOfCommittee = inCommittee.length > 0;
@@ -337,11 +368,32 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
337
368
  });
338
369
  return undefined;
339
370
  }
340
- return this.createCheckpointAttestationsFromProposal(proposal, attestors);
371
+ return await this.createCheckpointAttestationsFromProposal(proposal, attestors);
372
+ }
373
+ /**
374
+ * Checks if we should attest to a slot based on equivocation prevention rules.
375
+ * @returns true if we should attest, false if we should skip
376
+ */ shouldAttestToSlot(slotNumber) {
377
+ // If attestToEquivocatedProposals is true, always allow
378
+ if (this.config.attestToEquivocatedProposals) {
379
+ return true;
380
+ }
381
+ // Check if incoming slot is strictly greater than last attested
382
+ if (this.lastAttestedProposal && slotNumber <= this.lastAttestedProposal.slotNumber) {
383
+ this.log.warn(`Refusing to process a proposal for slot ${slotNumber} given we already attested to a proposal for slot ${this.lastAttestedProposal.slotNumber}`);
384
+ return false;
385
+ }
386
+ return true;
341
387
  }
342
388
  async createCheckpointAttestationsFromProposal(proposal, attestors = []) {
389
+ // Equivocation check: must happen right before signing to minimize the race window
390
+ if (!this.shouldAttestToSlot(proposal.slotNumber)) {
391
+ return undefined;
392
+ }
343
393
  const attestations = await this.validationService.attestToCheckpointProposal(proposal, attestors);
344
- await this.p2pClient.addCheckpointAttestations(attestations);
394
+ // Track the proposal we attested to (to prevent equivocation)
395
+ this.lastAttestedProposal = proposal;
396
+ await this.p2pClient.addOwnCheckpointAttestations(attestations);
345
397
  return attestations;
346
398
  }
347
399
  /**
@@ -349,7 +401,10 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
349
401
  * @returns Validation result with isValid flag and reason if invalid.
350
402
  */ async validateCheckpointProposal(proposal, proposalInfo) {
351
403
  const slot = proposal.slotNumber;
352
- const timeoutSeconds = 10; // TODO(palla/mbps): This should map to the timetable settings
404
+ // Timeout block syncing at the start of the next slot
405
+ const config = this.checkpointsBuilder.getConfig();
406
+ const nextSlotTimestampSeconds = Number(getTimestampForSlot(SlotNumber(slot + 1), config));
407
+ const timeoutSeconds = Math.max(1, nextSlotTimestampSeconds - Math.floor(this.dateProvider.now() / 1000));
353
408
  // Wait for last block to sync by archive
354
409
  let lastBlockHeader;
355
410
  try {
@@ -408,7 +463,7 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
408
463
  const fork = await this.worldState.fork(parentBlockNumber);
409
464
  try {
410
465
  // Create checkpoint builder with all existing blocks
411
- const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(checkpointNumber, constants, l1ToL2Messages, previousCheckpointOutHashes, fork, blocks, this.log.getBindings());
466
+ const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(checkpointNumber, constants, proposal.feeAssetPriceModifier, l1ToL2Messages, previousCheckpointOutHashes, fork, blocks, this.log.getBindings());
412
467
  // Complete the checkpoint to get computed values
413
468
  const computedCheckpoint = await checkpointBuilder.completeCheckpoint();
414
469
  // Compare checkpoint header with proposal
@@ -524,23 +579,75 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
524
579
  }
525
580
  ]);
526
581
  }
582
+ /**
583
+ * Handle detection of a duplicate proposal (equivocation).
584
+ * Emits a slash event when a proposer sends multiple proposals for the same position.
585
+ */ handleDuplicateProposal(info) {
586
+ const { slot, proposer, type } = info;
587
+ this.log.warn(`Triggering slash event for duplicate ${type} proposal from ${proposer.toString()} at slot ${slot}`, {
588
+ proposer: proposer.toString(),
589
+ slot,
590
+ type
591
+ });
592
+ // Emit slash event
593
+ this.emit(WANT_TO_SLASH_EVENT, [
594
+ {
595
+ validator: proposer,
596
+ amount: this.config.slashDuplicateProposalPenalty,
597
+ offenseType: OffenseType.DUPLICATE_PROPOSAL,
598
+ epochOrSlot: BigInt(slot)
599
+ }
600
+ ]);
601
+ }
602
+ /**
603
+ * Handle detection of a duplicate attestation (equivocation).
604
+ * Emits a slash event when an attester signs attestations for different proposals at the same slot.
605
+ */ handleDuplicateAttestation(info) {
606
+ const { slot, attester } = info;
607
+ this.log.warn(`Triggering slash event for duplicate attestation from ${attester.toString()} at slot ${slot}`, {
608
+ attester: attester.toString(),
609
+ slot
610
+ });
611
+ this.emit(WANT_TO_SLASH_EVENT, [
612
+ {
613
+ validator: attester,
614
+ amount: this.config.slashDuplicateAttestationPenalty,
615
+ offenseType: OffenseType.DUPLICATE_ATTESTATION,
616
+ epochOrSlot: BigInt(slot)
617
+ }
618
+ ]);
619
+ }
527
620
  async createBlockProposal(blockHeader, indexWithinCheckpoint, inHash, archive, txs, proposerAddress, options = {}) {
528
- // TODO(palla/mbps): Prevent double proposals properly
529
- // if (this.previousProposal?.slotNumber === blockHeader.globalVariables.slotNumber) {
530
- // this.log.verbose(`Already made a proposal for the same slot, skipping proposal`);
531
- // return Promise.resolve(undefined);
532
- // }
621
+ // Validate that we're not creating a proposal for an older or equal position
622
+ if (this.lastProposedBlock) {
623
+ const lastSlot = this.lastProposedBlock.slotNumber;
624
+ const lastIndex = this.lastProposedBlock.indexWithinCheckpoint;
625
+ const newSlot = blockHeader.globalVariables.slotNumber;
626
+ if (newSlot < lastSlot || newSlot === lastSlot && indexWithinCheckpoint <= lastIndex) {
627
+ throw new Error(`Cannot create block proposal for slot ${newSlot} index ${indexWithinCheckpoint}: ` + `already proposed block for slot ${lastSlot} index ${lastIndex}`);
628
+ }
629
+ }
533
630
  this.log.info(`Assembling block proposal for block ${blockHeader.globalVariables.blockNumber} slot ${blockHeader.globalVariables.slotNumber}`);
534
631
  const newProposal = await this.validationService.createBlockProposal(blockHeader, indexWithinCheckpoint, inHash, archive, txs, proposerAddress, {
535
632
  ...options,
536
633
  broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal
537
634
  });
538
- this.previousProposal = newProposal;
635
+ this.lastProposedBlock = newProposal;
539
636
  return newProposal;
540
637
  }
541
- async createCheckpointProposal(checkpointHeader, archive, lastBlockInfo, proposerAddress, options = {}) {
638
+ async createCheckpointProposal(checkpointHeader, archive, feeAssetPriceModifier, lastBlockInfo, proposerAddress, options = {}) {
639
+ // Validate that we're not creating a proposal for an older or equal slot
640
+ if (this.lastProposedCheckpoint) {
641
+ const lastSlot = this.lastProposedCheckpoint.slotNumber;
642
+ const newSlot = checkpointHeader.slotNumber;
643
+ if (newSlot <= lastSlot) {
644
+ throw new Error(`Cannot create checkpoint proposal for slot ${newSlot}: ` + `already proposed checkpoint for slot ${lastSlot}`);
645
+ }
646
+ }
542
647
  this.log.info(`Assembling checkpoint proposal for slot ${checkpointHeader.slotNumber}`);
543
- return await this.validationService.createCheckpointProposal(checkpointHeader, archive, lastBlockInfo, proposerAddress, options);
648
+ const newProposal = await this.validationService.createCheckpointProposal(checkpointHeader, archive, feeAssetPriceModifier, lastBlockInfo, proposerAddress, options);
649
+ this.lastProposedCheckpoint = newProposal;
650
+ return newProposal;
544
651
  }
545
652
  async broadcastBlockProposal(proposal) {
546
653
  await this.p2pClient.broadcastProposal(proposal);
@@ -555,6 +662,9 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
555
662
  inCommittee
556
663
  });
557
664
  const attestations = await this.createCheckpointAttestationsFromProposal(proposal, inCommittee);
665
+ if (!attestations) {
666
+ return [];
667
+ }
558
668
  // We broadcast our own attestations to our peers so, in case our block does not get mined on L1,
559
669
  // other nodes can see that our validators did attest to this block proposal, and do not slash us
560
670
  // due to inactivity for missed attestations.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/validator-client",
3
- "version": "0.0.1-commit.1a99e26c",
3
+ "version": "0.0.1-commit.1bb068fb5",
4
4
  "main": "dest/index.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -64,30 +64,30 @@
64
64
  ]
65
65
  },
66
66
  "dependencies": {
67
- "@aztec/blob-client": "0.0.1-commit.1a99e26c",
68
- "@aztec/blob-lib": "0.0.1-commit.1a99e26c",
69
- "@aztec/constants": "0.0.1-commit.1a99e26c",
70
- "@aztec/epoch-cache": "0.0.1-commit.1a99e26c",
71
- "@aztec/ethereum": "0.0.1-commit.1a99e26c",
72
- "@aztec/foundation": "0.0.1-commit.1a99e26c",
73
- "@aztec/node-keystore": "0.0.1-commit.1a99e26c",
74
- "@aztec/noir-protocol-circuits-types": "0.0.1-commit.1a99e26c",
75
- "@aztec/p2p": "0.0.1-commit.1a99e26c",
76
- "@aztec/protocol-contracts": "0.0.1-commit.1a99e26c",
77
- "@aztec/prover-client": "0.0.1-commit.1a99e26c",
78
- "@aztec/simulator": "0.0.1-commit.1a99e26c",
79
- "@aztec/slasher": "0.0.1-commit.1a99e26c",
80
- "@aztec/stdlib": "0.0.1-commit.1a99e26c",
81
- "@aztec/telemetry-client": "0.0.1-commit.1a99e26c",
82
- "@aztec/validator-ha-signer": "0.0.1-commit.1a99e26c",
67
+ "@aztec/blob-client": "0.0.1-commit.1bb068fb5",
68
+ "@aztec/blob-lib": "0.0.1-commit.1bb068fb5",
69
+ "@aztec/constants": "0.0.1-commit.1bb068fb5",
70
+ "@aztec/epoch-cache": "0.0.1-commit.1bb068fb5",
71
+ "@aztec/ethereum": "0.0.1-commit.1bb068fb5",
72
+ "@aztec/foundation": "0.0.1-commit.1bb068fb5",
73
+ "@aztec/node-keystore": "0.0.1-commit.1bb068fb5",
74
+ "@aztec/noir-protocol-circuits-types": "0.0.1-commit.1bb068fb5",
75
+ "@aztec/p2p": "0.0.1-commit.1bb068fb5",
76
+ "@aztec/protocol-contracts": "0.0.1-commit.1bb068fb5",
77
+ "@aztec/prover-client": "0.0.1-commit.1bb068fb5",
78
+ "@aztec/simulator": "0.0.1-commit.1bb068fb5",
79
+ "@aztec/slasher": "0.0.1-commit.1bb068fb5",
80
+ "@aztec/stdlib": "0.0.1-commit.1bb068fb5",
81
+ "@aztec/telemetry-client": "0.0.1-commit.1bb068fb5",
82
+ "@aztec/validator-ha-signer": "0.0.1-commit.1bb068fb5",
83
83
  "koa": "^2.16.1",
84
84
  "koa-router": "^13.1.1",
85
85
  "tslib": "^2.4.0",
86
86
  "viem": "npm:@aztec/viem@2.38.2"
87
87
  },
88
88
  "devDependencies": {
89
- "@aztec/archiver": "0.0.1-commit.1a99e26c",
90
- "@aztec/world-state": "0.0.1-commit.1a99e26c",
89
+ "@aztec/archiver": "0.0.1-commit.1bb068fb5",
90
+ "@aztec/world-state": "0.0.1-commit.1bb068fb5",
91
91
  "@electric-sql/pglite": "^0.3.14",
92
92
  "@jest/globals": "^30.0.0",
93
93
  "@types/jest": "^30.0.0",
@@ -491,6 +491,7 @@ export class BlockProposalHandler {
491
491
  const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(
492
492
  checkpointNumber,
493
493
  constants,
494
+ 0n, // only takes effect in the following checkpoint.
494
495
  l1ToL2Messages,
495
496
  previousCheckpointOutHashes,
496
497
  fork,
@@ -3,7 +3,7 @@ import { merge, pick } from '@aztec/foundation/collection';
3
3
  import { Fr } from '@aztec/foundation/curves/bn254';
4
4
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
5
5
  import { bufferToHex } from '@aztec/foundation/string';
6
- import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
6
+ import { DateProvider, elapsed } from '@aztec/foundation/timer';
7
7
  import { getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
8
8
  import { LightweightCheckpointBuilder } from '@aztec/prover-client/light';
9
9
  import {
@@ -24,6 +24,7 @@ import {
24
24
  type ICheckpointBlockBuilder,
25
25
  type ICheckpointsBuilder,
26
26
  type MerkleTreeWriteOperations,
27
+ NoValidTxsError,
27
28
  type PublicProcessorLimits,
28
29
  type WorldStateSynchronizer,
29
30
  } from '@aztec/stdlib/interfaces/server';
@@ -36,11 +37,6 @@ import { createValidatorForBlockBuilding } from './tx_validator/tx_validator_fac
36
37
  // Re-export for backward compatibility
37
38
  export type { BuildBlockInCheckpointResult } from '@aztec/stdlib/interfaces/server';
38
39
 
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
40
  /**
45
41
  * Builder for a single checkpoint. Handles building blocks within the checkpoint
46
42
  * and completing it.
@@ -75,8 +71,7 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
75
71
  blockNumber: BlockNumber,
76
72
  timestamp: bigint,
77
73
  opts: PublicProcessorLimits & { expectedEndState?: StateReference } = {},
78
- ): Promise<BuildBlockInCheckpointResultWithTimer> {
79
- const blockBuildingTimer = new Timer();
74
+ ): Promise<BuildBlockInCheckpointResult> {
80
75
  const slot = this.checkpointBuilder.constants.slotNumber;
81
76
 
82
77
  this.log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
@@ -103,6 +98,12 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
103
98
  processor.process(pendingTxs, opts, validator),
104
99
  );
105
100
 
101
+ // Throw if we didn't collect a single valid tx and we're not allowed to build empty blocks
102
+ // (only the first block in a checkpoint can be empty)
103
+ if (processedTxs.length === 0 && this.checkpointBuilder.getBlockCount() > 0) {
104
+ throw new NoValidTxsError(failedTxs);
105
+ }
106
+
106
107
  // Add block to checkpoint
107
108
  const block = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
108
109
  expectedEndState: opts.expectedEndState,
@@ -111,18 +112,21 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
111
112
  // How much public gas was processed
112
113
  const publicGas = processedTxs.reduce((acc, tx) => acc.add(tx.gasUsed.publicGas), Gas.empty());
113
114
 
114
- const res = {
115
+ this.log.debug('Built block within checkpoint', {
116
+ header: block.header.toInspect(),
117
+ processedTxs: processedTxs.map(tx => tx.hash.toString()),
118
+ failedTxs: failedTxs.map(tx => tx.tx.txHash.toString()),
119
+ });
120
+
121
+ return {
115
122
  block,
116
123
  publicGas,
117
124
  publicProcessorDuration,
118
125
  numTxs: processedTxs.length,
119
126
  failedTxs,
120
- blockBuildingTimer,
121
127
  usedTxs,
122
128
  usedTxBlobFields,
123
129
  };
124
- this.log.debug('Built block within checkpoint', res.block.header);
125
- return res;
126
130
  }
127
131
 
128
132
  /** Completes the checkpoint and returns it. */
@@ -211,6 +215,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
211
215
  async startCheckpoint(
212
216
  checkpointNumber: CheckpointNumber,
213
217
  constants: CheckpointGlobalVariables,
218
+ feeAssetPriceModifier: bigint,
214
219
  l1ToL2Messages: Fr[],
215
220
  previousCheckpointOutHashes: Fr[],
216
221
  fork: MerkleTreeWriteOperations,
@@ -225,6 +230,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
225
230
  initialStateReference: stateReference.toInspect(),
226
231
  initialArchiveRoot: bufferToHex(archiveTree.root),
227
232
  constants,
233
+ feeAssetPriceModifier,
228
234
  });
229
235
 
230
236
  const lightweightBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
@@ -234,6 +240,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
234
240
  previousCheckpointOutHashes,
235
241
  fork,
236
242
  bindings,
243
+ feeAssetPriceModifier,
237
244
  );
238
245
 
239
246
  return new CheckpointBuilder(
@@ -253,6 +260,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
253
260
  async openCheckpoint(
254
261
  checkpointNumber: CheckpointNumber,
255
262
  constants: CheckpointGlobalVariables,
263
+ feeAssetPriceModifier: bigint,
256
264
  l1ToL2Messages: Fr[],
257
265
  previousCheckpointOutHashes: Fr[],
258
266
  fork: MerkleTreeWriteOperations,
@@ -266,6 +274,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
266
274
  return this.startCheckpoint(
267
275
  checkpointNumber,
268
276
  constants,
277
+ feeAssetPriceModifier,
269
278
  l1ToL2Messages,
270
279
  previousCheckpointOutHashes,
271
280
  fork,
@@ -280,11 +289,13 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
280
289
  initialStateReference: stateReference.toInspect(),
281
290
  initialArchiveRoot: bufferToHex(archiveTree.root),
282
291
  constants,
292
+ feeAssetPriceModifier,
283
293
  });
284
294
 
285
295
  const lightweightBuilder = await LightweightCheckpointBuilder.resumeCheckpoint(
286
296
  checkpointNumber,
287
297
  constants,
298
+ feeAssetPriceModifier,
288
299
  l1ToL2Messages,
289
300
  previousCheckpointOutHashes,
290
301
  fork,
package/src/config.ts CHANGED
@@ -73,6 +73,10 @@ 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
+ },
76
80
  ...validatorHASignerConfigMappings,
77
81
  };
78
82
 
@@ -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,7 +144,7 @@ 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
  );
@@ -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
  /**