@aztec/validator-client 0.0.1-commit.e61ad554 → 0.0.1-commit.ec5f612

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 (46) hide show
  1. package/README.md +21 -18
  2. package/dest/block_proposal_handler.d.ts +4 -5
  3. package/dest/block_proposal_handler.d.ts.map +1 -1
  4. package/dest/block_proposal_handler.js +23 -36
  5. package/dest/checkpoint_builder.d.ts +14 -12
  6. package/dest/checkpoint_builder.d.ts.map +1 -1
  7. package/dest/checkpoint_builder.js +51 -31
  8. package/dest/config.d.ts +1 -1
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +7 -4
  11. package/dest/duties/validation_service.d.ts +2 -2
  12. package/dest/duties/validation_service.d.ts.map +1 -1
  13. package/dest/duties/validation_service.js +3 -3
  14. package/dest/index.d.ts +1 -2
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +0 -1
  17. package/dest/key_store/ha_key_store.d.ts +1 -1
  18. package/dest/key_store/ha_key_store.d.ts.map +1 -1
  19. package/dest/key_store/ha_key_store.js +2 -2
  20. package/dest/metrics.d.ts +4 -3
  21. package/dest/metrics.d.ts.map +1 -1
  22. package/dest/metrics.js +34 -5
  23. package/dest/validator.d.ts +38 -14
  24. package/dest/validator.d.ts.map +1 -1
  25. package/dest/validator.js +182 -51
  26. package/package.json +19 -17
  27. package/src/block_proposal_handler.ts +34 -53
  28. package/src/checkpoint_builder.ts +74 -30
  29. package/src/config.ts +7 -4
  30. package/src/duties/validation_service.ts +9 -2
  31. package/src/index.ts +0 -1
  32. package/src/key_store/ha_key_store.ts +2 -2
  33. package/src/metrics.ts +45 -6
  34. package/src/validator.ts +236 -63
  35. package/dest/tx_validator/index.d.ts +0 -3
  36. package/dest/tx_validator/index.d.ts.map +0 -1
  37. package/dest/tx_validator/index.js +0 -2
  38. package/dest/tx_validator/nullifier_cache.d.ts +0 -14
  39. package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
  40. package/dest/tx_validator/nullifier_cache.js +0 -24
  41. package/dest/tx_validator/tx_validator_factory.d.ts +0 -18
  42. package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
  43. package/dest/tx_validator/tx_validator_factory.js +0 -54
  44. package/src/tx_validator/index.ts +0 -2
  45. package/src/tx_validator/nullifier_cache.ts +0 -30
  46. package/src/tx_validator/tx_validator_factory.ts +0 -135
@@ -1,25 +1,19 @@
1
1
  import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
2
  import type { EpochCache } from '@aztec/epoch-cache';
3
3
  import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
4
- import { chunkBy } from '@aztec/foundation/collection';
5
4
  import { Fr } from '@aztec/foundation/curves/bn254';
6
5
  import { TimeoutError } from '@aztec/foundation/error';
7
6
  import { createLogger } from '@aztec/foundation/log';
8
7
  import { retryUntil } from '@aztec/foundation/retry';
9
8
  import { DateProvider, Timer } from '@aztec/foundation/timer';
10
9
  import type { P2P, PeerId } from '@aztec/p2p';
11
- import { TxProvider } from '@aztec/p2p';
12
10
  import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
13
- import type { L2Block, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
11
+ import type { BlockData, L2Block, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
14
12
  import { getEpochAtSlot, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
15
- import type { ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
16
- import {
17
- type L1ToL2MessageSource,
18
- computeCheckpointOutHash,
19
- computeInHashFromL1ToL2Messages,
20
- } from '@aztec/stdlib/messaging';
13
+ import type { ITxProvider, ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
14
+ import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
21
15
  import type { BlockProposal } from '@aztec/stdlib/p2p';
22
- import { BlockHeader, type CheckpointGlobalVariables, type FailedTx, type Tx } from '@aztec/stdlib/tx';
16
+ import type { CheckpointGlobalVariables, FailedTx, Tx } from '@aztec/stdlib/tx';
23
17
  import {
24
18
  ReExFailedTxsError,
25
19
  ReExStateMismatchError,
@@ -78,7 +72,7 @@ export class BlockProposalHandler {
78
72
  private worldState: WorldStateSynchronizer,
79
73
  private blockSource: L2BlockSource & L2BlockSink,
80
74
  private l1ToL2MessageSource: L1ToL2MessageSource,
81
- private txProvider: TxProvider,
75
+ private txProvider: ITxProvider,
82
76
  private blockProposalValidator: BlockProposalValidator,
83
77
  private epochCache: EpochCache,
84
78
  private config: ValidatorClientFullConfig,
@@ -154,16 +148,16 @@ export class BlockProposalHandler {
154
148
  }
155
149
 
156
150
  // Check that the parent proposal is a block we know, otherwise reexecution would fail
157
- const parentBlockHeader = await this.getParentBlock(proposal);
158
- if (parentBlockHeader === undefined) {
151
+ const parentBlock = await this.getParentBlock(proposal);
152
+ if (parentBlock === undefined) {
159
153
  this.log.warn(`Parent block for proposal not found, skipping processing`, proposalInfo);
160
154
  return { isValid: false, reason: 'parent_block_not_found' };
161
155
  }
162
156
 
163
- // Check that the parent block's slot is less than the proposal's slot (should not happen, but we check anyway)
164
- if (parentBlockHeader !== 'genesis' && parentBlockHeader.getSlot() >= slotNumber) {
165
- this.log.warn(`Parent block slot is greater than or equal to proposal slot, skipping processing`, {
166
- parentBlockSlot: parentBlockHeader.getSlot().toString(),
157
+ // Check that the parent block's slot is not greater than the proposal's slot.
158
+ if (parentBlock !== 'genesis' && parentBlock.header.getSlot() > slotNumber) {
159
+ this.log.warn(`Parent block slot is greater than proposal slot, skipping processing`, {
160
+ parentBlockSlot: parentBlock.header.getSlot().toString(),
167
161
  proposalSlot: slotNumber.toString(),
168
162
  ...proposalInfo,
169
163
  });
@@ -172,9 +166,9 @@ export class BlockProposalHandler {
172
166
 
173
167
  // Compute the block number based on the parent block
174
168
  const blockNumber =
175
- parentBlockHeader === 'genesis'
169
+ parentBlock === 'genesis'
176
170
  ? BlockNumber(INITIAL_L2_BLOCK_NUM)
177
- : BlockNumber(parentBlockHeader.getBlockNumber() + 1);
171
+ : BlockNumber(parentBlock.header.getBlockNumber() + 1);
178
172
 
179
173
  // Check that this block number does not exist already
180
174
  const existingBlock = await this.blockSource.getBlockHeader(blockNumber);
@@ -191,7 +185,7 @@ export class BlockProposalHandler {
191
185
  });
192
186
 
193
187
  // Compute the checkpoint number for this block and validate checkpoint consistency
194
- const checkpointResult = await this.computeCheckpointNumber(proposal, parentBlockHeader, proposalInfo);
188
+ const checkpointResult = this.computeCheckpointNumber(proposal, parentBlock, proposalInfo);
195
189
  if (checkpointResult.reason) {
196
190
  return { isValid: false, blockNumber, reason: checkpointResult.reason };
197
191
  }
@@ -219,17 +213,11 @@ export class BlockProposalHandler {
219
213
  // Try re-executing the transactions in the proposal if needed
220
214
  let reexecutionResult;
221
215
  if (shouldReexecute) {
222
- // Compute the previous checkpoint out hashes for the epoch.
223
- // TODO(leila/mbps): There can be a more efficient way to get the previous checkpoint out
224
- // hashes without having to fetch all the blocks.
216
+ // Collect the out hashes of all the checkpoints before this one in the same epoch
225
217
  const epoch = getEpochAtSlot(slotNumber, this.epochCache.getL1Constants());
226
- const checkpointedBlocks = (await this.blockSource.getCheckpointedBlocksForEpoch(epoch))
227
- .filter(b => b.block.number < blockNumber)
228
- .sort((a, b) => a.block.number - b.block.number);
229
- const blocksByCheckpoint = chunkBy(checkpointedBlocks, b => b.checkpointNumber);
230
- const previousCheckpointOutHashes = blocksByCheckpoint.map(checkpointBlocks =>
231
- computeCheckpointOutHash(checkpointBlocks.map(b => b.block.body.txEffects.map(tx => tx.l2ToL1Msgs))),
232
- );
218
+ const previousCheckpointOutHashes = (await this.blockSource.getCheckpointsDataForEpoch(epoch))
219
+ .filter(c => c.checkpointNumber < checkpointNumber)
220
+ .map(c => c.checkpointOutHash);
233
221
 
234
222
  try {
235
223
  this.log.verbose(`Re-executing transactions in the proposal`, proposalInfo);
@@ -261,7 +249,7 @@ export class BlockProposalHandler {
261
249
  return { isValid: true, blockNumber, reexecutionResult };
262
250
  }
263
251
 
264
- private async getParentBlock(proposal: BlockProposal): Promise<'genesis' | BlockHeader | undefined> {
252
+ private async getParentBlock(proposal: BlockProposal): Promise<'genesis' | BlockData | undefined> {
265
253
  const parentArchive = proposal.blockHeader.lastArchive.root;
266
254
  const slot = proposal.slotNumber;
267
255
  const config = this.checkpointsBuilder.getConfig();
@@ -277,12 +265,11 @@ export class BlockProposalHandler {
277
265
 
278
266
  try {
279
267
  return (
280
- (await this.blockSource.getBlockHeaderByArchive(parentArchive)) ??
268
+ (await this.blockSource.getBlockDataByArchive(parentArchive)) ??
281
269
  (timeoutDurationMs <= 0
282
270
  ? undefined
283
271
  : await retryUntil(
284
- () =>
285
- this.blockSource.syncImmediate().then(() => this.blockSource.getBlockHeaderByArchive(parentArchive)),
272
+ () => this.blockSource.syncImmediate().then(() => this.blockSource.getBlockDataByArchive(parentArchive)),
286
273
  'force archiver sync',
287
274
  timeoutDurationMs / 1000,
288
275
  0.5,
@@ -298,12 +285,12 @@ export class BlockProposalHandler {
298
285
  }
299
286
  }
300
287
 
301
- private async computeCheckpointNumber(
288
+ private computeCheckpointNumber(
302
289
  proposal: BlockProposal,
303
- parentBlockHeader: 'genesis' | BlockHeader,
290
+ parentBlock: 'genesis' | BlockData,
304
291
  proposalInfo: object,
305
- ): Promise<CheckpointComputationResult> {
306
- if (parentBlockHeader === 'genesis') {
292
+ ): CheckpointComputationResult {
293
+ if (parentBlock === 'genesis') {
307
294
  // First block is in checkpoint 1
308
295
  if (proposal.indexWithinCheckpoint !== 0) {
309
296
  this.log.warn(`First block proposal has non-zero indexWithinCheckpoint`, proposalInfo);
@@ -312,19 +299,9 @@ export class BlockProposalHandler {
312
299
  return { checkpointNumber: CheckpointNumber.INITIAL };
313
300
  }
314
301
 
315
- // Get the parent block to find its checkpoint number
316
- // TODO(palla/mbps): The block header should include the checkpoint number to avoid this lookup,
317
- // or at least the L2BlockSource should return a different struct that includes it.
318
- const parentBlockNumber = parentBlockHeader.getBlockNumber();
319
- const parentBlock = await this.blockSource.getL2Block(parentBlockNumber);
320
- if (!parentBlock) {
321
- this.log.warn(`Parent block ${parentBlockNumber} not found in archiver`, proposalInfo);
322
- return { reason: 'invalid_proposal' };
323
- }
324
-
325
302
  if (proposal.indexWithinCheckpoint === 0) {
326
303
  // If this is the first block in a new checkpoint, increment the checkpoint number
327
- if (!(proposal.blockHeader.getSlot() > parentBlockHeader.getSlot())) {
304
+ if (!(proposal.blockHeader.getSlot() > parentBlock.header.getSlot())) {
328
305
  this.log.warn(`Slot should be greater than parent block slot for first block in checkpoint`, proposalInfo);
329
306
  return { reason: 'invalid_proposal' };
330
307
  }
@@ -336,7 +313,7 @@ export class BlockProposalHandler {
336
313
  this.log.warn(`Non-sequential indexWithinCheckpoint`, proposalInfo);
337
314
  return { reason: 'invalid_proposal' };
338
315
  }
339
- if (proposal.blockHeader.getSlot() !== parentBlockHeader.getSlot()) {
316
+ if (proposal.blockHeader.getSlot() !== parentBlock.header.getSlot()) {
340
317
  this.log.warn(`Slot should be equal to parent block slot for non-first block in checkpoint`, proposalInfo);
341
318
  return { reason: 'invalid_proposal' };
342
319
  }
@@ -357,7 +334,7 @@ export class BlockProposalHandler {
357
334
  */
358
335
  private validateNonFirstBlockInCheckpoint(
359
336
  proposal: BlockProposal,
360
- parentBlock: L2Block,
337
+ parentBlock: BlockData,
361
338
  proposalInfo: object,
362
339
  ): CheckpointComputationResult | undefined {
363
340
  const proposalGlobals = proposal.blockHeader.globalVariables;
@@ -475,13 +452,15 @@ export class BlockProposalHandler {
475
452
 
476
453
  // Fork before the block to be built
477
454
  const parentBlockNumber = BlockNumber(blockNumber - 1);
478
- using fork = await this.worldState.fork(parentBlockNumber);
455
+ await this.worldState.syncImmediate(parentBlockNumber);
456
+ await using fork = await this.worldState.fork(parentBlockNumber);
479
457
 
480
- // Build checkpoint constants from proposal (excludes blockNumber and timestamp which are per-block)
458
+ // Build checkpoint constants from proposal (excludes blockNumber which is per-block)
481
459
  const constants: CheckpointGlobalVariables = {
482
460
  chainId: new Fr(config.l1ChainId),
483
461
  version: new Fr(config.rollupVersion),
484
462
  slotNumber: slot,
463
+ timestamp: blockHeader.globalVariables.timestamp,
485
464
  coinbase: blockHeader.globalVariables.coinbase,
486
465
  feeRecipient: blockHeader.globalVariables.feeRecipient,
487
466
  gasFees: blockHeader.globalVariables.gasFees,
@@ -491,10 +470,12 @@ export class BlockProposalHandler {
491
470
  const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(
492
471
  checkpointNumber,
493
472
  constants,
473
+ 0n, // only takes effect in the following checkpoint.
494
474
  l1ToL2Messages,
495
475
  previousCheckpointOutHashes,
496
476
  fork,
497
477
  priorBlocks,
478
+ this.log.getBindings(),
498
479
  );
499
480
 
500
481
  // Build the new block
@@ -1,10 +1,10 @@
1
1
  import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
2
2
  import { merge, pick } from '@aztec/foundation/collection';
3
3
  import { Fr } from '@aztec/foundation/curves/bn254';
4
- import { createLogger } from '@aztec/foundation/log';
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';
7
- import { getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
6
+ import { DateProvider, elapsed } from '@aztec/foundation/timer';
7
+ import { createTxValidatorForBlockBuilding, getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
8
8
  import { LightweightCheckpointBuilder } from '@aztec/prover-client/light';
9
9
  import {
10
10
  GuardedMerkleTreeOperations,
@@ -24,30 +24,25 @@ 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';
31
+ import { type DebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
30
32
  import { MerkleTreeId } from '@aztec/stdlib/trees';
31
33
  import { type CheckpointGlobalVariables, GlobalVariables, StateReference, Tx } from '@aztec/stdlib/tx';
32
34
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
33
35
 
34
- import { createValidatorForBlockBuilding } from './tx_validator/tx_validator_factory.js';
35
-
36
36
  // Re-export for backward compatibility
37
37
  export type { BuildBlockInCheckpointResult } from '@aztec/stdlib/interfaces/server';
38
38
 
39
- const log = createLogger('checkpoint-builder');
40
-
41
- /** Result of building a block within a checkpoint. Extends the base interface with timer. */
42
- export interface BuildBlockInCheckpointResultWithTimer extends BuildBlockInCheckpointResult {
43
- blockBuildingTimer: Timer;
44
- }
45
-
46
39
  /**
47
40
  * Builder for a single checkpoint. Handles building blocks within the checkpoint
48
41
  * and completing it.
49
42
  */
50
43
  export class CheckpointBuilder implements ICheckpointBlockBuilder {
44
+ private log: Logger;
45
+
51
46
  constructor(
52
47
  private checkpointBuilder: LightweightCheckpointBuilder,
53
48
  private fork: MerkleTreeWriteOperations,
@@ -55,7 +50,14 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
55
50
  private contractDataSource: ContractDataSource,
56
51
  private dateProvider: DateProvider,
57
52
  private telemetryClient: TelemetryClient,
58
- ) {}
53
+ bindings?: LoggerBindings,
54
+ private debugLogStore: DebugLogStore = new NullDebugLogStore(),
55
+ ) {
56
+ this.log = createLogger('checkpoint-builder', {
57
+ ...bindings,
58
+ instanceId: `checkpoint-${checkpointBuilder.checkpointNumber}`,
59
+ });
60
+ }
59
61
 
60
62
  getConstantData(): CheckpointGlobalVariables {
61
63
  return this.checkpointBuilder.constants;
@@ -68,12 +70,11 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
68
70
  pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
69
71
  blockNumber: BlockNumber,
70
72
  timestamp: bigint,
71
- opts: PublicProcessorLimits & { expectedEndState?: StateReference },
72
- ): Promise<BuildBlockInCheckpointResultWithTimer> {
73
- const blockBuildingTimer = new Timer();
73
+ opts: PublicProcessorLimits & { expectedEndState?: StateReference } = {},
74
+ ): Promise<BuildBlockInCheckpointResult> {
74
75
  const slot = this.checkpointBuilder.constants.slotNumber;
75
76
 
76
- log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
77
+ this.log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
77
78
  slot,
78
79
  blockNumber,
79
80
  ...opts,
@@ -97,33 +98,42 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
97
98
  processor.process(pendingTxs, opts, validator),
98
99
  );
99
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
+
100
107
  // Add block to checkpoint
101
- const block = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
108
+ const { block } = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
102
109
  expectedEndState: opts.expectedEndState,
103
110
  });
104
111
 
105
112
  // How much public gas was processed
106
113
  const publicGas = processedTxs.reduce((acc, tx) => acc.add(tx.gasUsed.publicGas), Gas.empty());
107
114
 
108
- 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 {
109
122
  block,
110
123
  publicGas,
111
124
  publicProcessorDuration,
112
125
  numTxs: processedTxs.length,
113
126
  failedTxs,
114
- blockBuildingTimer,
115
127
  usedTxs,
116
128
  usedTxBlobFields,
117
129
  };
118
- log.debug('Built block within checkpoint', res.block.header);
119
- return res;
120
130
  }
121
131
 
122
132
  /** Completes the checkpoint and returns it. */
123
133
  async completeCheckpoint(): Promise<Checkpoint> {
124
134
  const checkpoint = await this.checkpointBuilder.completeCheckpoint();
125
135
 
126
- log.verbose(`Completed checkpoint ${checkpoint.number}`, {
136
+ this.log.verbose(`Completed checkpoint ${checkpoint.number}`, {
127
137
  checkpointNumber: checkpoint.number,
128
138
  numBlocks: checkpoint.blocks.length,
129
139
  archiveRoot: checkpoint.archive.root.toString(),
@@ -139,14 +149,19 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
139
149
 
140
150
  protected async makeBlockBuilderDeps(globalVariables: GlobalVariables, fork: MerkleTreeWriteOperations) {
141
151
  const txPublicSetupAllowList = this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
142
- const contractsDB = new PublicContractsDB(this.contractDataSource);
152
+ const contractsDB = new PublicContractsDB(this.contractDataSource, this.log.getBindings());
143
153
  const guardedFork = new GuardedMerkleTreeOperations(fork);
144
154
 
155
+ const collectDebugLogs = this.debugLogStore.isEnabled;
156
+
157
+ const bindings = this.log.getBindings();
145
158
  const publicTxSimulator = createPublicTxSimulatorForBlockBuilding(
146
159
  guardedFork,
147
160
  contractsDB,
148
161
  globalVariables,
149
162
  this.telemetryClient,
163
+ bindings,
164
+ collectDebugLogs,
150
165
  );
151
166
 
152
167
  const processor = new PublicProcessor(
@@ -156,15 +171,17 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
156
171
  publicTxSimulator,
157
172
  this.dateProvider,
158
173
  this.telemetryClient,
159
- undefined,
174
+ createLogger('simulator:public-processor', bindings),
160
175
  this.config,
176
+ this.debugLogStore,
161
177
  );
162
178
 
163
- const validator = createValidatorForBlockBuilding(
179
+ const validator = createTxValidatorForBlockBuilding(
164
180
  fork,
165
181
  this.contractDataSource,
166
182
  globalVariables,
167
183
  txPublicSetupAllowList,
184
+ this.log.getBindings(),
168
185
  );
169
186
 
170
187
  return {
@@ -176,13 +193,18 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder {
176
193
 
177
194
  /** Factory for creating checkpoint builders. */
178
195
  export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
196
+ private log: Logger;
197
+
179
198
  constructor(
180
199
  private config: FullNodeBlockBuilderConfig & Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration'>,
181
200
  private worldState: WorldStateSynchronizer,
182
201
  private contractDataSource: ContractDataSource,
183
202
  private dateProvider: DateProvider,
184
203
  private telemetryClient: TelemetryClient = getTelemetryClient(),
185
- ) {}
204
+ private debugLogStore: DebugLogStore = new NullDebugLogStore(),
205
+ ) {
206
+ this.log = createLogger('checkpoint-builder');
207
+ }
186
208
 
187
209
  public getConfig(): FullNodeBlockBuilderConfig {
188
210
  return this.config;
@@ -198,19 +220,22 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
198
220
  async startCheckpoint(
199
221
  checkpointNumber: CheckpointNumber,
200
222
  constants: CheckpointGlobalVariables,
223
+ feeAssetPriceModifier: bigint,
201
224
  l1ToL2Messages: Fr[],
202
225
  previousCheckpointOutHashes: Fr[],
203
226
  fork: MerkleTreeWriteOperations,
227
+ bindings?: LoggerBindings,
204
228
  ): Promise<CheckpointBuilder> {
205
229
  const stateReference = await fork.getStateReference();
206
230
  const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
207
231
 
208
- log.verbose(`Building new checkpoint ${checkpointNumber}`, {
232
+ this.log.verbose(`Building new checkpoint ${checkpointNumber}`, {
209
233
  checkpointNumber,
210
234
  msgCount: l1ToL2Messages.length,
211
235
  initialStateReference: stateReference.toInspect(),
212
236
  initialArchiveRoot: bufferToHex(archiveTree.root),
213
237
  constants,
238
+ feeAssetPriceModifier,
214
239
  });
215
240
 
216
241
  const lightweightBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
@@ -219,6 +244,8 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
219
244
  l1ToL2Messages,
220
245
  previousCheckpointOutHashes,
221
246
  fork,
247
+ bindings,
248
+ feeAssetPriceModifier,
222
249
  );
223
250
 
224
251
  return new CheckpointBuilder(
@@ -228,6 +255,8 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
228
255
  this.contractDataSource,
229
256
  this.dateProvider,
230
257
  this.telemetryClient,
258
+ bindings,
259
+ this.debugLogStore,
231
260
  );
232
261
  }
233
262
 
@@ -237,34 +266,47 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
237
266
  async openCheckpoint(
238
267
  checkpointNumber: CheckpointNumber,
239
268
  constants: CheckpointGlobalVariables,
269
+ feeAssetPriceModifier: bigint,
240
270
  l1ToL2Messages: Fr[],
241
271
  previousCheckpointOutHashes: Fr[],
242
272
  fork: MerkleTreeWriteOperations,
243
273
  existingBlocks: L2Block[] = [],
274
+ bindings?: LoggerBindings,
244
275
  ): Promise<CheckpointBuilder> {
245
276
  const stateReference = await fork.getStateReference();
246
277
  const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
247
278
 
248
279
  if (existingBlocks.length === 0) {
249
- return this.startCheckpoint(checkpointNumber, constants, l1ToL2Messages, previousCheckpointOutHashes, fork);
280
+ return this.startCheckpoint(
281
+ checkpointNumber,
282
+ constants,
283
+ feeAssetPriceModifier,
284
+ l1ToL2Messages,
285
+ previousCheckpointOutHashes,
286
+ fork,
287
+ bindings,
288
+ );
250
289
  }
251
290
 
252
- log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
291
+ this.log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
253
292
  checkpointNumber,
254
293
  msgCount: l1ToL2Messages.length,
255
294
  existingBlockCount: existingBlocks.length,
256
295
  initialStateReference: stateReference.toInspect(),
257
296
  initialArchiveRoot: bufferToHex(archiveTree.root),
258
297
  constants,
298
+ feeAssetPriceModifier,
259
299
  });
260
300
 
261
301
  const lightweightBuilder = await LightweightCheckpointBuilder.resumeCheckpoint(
262
302
  checkpointNumber,
263
303
  constants,
304
+ feeAssetPriceModifier,
264
305
  l1ToL2Messages,
265
306
  previousCheckpointOutHashes,
266
307
  fork,
267
308
  existingBlocks,
309
+ bindings,
268
310
  );
269
311
 
270
312
  return new CheckpointBuilder(
@@ -274,6 +316,8 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
274
316
  this.contractDataSource,
275
317
  this.dateProvider,
276
318
  this.telemetryClient,
319
+ bindings,
320
+ this.debugLogStore,
277
321
  );
278
322
  }
279
323
 
package/src/config.ts CHANGED
@@ -6,8 +6,8 @@ import {
6
6
  secretValueConfigHelper,
7
7
  } from '@aztec/foundation/config';
8
8
  import { EthAddress } from '@aztec/foundation/eth-address';
9
+ import { validatorHASignerConfigMappings } from '@aztec/stdlib/ha-signing';
9
10
  import type { ValidatorClientConfig } from '@aztec/stdlib/interfaces/server';
10
- import { validatorHASignerConfigMappings } from '@aztec/validator-ha-signer/config';
11
11
 
12
12
  export type { ValidatorClientConfig };
13
13
 
@@ -65,15 +65,18 @@ export const validatorClientConfigMappings: ConfigMappingsType<ValidatorClientCo
65
65
  'Whether to run in fisherman mode: validates all proposals and attestations but does not broadcast attestations or participate in consensus.',
66
66
  ...booleanConfigHelper(false),
67
67
  },
68
- // TODO(palla/mbps): Change default to false once checkpoint validation is stable
69
68
  skipCheckpointProposalValidation: {
70
- description: 'Skip checkpoint proposal validation and always attest (default: true)',
71
- defaultValue: true,
69
+ description: 'Skip checkpoint proposal validation and always attest (default: false)',
70
+ defaultValue: false,
72
71
  },
73
72
  skipPushProposedBlocksToArchiver: {
74
73
  description: 'Skip pushing re-executed blocks to archiver (default: false)',
75
74
  defaultValue: false,
76
75
  },
76
+ attestToEquivocatedProposals: {
77
+ description: 'Agree to attest to equivocated checkpoint proposals (for testing purposes only)',
78
+ ...booleanConfigHelper(false),
79
+ },
77
80
  ...validatorHASignerConfigMappings,
78
81
  };
79
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
  );
package/src/index.ts CHANGED
@@ -4,4 +4,3 @@ export * from './config.js';
4
4
  export * from './factory.js';
5
5
  export * from './validator.js';
6
6
  export * from './key_store/index.js';
7
- export * from './tx_validator/index.js';
@@ -256,8 +256,8 @@ export class HAKeyStore implements ExtendedValidatorKeyStore {
256
256
  /**
257
257
  * Start the high-availability key store
258
258
  */
259
- public start(): Promise<void> {
260
- return Promise.resolve(this.haSigner.start());
259
+ public async start() {
260
+ await this.haSigner.start();
261
261
  }
262
262
 
263
263
  /**
package/src/metrics.ts CHANGED
@@ -6,8 +6,11 @@ import {
6
6
  Metrics,
7
7
  type TelemetryClient,
8
8
  type UpDownCounter,
9
+ createUpDownCounterWithDefault,
9
10
  } from '@aztec/telemetry-client';
10
11
 
12
+ import type { BlockProposalValidationFailureReason } from './block_proposal_handler.js';
13
+
11
14
  export class ValidatorMetrics {
12
15
  private failedReexecutionCounter: UpDownCounter;
13
16
  private successfulAttestationsCount: UpDownCounter;
@@ -21,16 +24,44 @@ export class ValidatorMetrics {
21
24
  constructor(telemetryClient: TelemetryClient) {
22
25
  const meter = telemetryClient.getMeter('Validator');
23
26
 
24
- this.failedReexecutionCounter = meter.createUpDownCounter(Metrics.VALIDATOR_FAILED_REEXECUTION_COUNT);
27
+ this.failedReexecutionCounter = createUpDownCounterWithDefault(meter, Metrics.VALIDATOR_FAILED_REEXECUTION_COUNT, {
28
+ [Attributes.STATUS]: ['failed'],
29
+ });
25
30
 
26
- this.successfulAttestationsCount = meter.createUpDownCounter(Metrics.VALIDATOR_ATTESTATION_SUCCESS_COUNT);
31
+ this.successfulAttestationsCount = createUpDownCounterWithDefault(
32
+ meter,
33
+ Metrics.VALIDATOR_ATTESTATION_SUCCESS_COUNT,
34
+ );
27
35
 
28
- this.failedAttestationsBadProposalCount = meter.createUpDownCounter(
36
+ this.failedAttestationsBadProposalCount = createUpDownCounterWithDefault(
37
+ meter,
29
38
  Metrics.VALIDATOR_ATTESTATION_FAILED_BAD_PROPOSAL_COUNT,
39
+ {
40
+ [Attributes.ERROR_TYPE]: [
41
+ 'invalid_proposal',
42
+ 'state_mismatch',
43
+ 'failed_txs',
44
+ 'in_hash_mismatch',
45
+ 'parent_block_wrong_slot',
46
+ ],
47
+ [Attributes.IS_COMMITTEE_MEMBER]: [true, false],
48
+ },
30
49
  );
31
50
 
32
- this.failedAttestationsNodeIssueCount = meter.createUpDownCounter(
51
+ this.failedAttestationsNodeIssueCount = createUpDownCounterWithDefault(
52
+ meter,
33
53
  Metrics.VALIDATOR_ATTESTATION_FAILED_NODE_ISSUE_COUNT,
54
+ {
55
+ [Attributes.ERROR_TYPE]: [
56
+ 'parent_block_not_found',
57
+ 'global_variables_mismatch',
58
+ 'block_number_already_exists',
59
+ 'txs_not_available',
60
+ 'timeout',
61
+ 'unknown_error',
62
+ ],
63
+ [Attributes.IS_COMMITTEE_MEMBER]: [true, false],
64
+ },
34
65
  );
35
66
 
36
67
  this.reexMana = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_MANA);
@@ -58,14 +89,22 @@ export class ValidatorMetrics {
58
89
  this.successfulAttestationsCount.add(num);
59
90
  }
60
91
 
61
- public incFailedAttestationsBadProposal(num: number, reason: string, inCommittee: boolean) {
92
+ public incFailedAttestationsBadProposal(
93
+ num: number,
94
+ reason: BlockProposalValidationFailureReason,
95
+ inCommittee: boolean,
96
+ ) {
62
97
  this.failedAttestationsBadProposalCount.add(num, {
63
98
  [Attributes.ERROR_TYPE]: reason,
64
99
  [Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
65
100
  });
66
101
  }
67
102
 
68
- public incFailedAttestationsNodeIssue(num: number, reason: string, inCommittee: boolean) {
103
+ public incFailedAttestationsNodeIssue(
104
+ num: number,
105
+ reason: BlockProposalValidationFailureReason,
106
+ inCommittee: boolean,
107
+ ) {
69
108
  this.failedAttestationsNodeIssueCount.add(num, {
70
109
  [Attributes.ERROR_TYPE]: reason,
71
110
  [Attributes.IS_COMMITTEE_MEMBER]: inCommittee,