@aztec/validator-client 4.0.0-nightly.20260112 → 4.0.0-nightly.20260113

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 (42) hide show
  1. package/README.md +256 -0
  2. package/dest/block_proposal_handler.d.ts +20 -10
  3. package/dest/block_proposal_handler.d.ts.map +1 -1
  4. package/dest/block_proposal_handler.js +333 -72
  5. package/dest/checkpoint_builder.d.ts +70 -0
  6. package/dest/checkpoint_builder.d.ts.map +1 -0
  7. package/dest/checkpoint_builder.js +155 -0
  8. package/dest/config.d.ts +1 -1
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +10 -0
  11. package/dest/duties/validation_service.d.ts +26 -10
  12. package/dest/duties/validation_service.d.ts.map +1 -1
  13. package/dest/duties/validation_service.js +51 -21
  14. package/dest/factory.d.ts +10 -7
  15. package/dest/factory.d.ts.map +1 -1
  16. package/dest/factory.js +2 -2
  17. package/dest/index.d.ts +3 -1
  18. package/dest/index.d.ts.map +1 -1
  19. package/dest/index.js +2 -0
  20. package/dest/tx_validator/index.d.ts +3 -0
  21. package/dest/tx_validator/index.d.ts.map +1 -0
  22. package/dest/tx_validator/index.js +2 -0
  23. package/dest/tx_validator/nullifier_cache.d.ts +14 -0
  24. package/dest/tx_validator/nullifier_cache.d.ts.map +1 -0
  25. package/dest/tx_validator/nullifier_cache.js +24 -0
  26. package/dest/tx_validator/tx_validator_factory.d.ts +18 -0
  27. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -0
  28. package/dest/tx_validator/tx_validator_factory.js +53 -0
  29. package/dest/validator.d.ts +39 -15
  30. package/dest/validator.d.ts.map +1 -1
  31. package/dest/validator.js +297 -449
  32. package/package.json +16 -12
  33. package/src/block_proposal_handler.ts +249 -39
  34. package/src/checkpoint_builder.ts +267 -0
  35. package/src/config.ts +10 -0
  36. package/src/duties/validation_service.ts +79 -25
  37. package/src/factory.ts +13 -8
  38. package/src/index.ts +2 -0
  39. package/src/tx_validator/index.ts +2 -0
  40. package/src/tx_validator/nullifier_cache.ts +30 -0
  41. package/src/tx_validator/tx_validator_factory.ts +133 -0
  42. package/src/validator.ts +400 -94
@@ -1,3 +1,68 @@
1
+ function _ts_add_disposable_resource(env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() {
16
+ try {
17
+ inner.call(this);
18
+ } catch (e) {
19
+ return Promise.reject(e);
20
+ }
21
+ };
22
+ env.stack.push({
23
+ value: value,
24
+ dispose: dispose,
25
+ async: async
26
+ });
27
+ } else if (async) {
28
+ env.stack.push({
29
+ async: true
30
+ });
31
+ }
32
+ return value;
33
+ }
34
+ function _ts_dispose_resources(env) {
35
+ var _SuppressedError = typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
36
+ var e = new Error(message);
37
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
38
+ };
39
+ return (_ts_dispose_resources = function _ts_dispose_resources(env) {
40
+ function fail(e) {
41
+ env.error = env.hasError ? new _SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
42
+ env.hasError = true;
43
+ }
44
+ var r, s = 0;
45
+ function next() {
46
+ while(r = env.stack.pop()){
47
+ try {
48
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
49
+ if (r.dispose) {
50
+ var result = r.dispose.call(r.value);
51
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
52
+ fail(e);
53
+ return next();
54
+ });
55
+ } else s |= 1;
56
+ } catch (e) {
57
+ fail(e);
58
+ }
59
+ }
60
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
61
+ if (env.hasError) throw env.error;
62
+ }
63
+ return next();
64
+ })(env);
65
+ }
1
66
  import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
67
  import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
3
68
  import { Fr } from '@aztec/foundation/curves/bn254';
@@ -7,12 +72,11 @@ import { retryUntil } from '@aztec/foundation/retry';
7
72
  import { DateProvider, Timer } from '@aztec/foundation/timer';
8
73
  import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
9
74
  import { computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
10
- import { ConsensusPayload } from '@aztec/stdlib/p2p';
11
- import { GlobalVariables } from '@aztec/stdlib/tx';
12
75
  import { ReExFailedTxsError, ReExStateMismatchError, ReExTimeoutError, TransactionsNotAvailableError } from '@aztec/stdlib/validators';
13
76
  import { getTelemetryClient } from '@aztec/telemetry-client';
14
77
  export class BlockProposalHandler {
15
- blockBuilder;
78
+ checkpointsBuilder;
79
+ worldState;
16
80
  blockSource;
17
81
  l1ToL2MessageSource;
18
82
  txProvider;
@@ -22,8 +86,9 @@ export class BlockProposalHandler {
22
86
  dateProvider;
23
87
  log;
24
88
  tracer;
25
- constructor(blockBuilder, blockSource, l1ToL2MessageSource, txProvider, blockProposalValidator, config, metrics, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator:block-proposal-handler')){
26
- this.blockBuilder = blockBuilder;
89
+ constructor(checkpointsBuilder, worldState, blockSource, l1ToL2MessageSource, txProvider, blockProposalValidator, config, metrics, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator:block-proposal-handler')){
90
+ this.checkpointsBuilder = checkpointsBuilder;
91
+ this.worldState = worldState;
27
92
  this.blockSource = blockSource;
28
93
  this.l1ToL2MessageSource = l1ToL2MessageSource;
29
94
  this.txProvider = txProvider;
@@ -38,6 +103,8 @@ export class BlockProposalHandler {
38
103
  this.tracer = telemetry.getTracer('BlockProposalHandler');
39
104
  }
40
105
  registerForReexecution(p2pClient) {
106
+ // Non-validator handler that re-executes for monitoring but does not attest.
107
+ // Returns boolean indicating whether the proposal was valid.
41
108
  const handler = async (proposal, proposalSender)=>{
42
109
  try {
43
110
  const result = await this.handleBlockProposal(proposal, proposalSender, true);
@@ -48,16 +115,18 @@ export class BlockProposalHandler {
48
115
  totalManaUsed: result.reexecutionResult?.totalManaUsed,
49
116
  numTxs: result.reexecutionResult?.block?.body?.txEffects?.length ?? 0
50
117
  });
118
+ return true;
51
119
  } else {
52
120
  this.log.warn(`Non-validator reexecution failed for slot ${proposal.slotNumber}`, {
53
121
  blockNumber: result.blockNumber,
54
122
  reason: result.reason
55
123
  });
124
+ return false;
56
125
  }
57
126
  } catch (error) {
58
127
  this.log.error('Error processing block proposal in non-validator handler', error);
128
+ return false;
59
129
  }
60
- return undefined; // Non-validator nodes don't return attestations
61
130
  };
62
131
  p2pClient.registerBlockProposalHandler(handler);
63
132
  return this;
@@ -65,7 +134,7 @@ export class BlockProposalHandler {
65
134
  async handleBlockProposal(proposal, proposalSender, shouldReexecute) {
66
135
  const slotNumber = proposal.slotNumber;
67
136
  const proposer = proposal.getSender();
68
- const config = this.blockBuilder.getConfig();
137
+ const config = this.checkpointsBuilder.getConfig();
69
138
  // Reject proposals with invalid signatures
70
139
  if (!proposer) {
71
140
  this.log.warn(`Received proposal with invalid signature for slot ${slotNumber}`);
@@ -131,10 +200,20 @@ export class BlockProposalHandler {
131
200
  pinnedPeer: proposalSender,
132
201
  deadline: this.getReexecutionDeadline(slotNumber, config)
133
202
  });
203
+ // Compute the checkpoint number for this block and validate checkpoint consistency
204
+ const checkpointResult = await this.computeCheckpointNumber(proposal, parentBlockHeader, proposalInfo);
205
+ if (checkpointResult.reason) {
206
+ return {
207
+ isValid: false,
208
+ blockNumber,
209
+ reason: checkpointResult.reason
210
+ };
211
+ }
212
+ const checkpointNumber = checkpointResult.checkpointNumber;
134
213
  // Check that I have the same set of l1ToL2Messages as the proposal
135
- const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(CheckpointNumber.fromBlockNumber(blockNumber));
214
+ const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
136
215
  const computedInHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
137
- const proposalInHash = proposal.payload.header.inHash;
216
+ const proposalInHash = proposal.inHash;
138
217
  if (!computedInHash.equals(proposalInHash)) {
139
218
  this.log.warn(`L1 to L2 messages in hash mismatch, skipping processing`, {
140
219
  proposalInHash: proposalInHash.toString(),
@@ -164,7 +243,7 @@ export class BlockProposalHandler {
164
243
  if (shouldReexecute) {
165
244
  try {
166
245
  this.log.verbose(`Re-executing transactions in the proposal`, proposalInfo);
167
- reexecutionResult = await this.reexecuteTransactions(proposal, blockNumber, txs, l1ToL2Messages);
246
+ reexecutionResult = await this.reexecuteTransactions(proposal, blockNumber, checkpointNumber, txs, l1ToL2Messages);
168
247
  } catch (error) {
169
248
  this.log.error(`Error reexecuting txs while processing block proposal`, error, proposalInfo);
170
249
  const reason = this.getReexecuteFailureReason(error);
@@ -176,7 +255,12 @@ export class BlockProposalHandler {
176
255
  };
177
256
  }
178
257
  }
179
- this.log.info(`Successfully processed proposal for slot ${slotNumber}`, proposalInfo);
258
+ // If we succeeded, push this block into the archiver (unless disabled)
259
+ // TODO(palla/mbps): Change default to false once block sync is stable.
260
+ if (reexecutionResult?.block && this.config.skipPushProposedBlocksToArchiver === false) {
261
+ await this.blockSource.addBlock(reexecutionResult?.block);
262
+ }
263
+ this.log.info(`Successfully processed block ${blockNumber} proposal at index ${proposal.indexWithinCheckpoint} on slot ${slotNumber}`, proposalInfo);
180
264
  return {
181
265
  isValid: true,
182
266
  blockNumber,
@@ -184,9 +268,9 @@ export class BlockProposalHandler {
184
268
  };
185
269
  }
186
270
  async getParentBlock(proposal) {
187
- const parentArchive = proposal.payload.header.lastArchiveRoot;
271
+ const parentArchive = proposal.blockHeader.lastArchive.root;
188
272
  const slot = proposal.slotNumber;
189
- const config = this.blockBuilder.getConfig();
273
+ const config = this.checkpointsBuilder.getConfig();
190
274
  const { genesisArchiveRoot } = await this.blockSource.getGenesisValues();
191
275
  if (parentArchive.equals(genesisArchiveRoot)) {
192
276
  return 'genesis';
@@ -209,11 +293,165 @@ export class BlockProposalHandler {
209
293
  return undefined;
210
294
  }
211
295
  }
296
+ async computeCheckpointNumber(proposal, parentBlockHeader, proposalInfo) {
297
+ if (parentBlockHeader === 'genesis') {
298
+ // First block is in checkpoint 1
299
+ if (proposal.indexWithinCheckpoint !== 0) {
300
+ this.log.warn(`First block proposal has non-zero indexWithinCheckpoint`, proposalInfo);
301
+ return {
302
+ reason: 'invalid_proposal'
303
+ };
304
+ }
305
+ return {
306
+ checkpointNumber: CheckpointNumber.INITIAL
307
+ };
308
+ }
309
+ // Get the parent block to find its checkpoint number
310
+ // TODO(palla/mbps): The block header should include the checkpoint number to avoid this lookup,
311
+ // or at least the L2BlockSource should return a different struct that includes it.
312
+ const parentBlockNumber = parentBlockHeader.getBlockNumber();
313
+ const parentBlock = await this.blockSource.getL2BlockNew(parentBlockNumber);
314
+ if (!parentBlock) {
315
+ this.log.warn(`Parent block ${parentBlockNumber} not found in archiver`, proposalInfo);
316
+ return {
317
+ reason: 'invalid_proposal'
318
+ };
319
+ }
320
+ if (proposal.indexWithinCheckpoint === 0) {
321
+ // If this is the first block in a new checkpoint, increment the checkpoint number
322
+ if (!(proposal.blockHeader.getSlot() > parentBlockHeader.getSlot())) {
323
+ this.log.warn(`Slot should be greater than parent block slot for first block in checkpoint`, proposalInfo);
324
+ return {
325
+ reason: 'invalid_proposal'
326
+ };
327
+ }
328
+ return {
329
+ checkpointNumber: CheckpointNumber(parentBlock.checkpointNumber + 1)
330
+ };
331
+ }
332
+ // Otherwise it should follow the previous block in the same checkpoint
333
+ if (proposal.indexWithinCheckpoint !== parentBlock.indexWithinCheckpoint + 1) {
334
+ this.log.warn(`Non-sequential indexWithinCheckpoint`, proposalInfo);
335
+ return {
336
+ reason: 'invalid_proposal'
337
+ };
338
+ }
339
+ if (proposal.blockHeader.getSlot() !== parentBlockHeader.getSlot()) {
340
+ this.log.warn(`Slot should be equal to parent block slot for non-first block in checkpoint`, proposalInfo);
341
+ return {
342
+ reason: 'invalid_proposal'
343
+ };
344
+ }
345
+ // For non-first blocks in a checkpoint, validate global variables match parent (except blockNumber)
346
+ const validationResult = this.validateNonFirstBlockInCheckpoint(proposal, parentBlock, proposalInfo);
347
+ if (validationResult) {
348
+ return validationResult;
349
+ }
350
+ return {
351
+ checkpointNumber: parentBlock.checkpointNumber
352
+ };
353
+ }
354
+ /**
355
+ * Validates that a non-first block in a checkpoint has consistent global variables with its parent.
356
+ * For blocks with indexWithinCheckpoint > 0, all global variables except blockNumber must match the parent.
357
+ * @returns A failure result if validation fails, undefined if validation passes
358
+ */ validateNonFirstBlockInCheckpoint(proposal, parentBlock, proposalInfo) {
359
+ const proposalGlobals = proposal.blockHeader.globalVariables;
360
+ const parentGlobals = parentBlock.header.globalVariables;
361
+ // All global variables except blockNumber should match the parent
362
+ // blockNumber naturally increments between blocks
363
+ if (!proposalGlobals.chainId.equals(parentGlobals.chainId)) {
364
+ this.log.warn(`Non-first block in checkpoint has mismatched chainId`, {
365
+ ...proposalInfo,
366
+ proposalChainId: proposalGlobals.chainId.toString(),
367
+ parentChainId: parentGlobals.chainId.toString()
368
+ });
369
+ return {
370
+ reason: 'global_variables_mismatch'
371
+ };
372
+ }
373
+ if (!proposalGlobals.version.equals(parentGlobals.version)) {
374
+ this.log.warn(`Non-first block in checkpoint has mismatched version`, {
375
+ ...proposalInfo,
376
+ proposalVersion: proposalGlobals.version.toString(),
377
+ parentVersion: parentGlobals.version.toString()
378
+ });
379
+ return {
380
+ reason: 'global_variables_mismatch'
381
+ };
382
+ }
383
+ if (proposalGlobals.slotNumber !== parentGlobals.slotNumber) {
384
+ this.log.warn(`Non-first block in checkpoint has mismatched slotNumber`, {
385
+ ...proposalInfo,
386
+ proposalSlotNumber: proposalGlobals.slotNumber,
387
+ parentSlotNumber: parentGlobals.slotNumber
388
+ });
389
+ return {
390
+ reason: 'global_variables_mismatch'
391
+ };
392
+ }
393
+ if (proposalGlobals.timestamp !== parentGlobals.timestamp) {
394
+ this.log.warn(`Non-first block in checkpoint has mismatched timestamp`, {
395
+ ...proposalInfo,
396
+ proposalTimestamp: proposalGlobals.timestamp.toString(),
397
+ parentTimestamp: parentGlobals.timestamp.toString()
398
+ });
399
+ return {
400
+ reason: 'global_variables_mismatch'
401
+ };
402
+ }
403
+ if (!proposalGlobals.coinbase.equals(parentGlobals.coinbase)) {
404
+ this.log.warn(`Non-first block in checkpoint has mismatched coinbase`, {
405
+ ...proposalInfo,
406
+ proposalCoinbase: proposalGlobals.coinbase.toString(),
407
+ parentCoinbase: parentGlobals.coinbase.toString()
408
+ });
409
+ return {
410
+ reason: 'global_variables_mismatch'
411
+ };
412
+ }
413
+ if (!proposalGlobals.feeRecipient.equals(parentGlobals.feeRecipient)) {
414
+ this.log.warn(`Non-first block in checkpoint has mismatched feeRecipient`, {
415
+ ...proposalInfo,
416
+ proposalFeeRecipient: proposalGlobals.feeRecipient.toString(),
417
+ parentFeeRecipient: parentGlobals.feeRecipient.toString()
418
+ });
419
+ return {
420
+ reason: 'global_variables_mismatch'
421
+ };
422
+ }
423
+ if (!proposalGlobals.gasFees.equals(parentGlobals.gasFees)) {
424
+ this.log.warn(`Non-first block in checkpoint has mismatched gasFees`, {
425
+ ...proposalInfo,
426
+ proposalGasFees: proposalGlobals.gasFees.toInspect(),
427
+ parentGasFees: parentGlobals.gasFees.toInspect()
428
+ });
429
+ return {
430
+ reason: 'global_variables_mismatch'
431
+ };
432
+ }
433
+ return undefined;
434
+ }
212
435
  getReexecutionDeadline(slot, config) {
213
436
  const nextSlotTimestampSeconds = Number(getTimestampForSlot(SlotNumber(slot + 1), config));
214
437
  const msNeededForPropagationAndPublishing = this.config.validatorReexecuteDeadlineMs;
215
438
  return new Date(nextSlotTimestampSeconds * 1000 - msNeededForPropagationAndPublishing);
216
439
  }
440
+ /**
441
+ * Gets all prior blocks in the same checkpoint (same slot and checkpoint number) up to but not including upToBlockNumber.
442
+ */ async getBlocksInCheckpoint(slot, upToBlockNumber, checkpointNumber) {
443
+ const blocks = [];
444
+ let currentBlockNumber = BlockNumber(upToBlockNumber - 1);
445
+ while(currentBlockNumber >= INITIAL_L2_BLOCK_NUM){
446
+ const block = await this.blockSource.getL2BlockNew(currentBlockNumber);
447
+ if (!block || block.header.getSlot() !== slot || block.checkpointNumber !== checkpointNumber) {
448
+ break;
449
+ }
450
+ blocks.unshift(block);
451
+ currentBlockNumber = BlockNumber(currentBlockNumber - 1);
452
+ }
453
+ return blocks;
454
+ }
217
455
  getReexecuteFailureReason(err) {
218
456
  if (err instanceof ReExStateMismatchError) {
219
457
  return 'state_mismatch';
@@ -225,66 +463,89 @@ export class BlockProposalHandler {
225
463
  return 'unknown_error';
226
464
  }
227
465
  }
228
- async reexecuteTransactions(proposal, blockNumber, txs, l1ToL2Messages) {
229
- const { header } = proposal.payload;
230
- const { txHashes } = proposal;
231
- // If we do not have all of the transactions, then we should fail
232
- if (txs.length !== txHashes.length) {
233
- const foundTxHashes = txs.map((tx)=>tx.getTxHash());
234
- const missingTxHashes = txHashes.filter((txHash)=>!foundTxHashes.includes(txHash));
235
- throw new TransactionsNotAvailableError(missingTxHashes);
236
- }
237
- // Use the sequencer's block building logic to re-execute the transactions
238
- const timer = new Timer();
239
- const config = this.blockBuilder.getConfig();
240
- // We source most global variables from the proposal
241
- const globalVariables = GlobalVariables.from({
242
- slotNumber: proposal.payload.header.slotNumber,
243
- coinbase: proposal.payload.header.coinbase,
244
- feeRecipient: proposal.payload.header.feeRecipient,
245
- gasFees: proposal.payload.header.gasFees,
246
- blockNumber,
247
- timestamp: header.timestamp,
248
- chainId: new Fr(config.l1ChainId),
249
- version: new Fr(config.rollupVersion)
250
- });
251
- const { block, failedTxs } = await this.blockBuilder.buildBlock(txs, l1ToL2Messages, globalVariables, {
252
- deadline: this.getReexecutionDeadline(proposal.payload.header.slotNumber, config)
253
- });
254
- const numFailedTxs = failedTxs.length;
255
- const slot = proposal.slotNumber;
256
- this.log.verbose(`Transaction re-execution complete for slot ${slot}`, {
257
- numFailedTxs,
258
- numProposalTxs: txHashes.length,
259
- numProcessedTxs: block.body.txEffects.length,
260
- slot
261
- });
262
- if (numFailedTxs > 0) {
263
- this.metrics?.recordFailedReexecution(proposal);
264
- throw new ReExFailedTxsError(numFailedTxs);
265
- }
266
- if (block.body.txEffects.length !== txHashes.length) {
267
- this.metrics?.recordFailedReexecution(proposal);
268
- throw new ReExTimeoutError();
269
- }
270
- // Throw a ReExStateMismatchError error if state updates do not match
271
- const blockPayload = ConsensusPayload.fromBlock(block);
272
- if (!blockPayload.equals(proposal.payload)) {
273
- this.log.warn(`Re-execution state mismatch for slot ${slot}`, {
274
- expected: blockPayload.toInspect(),
275
- actual: proposal.payload.toInspect()
466
+ async reexecuteTransactions(proposal, blockNumber, checkpointNumber, txs, l1ToL2Messages) {
467
+ const env = {
468
+ stack: [],
469
+ error: void 0,
470
+ hasError: false
471
+ };
472
+ try {
473
+ const { blockHeader, txHashes } = proposal;
474
+ // If we do not have all of the transactions, then we should fail
475
+ if (txs.length !== txHashes.length) {
476
+ const foundTxHashes = txs.map((tx)=>tx.getTxHash());
477
+ const missingTxHashes = txHashes.filter((txHash)=>!foundTxHashes.includes(txHash));
478
+ throw new TransactionsNotAvailableError(missingTxHashes);
479
+ }
480
+ const timer = new Timer();
481
+ const slot = proposal.slotNumber;
482
+ const config = this.checkpointsBuilder.getConfig();
483
+ // Get prior blocks in this checkpoint (same slot and checkpoint number)
484
+ const priorBlocks = await this.getBlocksInCheckpoint(slot, blockNumber, checkpointNumber);
485
+ // Fork before the block to be built
486
+ const parentBlockNumber = BlockNumber(blockNumber - 1);
487
+ const fork = _ts_add_disposable_resource(env, await this.worldState.fork(parentBlockNumber), false);
488
+ // Build checkpoint constants from proposal (excludes blockNumber and timestamp which are per-block)
489
+ const constants = {
490
+ chainId: new Fr(config.l1ChainId),
491
+ version: new Fr(config.rollupVersion),
492
+ slotNumber: slot,
493
+ coinbase: blockHeader.globalVariables.coinbase,
494
+ feeRecipient: blockHeader.globalVariables.feeRecipient,
495
+ gasFees: blockHeader.globalVariables.gasFees
496
+ };
497
+ // Create checkpoint builder with prior blocks
498
+ const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(checkpointNumber, constants, l1ToL2Messages, fork, priorBlocks);
499
+ // Build the new block
500
+ const deadline = this.getReexecutionDeadline(slot, config);
501
+ const result = await checkpointBuilder.buildBlock(txs, blockNumber, blockHeader.globalVariables.timestamp, {
502
+ deadline,
503
+ expectedEndState: blockHeader.state
276
504
  });
277
- this.metrics?.recordFailedReexecution(proposal);
278
- throw new ReExStateMismatchError(proposal.archive, block.archive.root);
505
+ const { block, failedTxs } = result;
506
+ const numFailedTxs = failedTxs.length;
507
+ this.log.verbose(`Transaction re-execution complete for slot ${slot}`, {
508
+ numFailedTxs,
509
+ numProposalTxs: txHashes.length,
510
+ numProcessedTxs: block.body.txEffects.length,
511
+ slot
512
+ });
513
+ if (numFailedTxs > 0) {
514
+ this.metrics?.recordFailedReexecution(proposal);
515
+ throw new ReExFailedTxsError(numFailedTxs);
516
+ }
517
+ if (block.body.txEffects.length !== txHashes.length) {
518
+ this.metrics?.recordFailedReexecution(proposal);
519
+ throw new ReExTimeoutError();
520
+ }
521
+ // Throw a ReExStateMismatchError error if state updates do not match
522
+ // Compare the full block structure (archive and header) from the built block with the proposal
523
+ const archiveMatches = proposal.archive.equals(block.archive.root);
524
+ const headerMatches = proposal.blockHeader.equals(block.header);
525
+ if (!archiveMatches || !headerMatches) {
526
+ this.log.warn(`Re-execution state mismatch for slot ${slot}`, {
527
+ expectedArchive: block.archive.root.toString(),
528
+ actualArchive: proposal.archive.toString(),
529
+ expectedHeader: block.header.toInspect(),
530
+ actualHeader: proposal.blockHeader.toInspect()
531
+ });
532
+ this.metrics?.recordFailedReexecution(proposal);
533
+ throw new ReExStateMismatchError(proposal.archive, block.archive.root);
534
+ }
535
+ const reexecutionTimeMs = timer.ms();
536
+ const totalManaUsed = block.header.totalManaUsed.toNumber() / 1e6;
537
+ this.metrics?.recordReex(reexecutionTimeMs, txs.length, totalManaUsed);
538
+ return {
539
+ block,
540
+ failedTxs,
541
+ reexecutionTimeMs,
542
+ totalManaUsed
543
+ };
544
+ } catch (e) {
545
+ env.error = e;
546
+ env.hasError = true;
547
+ } finally{
548
+ _ts_dispose_resources(env);
279
549
  }
280
- const reexecutionTimeMs = timer.ms();
281
- const totalManaUsed = block.header.totalManaUsed.toNumber() / 1e6;
282
- this.metrics?.recordReex(reexecutionTimeMs, txs.length, totalManaUsed);
283
- return {
284
- block,
285
- failedTxs,
286
- reexecutionTimeMs,
287
- totalManaUsed
288
- };
289
550
  }
290
551
  }
@@ -0,0 +1,70 @@
1
+ import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
2
+ import { Fr } from '@aztec/foundation/curves/bn254';
3
+ import { DateProvider, Timer } from '@aztec/foundation/timer';
4
+ import { LightweightCheckpointBuilder } from '@aztec/prover-client/light';
5
+ import { PublicProcessor } from '@aztec/simulator/server';
6
+ import { L2BlockNew } from '@aztec/stdlib/block';
7
+ import { Checkpoint } from '@aztec/stdlib/checkpoint';
8
+ import type { ContractDataSource } from '@aztec/stdlib/contract';
9
+ import { Gas } from '@aztec/stdlib/gas';
10
+ import { type FullNodeBlockBuilderConfig, type MerkleTreeWriteOperations, type PublicProcessorLimits } from '@aztec/stdlib/interfaces/server';
11
+ import { type CheckpointGlobalVariables, type FailedTx, GlobalVariables, StateReference, Tx } from '@aztec/stdlib/tx';
12
+ import { type TelemetryClient } from '@aztec/telemetry-client';
13
+ export interface BuildBlockInCheckpointResult {
14
+ block: L2BlockNew;
15
+ publicGas: Gas;
16
+ publicProcessorDuration: number;
17
+ numTxs: number;
18
+ failedTxs: FailedTx[];
19
+ blockBuildingTimer: Timer;
20
+ usedTxs: Tx[];
21
+ }
22
+ /**
23
+ * Builder for a single checkpoint. Handles building blocks within the checkpoint
24
+ * and completing it.
25
+ */
26
+ export declare class CheckpointBuilder {
27
+ private checkpointBuilder;
28
+ private fork;
29
+ private config;
30
+ private contractDataSource;
31
+ private dateProvider;
32
+ private telemetryClient;
33
+ constructor(checkpointBuilder: LightweightCheckpointBuilder, fork: MerkleTreeWriteOperations, config: FullNodeBlockBuilderConfig, contractDataSource: ContractDataSource, dateProvider: DateProvider, telemetryClient: TelemetryClient);
34
+ getConstantData(): CheckpointGlobalVariables;
35
+ /**
36
+ * Builds a single block within this checkpoint.
37
+ */
38
+ buildBlock(pendingTxs: Iterable<Tx> | AsyncIterable<Tx>, blockNumber: BlockNumber, timestamp: bigint, opts: PublicProcessorLimits & {
39
+ expectedEndState?: StateReference;
40
+ }): Promise<BuildBlockInCheckpointResult>;
41
+ /** Completes the checkpoint and returns it. */
42
+ completeCheckpoint(): Promise<Checkpoint>;
43
+ /** Gets the checkpoint currently in progress. */
44
+ getCheckpoint(): Promise<Checkpoint>;
45
+ protected makeBlockBuilderDeps(globalVariables: GlobalVariables, fork: MerkleTreeWriteOperations): Promise<{
46
+ processor: PublicProcessor;
47
+ validator: import("@aztec/stdlib/interfaces/server").PublicProcessorValidator;
48
+ }>;
49
+ }
50
+ /**
51
+ * Factory for creating checkpoint builders.
52
+ */
53
+ export declare class FullNodeCheckpointsBuilder {
54
+ private config;
55
+ private contractDataSource;
56
+ private dateProvider;
57
+ private telemetryClient;
58
+ constructor(config: FullNodeBlockBuilderConfig, contractDataSource: ContractDataSource, dateProvider: DateProvider, telemetryClient?: TelemetryClient);
59
+ getConfig(): FullNodeBlockBuilderConfig;
60
+ updateConfig(config: Partial<FullNodeBlockBuilderConfig>): void;
61
+ /**
62
+ * Starts a new checkpoint and returns a CheckpointBuilder to build blocks within it.
63
+ */
64
+ startCheckpoint(checkpointNumber: CheckpointNumber, constants: CheckpointGlobalVariables, l1ToL2Messages: Fr[], fork: MerkleTreeWriteOperations): Promise<CheckpointBuilder>;
65
+ /**
66
+ * Opens a checkpoint, either starting fresh or resuming from existing blocks.
67
+ */
68
+ openCheckpoint(checkpointNumber: CheckpointNumber, constants: CheckpointGlobalVariables, l1ToL2Messages: Fr[], fork: MerkleTreeWriteOperations, existingBlocks?: L2BlockNew[]): Promise<CheckpointBuilder>;
69
+ }
70
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2twb2ludF9idWlsZGVyLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY2hlY2twb2ludF9idWlsZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxXQUFXLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUVoRixPQUFPLEVBQUUsRUFBRSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFHcEQsT0FBTyxFQUFFLFlBQVksRUFBRSxLQUFLLEVBQVcsTUFBTSx5QkFBeUIsQ0FBQztBQUV2RSxPQUFPLEVBQUUsNEJBQTRCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUMxRSxPQUFPLEVBR0wsZUFBZSxFQUVoQixNQUFNLHlCQUF5QixDQUFDO0FBQ2pDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUNqRCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDdEQsT0FBTyxLQUFLLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUNqRSxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDeEMsT0FBTyxFQUNMLEtBQUssMEJBQTBCLEVBRS9CLEtBQUsseUJBQXlCLEVBQzlCLEtBQUsscUJBQXFCLEVBQzNCLE1BQU0saUNBQWlDLENBQUM7QUFFekMsT0FBTyxFQUFFLEtBQUsseUJBQXlCLEVBQUUsS0FBSyxRQUFRLEVBQUUsZUFBZSxFQUFFLGNBQWMsRUFBRSxFQUFFLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUN0SCxPQUFPLEVBQUUsS0FBSyxlQUFlLEVBQXNCLE1BQU0seUJBQXlCLENBQUM7QUFNbkYsTUFBTSxXQUFXLDRCQUE0QjtJQUMzQyxLQUFLLEVBQUUsVUFBVSxDQUFDO0lBQ2xCLFNBQVMsRUFBRSxHQUFHLENBQUM7SUFDZix1QkFBdUIsRUFBRSxNQUFNLENBQUM7SUFDaEMsTUFBTSxFQUFFLE1BQU0sQ0FBQztJQUNmLFNBQVMsRUFBRSxRQUFRLEVBQUUsQ0FBQztJQUN0QixrQkFBa0IsRUFBRSxLQUFLLENBQUM7SUFDMUIsT0FBTyxFQUFFLEVBQUUsRUFBRSxDQUFDO0NBQ2Y7QUFFRDs7O0dBR0c7QUFDSCxxQkFBYSxpQkFBaUI7SUFFMUIsT0FBTyxDQUFDLGlCQUFpQjtJQUN6QixPQUFPLENBQUMsSUFBSTtJQUNaLE9BQU8sQ0FBQyxNQUFNO0lBQ2QsT0FBTyxDQUFDLGtCQUFrQjtJQUMxQixPQUFPLENBQUMsWUFBWTtJQUNwQixPQUFPLENBQUMsZUFBZTtJQU56QixZQUNVLGlCQUFpQixFQUFFLDRCQUE0QixFQUMvQyxJQUFJLEVBQUUseUJBQXlCLEVBQy9CLE1BQU0sRUFBRSwwQkFBMEIsRUFDbEMsa0JBQWtCLEVBQUUsa0JBQWtCLEVBQ3RDLFlBQVksRUFBRSxZQUFZLEVBQzFCLGVBQWUsRUFBRSxlQUFlLEVBQ3RDO0lBRUosZUFBZSxJQUFJLHlCQUF5QixDQUUzQztJQUVEOztPQUVHO0lBQ0csVUFBVSxDQUNkLFVBQVUsRUFBRSxRQUFRLENBQUMsRUFBRSxDQUFDLEdBQUcsYUFBYSxDQUFDLEVBQUUsQ0FBQyxFQUM1QyxXQUFXLEVBQUUsV0FBVyxFQUN4QixTQUFTLEVBQUUsTUFBTSxFQUNqQixJQUFJLEVBQUUscUJBQXFCLEdBQUc7UUFBRSxnQkFBZ0IsQ0FBQyxFQUFFLGNBQWMsQ0FBQTtLQUFFLEdBQ2xFLE9BQU8sQ0FBQyw0QkFBNEIsQ0FBQyxDQTBDdkM7SUFFRCwrQ0FBK0M7SUFDekMsa0JBQWtCLElBQUksT0FBTyxDQUFDLFVBQVUsQ0FBQyxDQVU5QztJQUVELGlEQUFpRDtJQUNqRCxhQUFhLElBQUksT0FBTyxDQUFDLFVBQVUsQ0FBQyxDQUVuQztJQUVELFVBQWdCLG9CQUFvQixDQUFDLGVBQWUsRUFBRSxlQUFlLEVBQUUsSUFBSSxFQUFFLHlCQUF5Qjs7O09Ba0NyRztDQUNGO0FBRUQ7O0dBRUc7QUFDSCxxQkFBYSwwQkFBMEI7SUFFbkMsT0FBTyxDQUFDLE1BQU07SUFDZCxPQUFPLENBQUMsa0JBQWtCO0lBQzFCLE9BQU8sQ0FBQyxZQUFZO0lBQ3BCLE9BQU8sQ0FBQyxlQUFlO0lBSnpCLFlBQ1UsTUFBTSxFQUFFLDBCQUEwQixFQUNsQyxrQkFBa0IsRUFBRSxrQkFBa0IsRUFDdEMsWUFBWSxFQUFFLFlBQVksRUFDMUIsZUFBZSxHQUFFLGVBQXNDLEVBQzdEO0lBRUcsU0FBUyxJQUFJLDBCQUEwQixDQUU3QztJQUVNLFlBQVksQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLDBCQUEwQixDQUFDLFFBRTlEO0lBRUQ7O09BRUc7SUFDRyxlQUFlLENBQ25CLGdCQUFnQixFQUFFLGdCQUFnQixFQUNsQyxTQUFTLEVBQUUseUJBQXlCLEVBQ3BDLGNBQWMsRUFBRSxFQUFFLEVBQUUsRUFDcEIsSUFBSSxFQUFFLHlCQUF5QixHQUM5QixPQUFPLENBQUMsaUJBQWlCLENBQUMsQ0EyQjVCO0lBRUQ7O09BRUc7SUFDRyxjQUFjLENBQ2xCLGdCQUFnQixFQUFFLGdCQUFnQixFQUNsQyxTQUFTLEVBQUUseUJBQXlCLEVBQ3BDLGNBQWMsRUFBRSxFQUFFLEVBQUUsRUFDcEIsSUFBSSxFQUFFLHlCQUF5QixFQUMvQixjQUFjLEdBQUUsVUFBVSxFQUFPLEdBQ2hDLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBQyxDQWlDNUI7Q0FDRiJ9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint_builder.d.ts","sourceRoot":"","sources":["../src/checkpoint_builder.ts"],"names":[],"mappings":"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"}