@aztec/sequencer-client 0.0.1-commit.f504929 → 0.0.1-commit.f650c0a5c

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 (59) hide show
  1. package/dest/client/sequencer-client.d.ts +4 -1
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +46 -27
  4. package/dest/config.d.ts +25 -5
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +21 -12
  7. package/dest/global_variable_builder/global_builder.d.ts +15 -9
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +29 -25
  10. package/dest/global_variable_builder/index.d.ts +2 -2
  11. package/dest/global_variable_builder/index.d.ts.map +1 -1
  12. package/dest/publisher/config.d.ts +13 -1
  13. package/dest/publisher/config.d.ts.map +1 -1
  14. package/dest/publisher/config.js +17 -2
  15. package/dest/publisher/sequencer-publisher-factory.d.ts +3 -5
  16. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  17. package/dest/publisher/sequencer-publisher-factory.js +2 -3
  18. package/dest/publisher/sequencer-publisher.d.ts +58 -32
  19. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  20. package/dest/publisher/sequencer-publisher.js +132 -89
  21. package/dest/sequencer/checkpoint_proposal_job.d.ts +36 -9
  22. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  23. package/dest/sequencer/checkpoint_proposal_job.js +302 -180
  24. package/dest/sequencer/checkpoint_voter.d.ts +1 -2
  25. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  26. package/dest/sequencer/checkpoint_voter.js +2 -5
  27. package/dest/sequencer/events.d.ts +2 -1
  28. package/dest/sequencer/events.d.ts.map +1 -1
  29. package/dest/sequencer/metrics.d.ts +5 -1
  30. package/dest/sequencer/metrics.d.ts.map +1 -1
  31. package/dest/sequencer/metrics.js +11 -0
  32. package/dest/sequencer/sequencer.d.ts +23 -10
  33. package/dest/sequencer/sequencer.d.ts.map +1 -1
  34. package/dest/sequencer/sequencer.js +130 -71
  35. package/dest/sequencer/timetable.d.ts +7 -3
  36. package/dest/sequencer/timetable.d.ts.map +1 -1
  37. package/dest/sequencer/timetable.js +21 -12
  38. package/dest/sequencer/types.d.ts +2 -2
  39. package/dest/sequencer/types.d.ts.map +1 -1
  40. package/dest/test/mock_checkpoint_builder.d.ts +7 -9
  41. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  42. package/dest/test/mock_checkpoint_builder.js +39 -30
  43. package/package.json +27 -28
  44. package/src/client/sequencer-client.ts +56 -28
  45. package/src/config.ts +28 -14
  46. package/src/global_variable_builder/global_builder.ts +37 -26
  47. package/src/global_variable_builder/index.ts +1 -1
  48. package/src/publisher/config.ts +32 -0
  49. package/src/publisher/sequencer-publisher-factory.ts +3 -6
  50. package/src/publisher/sequencer-publisher.ts +205 -127
  51. package/src/sequencer/README.md +81 -12
  52. package/src/sequencer/checkpoint_proposal_job.ts +385 -199
  53. package/src/sequencer/checkpoint_voter.ts +1 -12
  54. package/src/sequencer/events.ts +1 -1
  55. package/src/sequencer/metrics.ts +14 -0
  56. package/src/sequencer/sequencer.ts +186 -80
  57. package/src/sequencer/timetable.ts +26 -15
  58. package/src/sequencer/types.ts +1 -1
  59. package/src/test/mock_checkpoint_builder.ts +51 -48
@@ -379,17 +379,20 @@ import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from
379
379
  import { sumBigint } from '@aztec/foundation/bigint';
380
380
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
381
381
  import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
382
+ import { trimmedBytesLength } from '@aztec/foundation/buffer';
382
383
  import { pick } from '@aztec/foundation/collection';
383
384
  import { TimeoutError } from '@aztec/foundation/error';
384
385
  import { EthAddress } from '@aztec/foundation/eth-address';
385
386
  import { Signature } from '@aztec/foundation/eth-signature';
386
387
  import { createLogger } from '@aztec/foundation/log';
387
388
  import { makeBackoff, retry } from '@aztec/foundation/retry';
389
+ import { InterruptibleSleep } from '@aztec/foundation/sleep';
388
390
  import { bufferToHex } from '@aztec/foundation/string';
389
391
  import { Timer } from '@aztec/foundation/timer';
390
392
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
391
393
  import { encodeSlashConsensusVotes } from '@aztec/slasher';
392
394
  import { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
395
+ import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
393
396
  import { getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
394
397
  import { encodeFunctionData, keccak256, multicall3Abi, toHex } from 'viem';
395
398
  import { createL1TxFailedStore } from './l1_tx_failed_store/index.js';
@@ -399,9 +402,6 @@ export const Actions = [
399
402
  'invalidate-by-insufficient-attestations',
400
403
  'propose',
401
404
  'governance-signal',
402
- 'empire-slashing-signal',
403
- 'create-empire-payload',
404
- 'execute-empire-payload',
405
405
  'vote-offenses',
406
406
  'execute-slash'
407
407
  ];
@@ -440,11 +440,14 @@ export class SequencerPublisher {
440
440
  payloadProposedCache;
441
441
  log;
442
442
  ethereumSlotDuration;
443
+ aztecSlotDuration;
444
+ /** Date provider for wall-clock time. */ dateProvider;
443
445
  blobClient;
444
446
  /** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
445
447
  /** Optional callback to obtain a replacement publisher when the current one fails to send. */ getNextPublisher;
446
448
  /** L1 fee analyzer for fisherman mode */ l1FeeAnalyzer;
447
449
  /** Fee asset price oracle for computing price modifiers from Uniswap V4 */ feeAssetPriceOracle;
450
+ /** Interruptible sleep used by sendRequestsAt to wait until a target timestamp. */ interruptibleSleep;
448
451
  // A CALL to a cold address is 2700 gas
449
452
  static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
450
453
  // Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
@@ -453,7 +456,6 @@ export class SequencerPublisher {
453
456
  rollupContract;
454
457
  govProposerContract;
455
458
  slashingProposerContract;
456
- slashFactoryContract;
457
459
  tracer;
458
460
  requests;
459
461
  constructor(config, deps){
@@ -464,12 +466,16 @@ export class SequencerPublisher {
464
466
  this.lastActions = {};
465
467
  this.isPayloadEmptyCache = new Map();
466
468
  this.payloadProposedCache = new Set();
469
+ this.interruptibleSleep = new InterruptibleSleep();
467
470
  this.requests = [];
468
471
  this.log = deps.log ?? createLogger('sequencer:publisher');
469
472
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
473
+ this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
474
+ this.dateProvider = deps.dateProvider;
470
475
  this.epochCache = deps.epochCache;
471
476
  this.lastActions = deps.lastActions;
472
477
  this.blobClient = deps.blobClient;
478
+ this.dateProvider = deps.dateProvider;
473
479
  const telemetry = deps.telemetry ?? getTelemetryClient();
474
480
  this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
475
481
  this.tracer = telemetry.getTracer('SequencerPublisher');
@@ -483,7 +489,6 @@ export class SequencerPublisher {
483
489
  const newSlashingProposer = await this.rollupContract.getSlashingProposer();
484
490
  this.slashingProposerContract = newSlashingProposer;
485
491
  });
486
- this.slashFactoryContract = deps.slashFactoryContract;
487
492
  // Initialize L1 fee analyzer for fisherman mode
488
493
  if (config.fishermanMode) {
489
494
  this.l1FeeAnalyzer = new L1FeeAnalyzer(this.l1TxUtils.client, deps.dateProvider, createLogger('sequencer:publisher:fee-analyzer'));
@@ -536,7 +541,7 @@ export class SequencerPublisher {
536
541
  this.requests.push(request);
537
542
  }
538
543
  getCurrentL2Slot() {
539
- return this.epochCache.getEpochAndSlotNow().slot;
544
+ return this.epochCache.getSlotNow();
540
545
  }
541
546
  /**
542
547
  * Clears all pending requests without sending them.
@@ -625,8 +630,8 @@ export class SequencerPublisher {
625
630
  // @note - we can only have one blob config per bundle
626
631
  // find requests with gas and blob configs
627
632
  // See https://github.com/AztecProtocol/aztec-packages/issues/11513
628
- const gasConfigs = requestsToProcess.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
629
- const blobConfigs = requestsToProcess.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
633
+ const gasConfigs = validRequests.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
634
+ const blobConfigs = validRequests.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
630
635
  if (blobConfigs.length > 1) {
631
636
  throw new Error('Multiple blob configs found');
632
637
  }
@@ -734,6 +739,41 @@ export class SequencerPublisher {
734
739
  }
735
740
  }
736
741
  }
742
+ /*
743
+ * Schedules sending all enqueued requests at (or after) the given timestamp.
744
+ * Uses InterruptibleSleep so it can be cancelled via interrupt().
745
+ * Returns the promise for the L1 response (caller should NOT await this in the work loop).
746
+ */ async sendRequestsAt(submitAfter) {
747
+ const ms = submitAfter.getTime() - this.dateProvider.now();
748
+ if (ms > 0) {
749
+ this.log.debug(`Sleeping ${ms}ms before sending requests`, {
750
+ submitAfter
751
+ });
752
+ await this.interruptibleSleep.sleep(ms);
753
+ }
754
+ if (this.interrupted) {
755
+ return undefined;
756
+ }
757
+ // Re-validate enqueued requests after the sleep (state may have changed, e.g. prune or L1 reorg)
758
+ const validRequests = [];
759
+ for (const request of this.requests){
760
+ if (!request.preCheck) {
761
+ validRequests.push(request);
762
+ continue;
763
+ }
764
+ try {
765
+ await request.preCheck();
766
+ validRequests.push(request);
767
+ } catch (err) {
768
+ this.log.warn(`Pre-send validation failed for ${request.action}, discarding request`, err);
769
+ }
770
+ }
771
+ this.requests = validRequests;
772
+ if (this.requests.length === 0) {
773
+ return undefined;
774
+ }
775
+ return this.sendRequests();
776
+ }
737
777
  callbackBundledTransactions(requests, result, txContext) {
738
778
  const actionsListStr = requests.map((r)=>r.action).join(', ');
739
779
  if (result instanceof FormattedViemError) {
@@ -767,7 +807,16 @@ export class SequencerPublisher {
767
807
  } else {
768
808
  this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
769
809
  result,
770
- requests
810
+ requests: requests.map((r)=>({
811
+ ...r,
812
+ // Avoid logging large blob data
813
+ blobConfig: r.blobConfig ? {
814
+ ...r.blobConfig,
815
+ blobs: r.blobConfig.blobs.map((b)=>({
816
+ size: trimmedBytesLength(b)
817
+ }))
818
+ } : undefined
819
+ }))
771
820
  });
772
821
  const successfulActions = [];
773
822
  const failedActions = [];
@@ -816,18 +865,22 @@ export class SequencerPublisher {
816
865
  }
817
866
  }
818
867
  /**
819
- * @notice Will call `canProposeAtNextEthBlock` to make sure that it is possible to propose
868
+ * @notice Will call `canProposeAt` to make sure that it is possible to propose
820
869
  * @param tipArchive - The archive to check
821
870
  * @returns The slot and block number if it is possible to propose, undefined otherwise
822
- */ canProposeAtNextEthBlock(tipArchive, msgSender, opts = {}) {
871
+ */ canProposeAt(tipArchive, msgSender, opts = {}) {
823
872
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
824
873
  const ignoredErrors = [
825
874
  'SlotAlreadyInChain',
826
875
  'InvalidProposer',
827
876
  'InvalidArchive'
828
877
  ];
829
- return this.rollupContract.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
830
- forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber
878
+ const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
879
+ const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
880
+ const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
881
+ return this.rollupContract.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
882
+ forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
883
+ forceArchive: opts.forceArchive
831
884
  }).catch((err)=>{
832
885
  if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
833
886
  this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find((e)=>err.message.includes(e))}`, {
@@ -858,7 +911,7 @@ export class SequencerPublisher {
858
911
  header.blobsHash.toString(),
859
912
  flags
860
913
  ];
861
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
914
+ const ts = this.getSimulationTimestamp(header.slotNumber);
862
915
  const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(opts?.forcePendingCheckpointNumber);
863
916
  let balance = 0n;
864
917
  if (this.config.fishermanMode) {
@@ -881,7 +934,7 @@ export class SequencerPublisher {
881
934
  }),
882
935
  from: MULTI_CALL_3_ADDRESS
883
936
  }, {
884
- time: ts + 1n
937
+ time: ts
885
938
  }, stateOverrides);
886
939
  this.log.debug(`Simulated validateHeader`);
887
940
  }
@@ -927,6 +980,7 @@ export class SequencerPublisher {
927
980
  gasUsed,
928
981
  checkpointNumber,
929
982
  forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
983
+ lastArchive: validationResult.checkpoint.lastArchive,
930
984
  reason
931
985
  };
932
986
  } catch (err) {
@@ -939,8 +993,8 @@ export class SequencerPublisher {
939
993
  request,
940
994
  error: viemError.message
941
995
  });
942
- const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
943
- if (latestPendingCheckpointNumber < checkpointNumber) {
996
+ const latestProposedCheckpointNumber = await this.rollupContract.getCheckpointNumber();
997
+ if (latestProposedCheckpointNumber < checkpointNumber) {
944
998
  this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, {
945
999
  ...logData
946
1000
  });
@@ -1001,7 +1055,6 @@ export class SequencerPublisher {
1001
1055
  }
1002
1056
  }
1003
1057
  /** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, options) {
1004
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
1005
1058
  const blobFields = checkpoint.toBlobFields();
1006
1059
  const blobs = await getBlobsPerL1Block(blobFields);
1007
1060
  const blobInput = getPrefixedEthBlobCommitments(blobs);
@@ -1018,10 +1071,9 @@ export class SequencerPublisher {
1018
1071
  attestationsAndSignersSignature.toViemSignature(),
1019
1072
  blobInput
1020
1073
  ];
1021
- await this.simulateProposeTx(args, ts, options);
1022
- return ts;
1074
+ await this.simulateProposeTx(args, options);
1023
1075
  }
1024
- async enqueueCastSignalHelper(slotNumber, timestamp, signalType, payload, base, signerAddress, signer) {
1076
+ async enqueueCastSignalHelper(slotNumber, signalType, payload, base, signerAddress, signer) {
1025
1077
  if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
1026
1078
  this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
1027
1079
  return false;
@@ -1078,6 +1130,7 @@ export class SequencerPublisher {
1078
1130
  lastValidL2Slot: slotNumber
1079
1131
  });
1080
1132
  const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
1133
+ const timestamp = this.getSimulationTimestamp(slotNumber);
1081
1134
  try {
1082
1135
  await this.l1TxUtils.simulate(request, {
1083
1136
  time: timestamp
@@ -1090,7 +1143,10 @@ export class SequencerPublisher {
1090
1143
  });
1091
1144
  } catch (err) {
1092
1145
  const viemError = formatViemError(err);
1093
- this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
1146
+ this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
1147
+ simulationTimestamp: timestamp,
1148
+ l1BlockNumber
1149
+ });
1094
1150
  this.backupFailedTx({
1095
1151
  id: keccak256(request.data),
1096
1152
  failureType: 'simulation',
@@ -1155,55 +1211,17 @@ export class SequencerPublisher {
1155
1211
  /**
1156
1212
  * Enqueues a governance castSignal transaction to cast a signal for a given slot number.
1157
1213
  * @param slotNumber - The slot number to cast a signal for.
1158
- * @param timestamp - The timestamp of the slot to cast a signal for.
1159
1214
  * @returns True if the signal was successfully enqueued, false otherwise.
1160
- */ enqueueGovernanceCastSignal(governancePayload, slotNumber, timestamp, signerAddress, signer) {
1161
- return this.enqueueCastSignalHelper(slotNumber, timestamp, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
1215
+ */ enqueueGovernanceCastSignal(governancePayload, slotNumber, signerAddress, signer) {
1216
+ return this.enqueueCastSignalHelper(slotNumber, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
1162
1217
  }
1163
- /** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, timestamp, signerAddress, signer) {
1218
+ /** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, signerAddress, signer) {
1164
1219
  if (actions.length === 0) {
1165
1220
  this.log.debug(`No slashing actions to enqueue for slot ${slotNumber}`);
1166
1221
  return false;
1167
1222
  }
1168
1223
  for (const action of actions){
1169
1224
  switch(action.type){
1170
- case 'vote-empire-payload':
1171
- {
1172
- if (this.slashingProposerContract?.type !== 'empire') {
1173
- this.log.error('Cannot vote for empire payload on non-empire slashing contract');
1174
- break;
1175
- }
1176
- this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
1177
- signerAddress
1178
- });
1179
- await this.enqueueCastSignalHelper(slotNumber, timestamp, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
1180
- break;
1181
- }
1182
- case 'create-empire-payload':
1183
- {
1184
- this.log.debug(`Enqueuing slashing create payload at slot ${slotNumber}`, {
1185
- slotNumber,
1186
- signerAddress
1187
- });
1188
- const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
1189
- await this.simulateAndEnqueueRequest('create-empire-payload', request, (receipt)=>!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs), slotNumber, timestamp);
1190
- break;
1191
- }
1192
- case 'execute-empire-payload':
1193
- {
1194
- this.log.debug(`Enqueuing slashing execute payload at slot ${slotNumber}`, {
1195
- slotNumber,
1196
- signerAddress
1197
- });
1198
- if (this.slashingProposerContract?.type !== 'empire') {
1199
- this.log.error('Cannot execute slashing payload on non-empire slashing contract');
1200
- return false;
1201
- }
1202
- const empireSlashingProposer = this.slashingProposerContract;
1203
- const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
1204
- await this.simulateAndEnqueueRequest('execute-empire-payload', request, (receipt)=>!!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs), slotNumber, timestamp);
1205
- break;
1206
- }
1207
1225
  case 'vote-offenses':
1208
1226
  {
1209
1227
  this.log.debug(`Enqueuing slashing vote for ${action.votes.length} votes at slot ${slotNumber}`, {
@@ -1212,14 +1230,13 @@ export class SequencerPublisher {
1212
1230
  votesCount: action.votes.length,
1213
1231
  signerAddress
1214
1232
  });
1215
- if (this.slashingProposerContract?.type !== 'tally') {
1216
- this.log.error('Cannot vote for slashing offenses on non-tally slashing contract');
1233
+ if (!this.slashingProposerContract) {
1234
+ this.log.error('No slashing proposer contract available');
1217
1235
  return false;
1218
1236
  }
1219
- const tallySlashingProposer = this.slashingProposerContract;
1220
1237
  const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
1221
- const request = await tallySlashingProposer.buildVoteRequestFromSigner(votes, slotNumber, signer);
1222
- await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs), slotNumber, timestamp);
1238
+ const request = await this.slashingProposerContract.buildVoteRequestFromSigner(votes, slotNumber, signer);
1239
+ await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!this.slashingProposerContract.tryExtractVoteCastEvent(receipt.logs), slotNumber);
1223
1240
  break;
1224
1241
  }
1225
1242
  case 'execute-slash':
@@ -1229,13 +1246,12 @@ export class SequencerPublisher {
1229
1246
  round: action.round,
1230
1247
  signerAddress
1231
1248
  });
1232
- if (this.slashingProposerContract?.type !== 'tally') {
1233
- this.log.error('Cannot execute slashing offenses on non-tally slashing contract');
1249
+ if (!this.slashingProposerContract) {
1250
+ this.log.error('No slashing proposer contract available');
1234
1251
  return false;
1235
1252
  }
1236
- const tallySlashingProposer = this.slashingProposerContract;
1237
- const request = tallySlashingProposer.buildExecuteRoundRequest(action.round, action.committees);
1238
- await this.simulateAndEnqueueRequest('execute-slash', request, (receipt)=>!!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs), slotNumber, timestamp);
1253
+ const executeRequest = this.slashingProposerContract.buildExecuteRoundRequest(action.round, action.committees);
1254
+ await this.simulateAndEnqueueRequest('execute-slash', executeRequest, (receipt)=>!!this.slashingProposerContract.tryExtractRoundExecutedEvent(receipt.logs), slotNumber);
1239
1255
  break;
1240
1256
  }
1241
1257
  default:
@@ -1259,14 +1275,13 @@ export class SequencerPublisher {
1259
1275
  attestationsAndSignersSignature,
1260
1276
  feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
1261
1277
  };
1262
- let ts;
1263
1278
  try {
1264
1279
  // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
1265
1280
  // This means that we can avoid the simulation issues in later checks.
1266
1281
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
1267
1282
  // make time consistency checks break.
1268
1283
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
1269
- ts = await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts);
1284
+ await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts);
1270
1285
  } catch (err) {
1271
1286
  this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
1272
1287
  ...checkpoint.getStats(),
@@ -1275,11 +1290,23 @@ export class SequencerPublisher {
1275
1290
  });
1276
1291
  throw err;
1277
1292
  }
1293
+ // Build a pre-check callback that re-validates the checkpoint before L1 submission.
1294
+ // During pipelining this catches stale proposals due to prunes or L1 reorgs that occur during the pipeline sleep.
1295
+ let preCheck = undefined;
1296
+ if (this.epochCache.isProposerPipeliningEnabled()) {
1297
+ preCheck = async ()=>{
1298
+ this.log.debug(`Re-validating checkpoint ${checkpoint.number} before L1 submission`);
1299
+ await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, {
1300
+ // Forcing pending checkpoint number is included its required if an invalidation request is included
1301
+ forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber
1302
+ });
1303
+ };
1304
+ }
1278
1305
  this.log.verbose(`Enqueuing checkpoint propose transaction`, {
1279
1306
  ...checkpoint.toCheckpointInfo(),
1280
1307
  ...opts
1281
1308
  });
1282
- await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
1309
+ await this.addProposeTx(checkpoint, proposeTxArgs, opts, preCheck);
1283
1310
  }
1284
1311
  enqueueInvalidateCheckpoint(request, opts = {}) {
1285
1312
  if (!request) {
@@ -1320,7 +1347,8 @@ export class SequencerPublisher {
1320
1347
  }
1321
1348
  });
1322
1349
  }
1323
- async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber, timestamp) {
1350
+ async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber) {
1351
+ const timestamp = this.getSimulationTimestamp(slotNumber);
1324
1352
  const logData = {
1325
1353
  slotNumber,
1326
1354
  timestamp,
@@ -1342,7 +1370,7 @@ export class SequencerPublisher {
1342
1370
  try {
1343
1371
  ({ gasUsed } = await this.l1TxUtils.simulate(request, {
1344
1372
  time: timestamp
1345
- }, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
1373
+ }, [], simulateAbi));
1346
1374
  this.log.verbose(`Simulation for ${action} succeeded`, {
1347
1375
  ...logData,
1348
1376
  request,
@@ -1417,13 +1445,14 @@ export class SequencerPublisher {
1417
1445
  * A call to `restart` is required before you can continue publishing.
1418
1446
  */ interrupt() {
1419
1447
  this.interrupted = true;
1448
+ this.interruptibleSleep.interrupt();
1420
1449
  this.l1TxUtils.interrupt();
1421
1450
  }
1422
1451
  /** Restarts the publisher after calling `interrupt`. */ restart() {
1423
1452
  this.interrupted = false;
1424
1453
  this.l1TxUtils.restart();
1425
1454
  }
1426
- async prepareProposeTx(encodedData, timestamp, options) {
1455
+ async prepareProposeTx(encodedData, options) {
1427
1456
  const kzg = Blob.getViemKzgInstance();
1428
1457
  const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
1429
1458
  this.log.debug('Validating blob input', {
@@ -1500,7 +1529,7 @@ export class SequencerPublisher {
1500
1529
  encodedData.attestationsAndSignersSignature.toViemSignature(),
1501
1530
  blobInput
1502
1531
  ];
1503
- const { rollupData, simulationResult } = await this.simulateProposeTx(args, timestamp, options);
1532
+ const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
1504
1533
  return {
1505
1534
  args,
1506
1535
  blobEvaluationGas,
@@ -1511,16 +1540,17 @@ export class SequencerPublisher {
1511
1540
  /**
1512
1541
  * Simulates the propose tx with eth_simulateV1
1513
1542
  * @param args - The propose tx args
1514
- * @param timestamp - The timestamp to simulate proposal at
1515
1543
  * @returns The simulation result
1516
- */ async simulateProposeTx(args, timestamp, options) {
1544
+ */ async simulateProposeTx(args, options) {
1517
1545
  const rollupData = encodeFunctionData({
1518
1546
  abi: RollupAbi,
1519
1547
  functionName: 'propose',
1520
1548
  args
1521
1549
  });
1522
- // override the pending checkpoint number if requested
1550
+ // override the proposed checkpoint number if requested
1523
1551
  const forcePendingCheckpointNumberStateDiff = (options.forcePendingCheckpointNumber !== undefined ? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber) : []).flatMap((override)=>override.stateDiff ?? []);
1552
+ // override the fee header for a specific checkpoint number if requested (used when pipelining)
1553
+ const forceProposedFeeHeaderStateDiff = (options.forceProposedFeeHeader !== undefined ? await this.rollupContract.makeFeeHeaderOverride(options.forceProposedFeeHeader.checkpointNumber, options.forceProposedFeeHeader.feeHeader) : []).flatMap((override)=>override.stateDiff ?? []);
1524
1554
  const stateOverrides = [
1525
1555
  {
1526
1556
  address: this.rollupContract.address,
@@ -1530,7 +1560,8 @@ export class SequencerPublisher {
1530
1560
  slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true),
1531
1561
  value: toPaddedHex(0n, true)
1532
1562
  },
1533
- ...forcePendingCheckpointNumberStateDiff
1563
+ ...forcePendingCheckpointNumberStateDiff,
1564
+ ...forceProposedFeeHeaderStateDiff
1534
1565
  ]
1535
1566
  }
1536
1567
  ];
@@ -1542,6 +1573,7 @@ export class SequencerPublisher {
1542
1573
  });
1543
1574
  }
1544
1575
  const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
1576
+ const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
1545
1577
  const simulationResult = await this.l1TxUtils.simulate({
1546
1578
  to: this.rollupContract.address,
1547
1579
  data: rollupData,
@@ -1550,8 +1582,7 @@ export class SequencerPublisher {
1550
1582
  from: this.proposerAddressForSimulation.toString()
1551
1583
  }
1552
1584
  }, {
1553
- // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
1554
- time: timestamp + 1n,
1585
+ time: simTs,
1555
1586
  // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
1556
1587
  gasLimit: MAX_L1_TX_LIMIT * 2n
1557
1588
  }, stateOverrides, RollupAbi, {
@@ -1568,7 +1599,9 @@ export class SequencerPublisher {
1568
1599
  logs: []
1569
1600
  };
1570
1601
  }
1571
- this.log.error(`Failed to simulate propose tx`, viemError);
1602
+ this.log.error(`Failed to simulate propose tx`, viemError, {
1603
+ simulationTimestamp: simTs
1604
+ });
1572
1605
  this.backupFailedTx({
1573
1606
  id: keccak256(rollupData),
1574
1607
  failureType: 'simulation',
@@ -1596,11 +1629,11 @@ export class SequencerPublisher {
1596
1629
  simulationResult
1597
1630
  };
1598
1631
  }
1599
- async addProposeTx(checkpoint, encodedData, opts = {}, timestamp) {
1632
+ async addProposeTx(checkpoint, encodedData, opts = {}, preCheck) {
1600
1633
  const slot = checkpoint.header.slotNumber;
1601
1634
  const timer = new Timer();
1602
1635
  const kzg = Blob.getViemKzgInstance();
1603
- const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, timestamp, opts);
1636
+ const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
1604
1637
  const startBlock = await this.l1TxUtils.getBlockNumber();
1605
1638
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(simulationResult.gasUsed) * 64 / 63)) + blobEvaluationGas + SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS);
1606
1639
  // Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
@@ -1619,6 +1652,7 @@ export class SequencerPublisher {
1619
1652
  ...opts,
1620
1653
  gasLimit
1621
1654
  },
1655
+ preCheck,
1622
1656
  blobConfig: {
1623
1657
  blobs: encodedData.blobs.map((b)=>b.data),
1624
1658
  kzg
@@ -1665,4 +1699,13 @@ export class SequencerPublisher {
1665
1699
  }
1666
1700
  });
1667
1701
  }
1702
+ /** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
1703
+ * for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */ getSimulationTimestamp(slot) {
1704
+ const l1Constants = this.epochCache.getL1Constants();
1705
+ return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
1706
+ }
1707
+ /** Returns the timestamp of the next L1 slot boundary after now. */ getNextL1SlotTimestamp() {
1708
+ const l1Constants = this.epochCache.getL1Constants();
1709
+ return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
1710
+ }
1668
1711
  }
@@ -1,4 +1,5 @@
1
1
  import type { EpochCache } from '@aztec/epoch-cache';
2
+ import { type FeeHeader } from '@aztec/ethereum/contracts';
2
3
  import { BlockNumber, CheckpointNumber, EpochNumber, IndexWithinCheckpoint, SlotNumber } from '@aztec/foundation/branded-types';
3
4
  import { EthAddress } from '@aztec/foundation/eth-address';
4
5
  import { type Logger, type LoggerBindings } from '@aztec/foundation/log';
@@ -7,7 +8,7 @@ import { type TypedEventEmitter } from '@aztec/foundation/types';
7
8
  import type { P2P } from '@aztec/p2p';
8
9
  import type { SlasherClientInterface } from '@aztec/slasher';
9
10
  import { L2Block, type L2BlockSink, type L2BlockSource } from '@aztec/stdlib/block';
10
- import type { Checkpoint } from '@aztec/stdlib/checkpoint';
11
+ import { type Checkpoint, type ProposedCheckpointData } from '@aztec/stdlib/checkpoint';
11
12
  import { type ResolvedSequencerConfig, type WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
12
13
  import { type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
13
14
  import { Tx } from '@aztec/stdlib/tx';
@@ -27,8 +28,9 @@ import { SequencerState } from './utils.js';
27
28
  * the Sequencer once the check for being the proposer for the slot has succeeded.
28
29
  */
29
30
  export declare class CheckpointProposalJob implements Traceable {
30
- private readonly epoch;
31
- private readonly slot;
31
+ private readonly slotNow;
32
+ private readonly targetSlot;
33
+ private readonly targetEpoch;
32
34
  private readonly checkpointNumber;
33
35
  private readonly syncedToBlockNumber;
34
36
  private readonly proposer;
@@ -53,15 +55,27 @@ export declare class CheckpointProposalJob implements Traceable {
53
55
  private readonly eventEmitter;
54
56
  private readonly setStateFn;
55
57
  readonly tracer: Tracer;
58
+ private readonly proposedCheckpointData?;
56
59
  protected readonly log: Logger;
57
- constructor(epoch: EpochNumber, slot: SlotNumber, checkpointNumber: CheckpointNumber, syncedToBlockNumber: BlockNumber, proposer: EthAddress | undefined, publisher: SequencerPublisher, attestorAddress: EthAddress, invalidateCheckpoint: InvalidateCheckpointRequest | undefined, validatorClient: ValidatorClient, globalsBuilder: GlobalVariableBuilder, p2pClient: P2P, worldState: WorldStateSynchronizer, l1ToL2MessageSource: L1ToL2MessageSource, l2BlockSource: L2BlockSource, checkpointsBuilder: FullNodeCheckpointsBuilder, blockSink: L2BlockSink, l1Constants: SequencerRollupConstants, config: ResolvedSequencerConfig, timetable: SequencerTimetable, slasherClient: SlasherClientInterface | undefined, epochCache: EpochCache, dateProvider: DateProvider, metrics: SequencerMetrics, eventEmitter: TypedEventEmitter<SequencerEvents>, setStateFn: (state: SequencerState, slot?: SlotNumber) => void, tracer: Tracer, bindings?: LoggerBindings);
60
+ /** Tracks the fire-and-forget L1 submission promise so it can be awaited during shutdown. */
61
+ private pendingL1Submission;
62
+ /** Fee header override computed during proposeCheckpoint, reused in enqueueCheckpointForSubmission. */
63
+ private computedForceProposedFeeHeader?;
64
+ constructor(slotNow: SlotNumber, targetSlot: SlotNumber, targetEpoch: EpochNumber, checkpointNumber: CheckpointNumber, syncedToBlockNumber: BlockNumber, proposer: EthAddress | undefined, publisher: SequencerPublisher, attestorAddress: EthAddress, invalidateCheckpoint: InvalidateCheckpointRequest | undefined, validatorClient: ValidatorClient, globalsBuilder: GlobalVariableBuilder, p2pClient: P2P, worldState: WorldStateSynchronizer, l1ToL2MessageSource: L1ToL2MessageSource, l2BlockSource: L2BlockSource, checkpointsBuilder: FullNodeCheckpointsBuilder, blockSink: L2BlockSink, l1Constants: SequencerRollupConstants, config: ResolvedSequencerConfig, timetable: SequencerTimetable, slasherClient: SlasherClientInterface | undefined, epochCache: EpochCache, dateProvider: DateProvider, metrics: SequencerMetrics, eventEmitter: TypedEventEmitter<SequencerEvents>, setStateFn: (state: SequencerState, slot?: SlotNumber) => void, tracer: Tracer, bindings?: LoggerBindings, proposedCheckpointData?: ProposedCheckpointData | undefined);
65
+ /** Awaits the pending L1 submission if one is in progress. Call during shutdown. */
66
+ awaitPendingSubmission(): Promise<void>;
58
67
  /**
59
68
  * Executes the checkpoint proposal job.
60
- * Returns the published checkpoint if successful, undefined otherwise.
69
+ * Builds blocks, collects attestations, enqueues requests, and schedules L1 submission as a
70
+ * background task so the work loop can return to IDLE immediately.
71
+ * Returns the built checkpoint if successful, undefined otherwise.
61
72
  */
62
73
  execute(): Promise<Checkpoint | undefined>;
74
+ private enqueueCheckpointForSubmission;
63
75
  private proposeCheckpoint;
64
76
  private buildBlocksForCheckpoint;
77
+ /** Creates a block proposal for a given block via the validator client (unless in fisherman mode) */
78
+ private createBlockProposal;
65
79
  private waitUntilNextSubslot;
66
80
  /** Builds a single block. Called from the main block building loop. */
67
81
  protected buildSingleBlock(checkpointBuilder: CheckpointBuilder, opts: {
@@ -71,14 +85,14 @@ export declare class CheckpointProposalJob implements Traceable {
71
85
  indexWithinCheckpoint: IndexWithinCheckpoint;
72
86
  buildDeadline: Date | undefined;
73
87
  txHashesAlreadyIncluded: Set<string>;
74
- remainingBlobFields: number;
75
88
  }): Promise<{
76
89
  block: L2Block;
77
90
  usedTxs: Tx[];
78
- remainingBlobFields: number;
91
+ } | {
92
+ failure: 'insufficient-txs' | 'insufficient-valid-txs';
79
93
  } | {
80
94
  error: Error;
81
- } | undefined>;
95
+ }>;
82
96
  private buildSingleBlockWithCheckpointBuilder;
83
97
  private waitForMinTxs;
84
98
  private waitForAttestations;
@@ -91,6 +105,19 @@ export declare class CheckpointProposalJob implements Traceable {
91
105
  * Helper to handle HA double-signing errors. Returns true if the error was handled (caller should yield).
92
106
  */
93
107
  private handleHASigningError;
108
+ /**
109
+ * In times of congestion we need to simulate using the correct fee header override for the previous block
110
+ * We calculate the correct fee header values.
111
+ *
112
+ * If we are in block 1, or the checkpoint we are querying does not exist, we return undefined. However
113
+ * If we are pipelining - where this function is called, the grandparentCheckpointNumber should always exist
114
+ * @param parentCheckpointNumber
115
+ * @returns
116
+ */
117
+ protected computeForceProposedFeeHeader(parentCheckpointNumber: CheckpointNumber): Promise<{
118
+ checkpointNumber: CheckpointNumber;
119
+ feeHeader: FeeHeader;
120
+ } | undefined>;
94
121
  /** Waits until a specific time within the current slot */
95
122
  protected waitUntilTimeInSlot(targetSecondsIntoSlot: number): Promise<void>;
96
123
  /** Waits the polling interval for transactions. Extracted for test overriding. */
@@ -99,4 +126,4 @@ export declare class CheckpointProposalJob implements Traceable {
99
126
  private getSecondsIntoSlot;
100
127
  getPublisher(): SequencerPublisher;
101
128
  }
102
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2twb2ludF9wcm9wb3NhbF9qb2IuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXF1ZW5jZXIvY2hlY2twb2ludF9wcm9wb3NhbF9qb2IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUNMLFdBQVcsRUFDWCxnQkFBZ0IsRUFDaEIsV0FBVyxFQUNYLHFCQUFxQixFQUNyQixVQUFVLEVBQ1gsTUFBTSxpQ0FBaUMsQ0FBQztBQVF6QyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFHM0QsT0FBTyxFQUFFLEtBQUssTUFBTSxFQUFFLEtBQUssY0FBYyxFQUFnQixNQUFNLHVCQUF1QixDQUFDO0FBRXZGLE9BQU8sRUFBRSxLQUFLLFlBQVksRUFBUyxNQUFNLHlCQUF5QixDQUFDO0FBQ25FLE9BQU8sRUFBRSxLQUFLLGlCQUFpQixFQUEwQixNQUFNLHlCQUF5QixDQUFDO0FBQ3pGLE9BQU8sS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUN0QyxPQUFPLEtBQUssRUFBRSxzQkFBc0IsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzdELE9BQU8sRUFHTCxPQUFPLEVBQ1AsS0FBSyxXQUFXLEVBQ2hCLEtBQUssYUFBYSxFQUVuQixNQUFNLHFCQUFxQixDQUFDO0FBQzdCLE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBRzNELE9BQU8sRUFHTCxLQUFLLHVCQUF1QixFQUM1QixLQUFLLHNCQUFzQixFQUM1QixNQUFNLGlDQUFpQyxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxLQUFLLG1CQUFtQixFQUFtQyxNQUFNLHlCQUF5QixDQUFDO0FBSXBHLE9BQU8sRUFBaUIsRUFBRSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFFckQsT0FBTyxFQUFjLEtBQUssU0FB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
129
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2twb2ludF9wcm9wb3NhbF9qb2IuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXF1ZW5jZXIvY2hlY2twb2ludF9wcm9wb3NhbF9qb2IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUFFLEtBQUssU0FBUyxFQUFrQixNQUFNLDJCQUEyQixDQUFDO0FBQzNFLE9BQU8sRUFDTCxXQUFXLEVBQ1gsZ0JBQWdCLEVBQ2hCLFdBQVcsRUFDWCxxQkFBcUIsRUFDckIsVUFBVSxFQUNYLE1BQU0saUNBQWlDLENBQUM7QUFRekMsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBRzNELE9BQU8sRUFBRSxLQUFLLE1BQU0sRUFBRSxLQUFLLGNBQWMsRUFBZ0IsTUFBTSx1QkFBdUIsQ0FBQztBQUV2RixPQUFPLEVBQUUsS0FBSyxZQUFZLEVBQVMsTUFBTSx5QkFBeUIsQ0FBQztBQUNuRSxPQUFPLEVBQUUsS0FBSyxpQkFBaUIsRUFBMEIsTUFBTSx5QkFBeUIsQ0FBQztBQUN6RixPQUFPLEtBQUssRUFBRSxHQUFHLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDdEMsT0FBTyxLQUFLLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUM3RCxPQUFPLEVBR0wsT0FBTyxFQUNQLEtBQUssV0FBVyxFQUNoQixLQUFLLGFBQWEsRUFFbkIsTUFBTSxxQkFBcUIsQ0FBQztBQUM3QixPQUFPLEVBQUUsS0FBSyxVQUFVLEVBQUUsS0FBSyxzQkFBc0IsRUFBc0IsTUFBTSwwQkFBMEIsQ0FBQztBQUc1RyxPQUFPLEVBR0wsS0FBSyx1QkFBdUIsRUFDNUIsS0FBSyxzQkFBc0IsRUFDNUIsTUFBTSxpQ0FBaUMsQ0FBQztBQUN6QyxPQUFPLEVBQUUsS0FBSyxtQkFBbUIsRUFBbUMsTUFBTSx5QkFBeUIsQ0FBQztB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