@aztec/sequencer-client 0.0.1-commit.54489865 → 0.0.1-commit.5914bae

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 (62) hide show
  1. package/dest/client/sequencer-client.d.ts +16 -7
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +56 -23
  4. package/dest/config.d.ts +25 -6
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +46 -28
  7. package/dest/global_variable_builder/global_builder.d.ts +14 -10
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +22 -21
  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 +31 -17
  13. package/dest/publisher/config.d.ts.map +1 -1
  14. package/dest/publisher/config.js +101 -42
  15. package/dest/publisher/sequencer-publisher-factory.d.ts +11 -3
  16. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  17. package/dest/publisher/sequencer-publisher-factory.js +13 -2
  18. package/dest/publisher/sequencer-publisher.d.ts +29 -14
  19. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  20. package/dest/publisher/sequencer-publisher.js +144 -74
  21. package/dest/sequencer/checkpoint_proposal_job.d.ts +30 -7
  22. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  23. package/dest/sequencer/checkpoint_proposal_job.js +191 -113
  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/metrics.d.ts +17 -5
  28. package/dest/sequencer/metrics.d.ts.map +1 -1
  29. package/dest/sequencer/metrics.js +86 -15
  30. package/dest/sequencer/sequencer.d.ts +29 -13
  31. package/dest/sequencer/sequencer.d.ts.map +1 -1
  32. package/dest/sequencer/sequencer.js +47 -43
  33. package/dest/sequencer/timetable.d.ts +4 -6
  34. package/dest/sequencer/timetable.d.ts.map +1 -1
  35. package/dest/sequencer/timetable.js +7 -11
  36. package/dest/sequencer/types.d.ts +2 -2
  37. package/dest/sequencer/types.d.ts.map +1 -1
  38. package/dest/test/index.d.ts +3 -5
  39. package/dest/test/index.d.ts.map +1 -1
  40. package/dest/test/mock_checkpoint_builder.d.ts +12 -12
  41. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  42. package/dest/test/mock_checkpoint_builder.js +47 -36
  43. package/dest/test/utils.d.ts +3 -3
  44. package/dest/test/utils.d.ts.map +1 -1
  45. package/dest/test/utils.js +5 -4
  46. package/package.json +28 -28
  47. package/src/client/sequencer-client.ts +78 -21
  48. package/src/config.ts +61 -38
  49. package/src/global_variable_builder/global_builder.ts +23 -24
  50. package/src/global_variable_builder/index.ts +1 -1
  51. package/src/publisher/config.ts +112 -43
  52. package/src/publisher/sequencer-publisher-factory.ts +23 -6
  53. package/src/publisher/sequencer-publisher.ts +155 -87
  54. package/src/sequencer/checkpoint_proposal_job.ts +285 -159
  55. package/src/sequencer/checkpoint_voter.ts +1 -12
  56. package/src/sequencer/metrics.ts +92 -18
  57. package/src/sequencer/sequencer.ts +60 -49
  58. package/src/sequencer/timetable.ts +13 -12
  59. package/src/sequencer/types.ts +1 -1
  60. package/src/test/index.ts +2 -4
  61. package/src/test/mock_checkpoint_builder.ts +67 -53
  62. package/src/test/utils.ts +5 -2
@@ -372,22 +372,25 @@ function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
372
372
  }
373
373
  var _dec, _dec1, _dec2, _initProto;
374
374
  import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
375
- import { MULTI_CALL_3_ADDRESS, Multicall3, RollupContract } from '@aztec/ethereum/contracts';
375
+ import { FeeAssetPriceOracle, MULTI_CALL_3_ADDRESS, Multicall3, RollupContract } from '@aztec/ethereum/contracts';
376
376
  import { L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
377
- import { WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
378
- import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
377
+ import { MAX_L1_TX_LIMIT, WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
378
+ import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
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 { EthAddress } from '@aztec/foundation/eth-address';
384
385
  import { Signature } from '@aztec/foundation/eth-signature';
385
386
  import { createLogger } from '@aztec/foundation/log';
387
+ import { makeBackoff, retry } from '@aztec/foundation/retry';
386
388
  import { bufferToHex } from '@aztec/foundation/string';
387
389
  import { Timer } from '@aztec/foundation/timer';
388
390
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
389
391
  import { encodeSlashConsensusVotes } from '@aztec/slasher';
390
392
  import { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
393
+ import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
391
394
  import { getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
392
395
  import { encodeFunctionData, toHex } from 'viem';
393
396
  import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
@@ -433,15 +436,15 @@ export class SequencerPublisher {
433
436
  slashingLog;
434
437
  lastActions;
435
438
  isPayloadEmptyCache;
439
+ payloadProposedCache;
436
440
  log;
437
441
  ethereumSlotDuration;
442
+ aztecSlotDuration;
443
+ dateProvider;
438
444
  blobClient;
439
445
  /** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
440
446
  /** L1 fee analyzer for fisherman mode */ l1FeeAnalyzer;
441
- // @note - with blobs, the below estimate seems too large.
442
- // Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
443
- // Total used for emptier block from above test: 429k (of which 84k is 1x blob)
444
- static PROPOSE_GAS_GUESS = 12_000_000n;
447
+ /** Fee asset price oracle for computing price modifiers from Uniswap V4 */ feeAssetPriceOracle;
445
448
  // A CALL to a cold address is 2700 gas
446
449
  static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
447
450
  // Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
@@ -460,9 +463,12 @@ export class SequencerPublisher {
460
463
  this.slashingLog = createLogger('sequencer:publisher:slashing');
461
464
  this.lastActions = {};
462
465
  this.isPayloadEmptyCache = new Map();
466
+ this.payloadProposedCache = new Set();
463
467
  this.requests = [];
464
468
  this.log = deps.log ?? createLogger('sequencer:publisher');
465
469
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
470
+ this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
471
+ this.dateProvider = deps.dateProvider;
466
472
  this.epochCache = deps.epochCache;
467
473
  this.lastActions = deps.lastActions;
468
474
  this.blobClient = deps.blobClient;
@@ -483,10 +489,18 @@ export class SequencerPublisher {
483
489
  if (config.fishermanMode) {
484
490
  this.l1FeeAnalyzer = new L1FeeAnalyzer(this.l1TxUtils.client, deps.dateProvider, createLogger('sequencer:publisher:fee-analyzer'));
485
491
  }
492
+ // Initialize fee asset price oracle
493
+ this.feeAssetPriceOracle = new FeeAssetPriceOracle(this.l1TxUtils.client, this.rollupContract, createLogger('sequencer:publisher:price-oracle'));
486
494
  }
487
495
  getRollupContract() {
488
496
  return this.rollupContract;
489
497
  }
498
+ /**
499
+ * Gets the fee asset price modifier from the oracle.
500
+ * Returns 0n if the oracle query fails.
501
+ */ getFeeAssetPriceModifier() {
502
+ return this.feeAssetPriceOracle.computePriceModifier();
503
+ }
490
504
  getSenderAddress() {
491
505
  return this.l1TxUtils.getSenderAddress();
492
506
  }
@@ -544,7 +558,7 @@ export class SequencerPublisher {
544
558
  // Get the transaction requests
545
559
  const l1Requests = requestsToAnalyze.map((r)=>r.request);
546
560
  // Start the analysis
547
- const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit : SequencerPublisher.PROPOSE_GAS_GUESS, l1Requests, blobConfig, onComplete);
561
+ const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT, l1Requests, blobConfig, onComplete);
548
562
  this.log.info('Started L1 fee analysis', {
549
563
  analysisId,
550
564
  l2SlotNumber: l2SlotNumber.toString(),
@@ -594,15 +608,24 @@ export class SequencerPublisher {
594
608
  // @note - we can only have one blob config per bundle
595
609
  // find requests with gas and blob configs
596
610
  // See https://github.com/AztecProtocol/aztec-packages/issues/11513
597
- const gasConfigs = requestsToProcess.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
598
- const blobConfigs = requestsToProcess.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
611
+ const gasConfigs = validRequests.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
612
+ const blobConfigs = validRequests.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
599
613
  if (blobConfigs.length > 1) {
600
614
  throw new Error('Multiple blob configs found');
601
615
  }
602
616
  const blobConfig = blobConfigs[0];
603
617
  // Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
604
618
  const gasLimits = gasConfigs.map((g)=>g?.gasLimit).filter((g)=>g !== undefined);
605
- const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
619
+ let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
620
+ // Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
621
+ const maxGas = MAX_L1_TX_LIMIT;
622
+ if (gasLimit !== undefined && gasLimit > maxGas) {
623
+ this.log.debug('Capping bundled tx gas limit to L1 max', {
624
+ requested: gasLimit,
625
+ capped: maxGas
626
+ });
627
+ gasLimit = maxGas;
628
+ }
606
629
  const txTimeoutAts = gasConfigs.map((g)=>g?.txTimeoutAt).filter((g)=>g !== undefined);
607
630
  const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map((g)=>g.getTime()))) : undefined; // earliest
608
631
  const txConfig = {
@@ -648,7 +671,16 @@ export class SequencerPublisher {
648
671
  } else {
649
672
  this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
650
673
  result,
651
- requests
674
+ requests: requests.map((r)=>({
675
+ ...r,
676
+ // Avoid logging large blob data
677
+ blobConfig: r.blobConfig ? {
678
+ ...r.blobConfig,
679
+ blobs: r.blobConfig.blobs.map((b)=>({
680
+ size: trimmedBytesLength(b)
681
+ }))
682
+ } : undefined
683
+ }))
652
684
  });
653
685
  const successfulActions = [];
654
686
  const failedActions = [];
@@ -666,17 +698,20 @@ export class SequencerPublisher {
666
698
  }
667
699
  }
668
700
  /**
669
- * @notice Will call `canProposeAtNextEthBlock` to make sure that it is possible to propose
701
+ * @notice Will call `canProposeAt` to make sure that it is possible to propose
670
702
  * @param tipArchive - The archive to check
671
703
  * @returns The slot and block number if it is possible to propose, undefined otherwise
672
- */ canProposeAtNextEthBlock(tipArchive, msgSender, opts = {}) {
704
+ */ canProposeAt(tipArchive, msgSender, opts = {}) {
673
705
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
674
706
  const ignoredErrors = [
675
707
  'SlotAlreadyInChain',
676
708
  'InvalidProposer',
677
709
  'InvalidArchive'
678
710
  ];
679
- return this.rollupContract.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
711
+ const pipelined = opts.pipelined ?? false;
712
+ const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
713
+ const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
714
+ return this.rollupContract.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
680
715
  forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber
681
716
  }).catch((err)=>{
682
717
  if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
@@ -708,7 +743,7 @@ export class SequencerPublisher {
708
743
  header.blobsHash.toString(),
709
744
  flags
710
745
  ];
711
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
746
+ const ts = this.getSimulationTimestamp(header.slotNumber);
712
747
  const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(opts?.forcePendingCheckpointNumber);
713
748
  let balance = 0n;
714
749
  if (this.config.fishermanMode) {
@@ -731,7 +766,7 @@ export class SequencerPublisher {
731
766
  }),
732
767
  from: MULTI_CALL_3_ADDRESS
733
768
  }, {
734
- time: ts + 1n
769
+ time: ts
735
770
  }, stateOverrides);
736
771
  this.log.debug(`Simulated validateHeader`);
737
772
  }
@@ -762,7 +797,10 @@ export class SequencerPublisher {
762
797
  request
763
798
  });
764
799
  try {
765
- const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
800
+ const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, mergeAbis([
801
+ request.abi ?? [],
802
+ ErrorsAbi
803
+ ]));
766
804
  this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
767
805
  ...logData,
768
806
  request,
@@ -779,7 +817,7 @@ export class SequencerPublisher {
779
817
  const viemError = formatViemError(err);
780
818
  // If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
781
819
  // we can safely ignore it and return undefined so we go ahead with checkpoint building.
782
- if (viemError.message?.includes('Rollup__BlockNotInPendingChain')) {
820
+ if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
783
821
  this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`, {
784
822
  ...logData,
785
823
  request,
@@ -826,30 +864,15 @@ export class SequencerPublisher {
826
864
  }
827
865
  }
828
866
  /** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, options) {
829
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
830
- // TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
831
- // If we have no attestations, we still need to provide the empty attestations
832
- // so that the committee is recalculated correctly
833
- // const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
834
- // if (ignoreSignatures) {
835
- // const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
836
- // if (!committee) {
837
- // this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
838
- // throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
839
- // }
840
- // attestationsAndSigners.attestations = committee.map(committeeMember =>
841
- // CommitteeAttestation.fromAddress(committeeMember),
842
- // );
843
- // }
844
867
  const blobFields = checkpoint.toBlobFields();
845
- const blobs = getBlobsPerL1Block(blobFields);
868
+ const blobs = await getBlobsPerL1Block(blobFields);
846
869
  const blobInput = getPrefixedEthBlobCommitments(blobs);
847
870
  const args = [
848
871
  {
849
872
  header: checkpoint.header.toViem(),
850
873
  archive: toHex(checkpoint.archive.root.toBuffer()),
851
874
  oracleInput: {
852
- feeAssetPriceModifier: 0n
875
+ feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
853
876
  }
854
877
  },
855
878
  attestationsAndSigners.getPackedAttestations(),
@@ -857,10 +880,9 @@ export class SequencerPublisher {
857
880
  attestationsAndSignersSignature.toViemSignature(),
858
881
  blobInput
859
882
  ];
860
- await this.simulateProposeTx(args, ts, options);
861
- return ts;
883
+ await this.simulateProposeTx(args, options);
862
884
  }
863
- async enqueueCastSignalHelper(slotNumber, timestamp, signalType, payload, base, signerAddress, signer) {
885
+ async enqueueCastSignalHelper(slotNumber, signalType, payload, base, signerAddress, signer) {
864
886
  if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
865
887
  this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
866
888
  return false;
@@ -884,6 +906,28 @@ export class SequencerPublisher {
884
906
  this.log.warn(`Skipping vote cast for payload with empty code`);
885
907
  return false;
886
908
  }
909
+ // Check if payload was already submitted to governance
910
+ const cacheKey = payload.toString();
911
+ if (!this.payloadProposedCache.has(cacheKey)) {
912
+ try {
913
+ const l1StartBlock = await this.rollupContract.getL1StartBlock();
914
+ const proposed = await retry(()=>base.hasPayloadBeenProposed(payload.toString(), l1StartBlock), 'Check if payload was proposed', makeBackoff([
915
+ 0,
916
+ 1,
917
+ 2
918
+ ]), this.log, true);
919
+ if (proposed) {
920
+ this.payloadProposedCache.add(cacheKey);
921
+ }
922
+ } catch (err) {
923
+ this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
924
+ return false;
925
+ }
926
+ }
927
+ if (this.payloadProposedCache.has(cacheKey)) {
928
+ this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
929
+ return false;
930
+ }
887
931
  const cachedLastVote = this.lastActions[signalType];
888
932
  this.lastActions[signalType] = slotNumber;
889
933
  const action = signalType;
@@ -894,15 +938,22 @@ export class SequencerPublisher {
894
938
  signer: this.l1TxUtils.client.account?.address,
895
939
  lastValidL2Slot: slotNumber
896
940
  });
941
+ const timestamp = this.getSimulationTimestamp(slotNumber);
897
942
  try {
898
943
  await this.l1TxUtils.simulate(request, {
899
944
  time: timestamp
900
- }, [], ErrorsAbi);
945
+ }, [], mergeAbis([
946
+ request.abi ?? [],
947
+ ErrorsAbi
948
+ ]));
901
949
  this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, {
902
950
  request
903
951
  });
904
952
  } catch (err) {
905
- this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, err);
953
+ const viemError = formatViemError(err);
954
+ this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
955
+ simulationTimestamp: timestamp
956
+ });
906
957
  // Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
907
958
  }
908
959
  // TODO(palla/slash): All votes (governance and slashing) should txTimeoutAt at the end of the slot.
@@ -946,12 +997,11 @@ export class SequencerPublisher {
946
997
  /**
947
998
  * Enqueues a governance castSignal transaction to cast a signal for a given slot number.
948
999
  * @param slotNumber - The slot number to cast a signal for.
949
- * @param timestamp - The timestamp of the slot to cast a signal for.
950
1000
  * @returns True if the signal was successfully enqueued, false otherwise.
951
- */ enqueueGovernanceCastSignal(governancePayload, slotNumber, timestamp, signerAddress, signer) {
952
- return this.enqueueCastSignalHelper(slotNumber, timestamp, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
1001
+ */ enqueueGovernanceCastSignal(governancePayload, slotNumber, signerAddress, signer) {
1002
+ return this.enqueueCastSignalHelper(slotNumber, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
953
1003
  }
954
- /** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, timestamp, signerAddress, signer) {
1004
+ /** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, signerAddress, signer) {
955
1005
  if (actions.length === 0) {
956
1006
  this.log.debug(`No slashing actions to enqueue for slot ${slotNumber}`);
957
1007
  return false;
@@ -967,7 +1017,7 @@ export class SequencerPublisher {
967
1017
  this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
968
1018
  signerAddress
969
1019
  });
970
- await this.enqueueCastSignalHelper(slotNumber, timestamp, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
1020
+ await this.enqueueCastSignalHelper(slotNumber, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
971
1021
  break;
972
1022
  }
973
1023
  case 'create-empire-payload':
@@ -977,7 +1027,7 @@ export class SequencerPublisher {
977
1027
  signerAddress
978
1028
  });
979
1029
  const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
980
- await this.simulateAndEnqueueRequest('create-empire-payload', request, (receipt)=>!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs), slotNumber, timestamp);
1030
+ await this.simulateAndEnqueueRequest('create-empire-payload', request, (receipt)=>!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs), slotNumber);
981
1031
  break;
982
1032
  }
983
1033
  case 'execute-empire-payload':
@@ -992,7 +1042,7 @@ export class SequencerPublisher {
992
1042
  }
993
1043
  const empireSlashingProposer = this.slashingProposerContract;
994
1044
  const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
995
- await this.simulateAndEnqueueRequest('execute-empire-payload', request, (receipt)=>!!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs), slotNumber, timestamp);
1045
+ await this.simulateAndEnqueueRequest('execute-empire-payload', request, (receipt)=>!!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs), slotNumber);
996
1046
  break;
997
1047
  }
998
1048
  case 'vote-offenses':
@@ -1010,7 +1060,7 @@ export class SequencerPublisher {
1010
1060
  const tallySlashingProposer = this.slashingProposerContract;
1011
1061
  const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
1012
1062
  const request = await tallySlashingProposer.buildVoteRequestFromSigner(votes, slotNumber, signer);
1013
- await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs), slotNumber, timestamp);
1063
+ await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs), slotNumber);
1014
1064
  break;
1015
1065
  }
1016
1066
  case 'execute-slash':
@@ -1026,7 +1076,7 @@ export class SequencerPublisher {
1026
1076
  }
1027
1077
  const tallySlashingProposer = this.slashingProposerContract;
1028
1078
  const request = tallySlashingProposer.buildExecuteRoundRequest(action.round, action.committees);
1029
- await this.simulateAndEnqueueRequest('execute-slash', request, (receipt)=>!!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs), slotNumber, timestamp);
1079
+ await this.simulateAndEnqueueRequest('execute-slash', request, (receipt)=>!!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs), slotNumber);
1030
1080
  break;
1031
1081
  }
1032
1082
  default:
@@ -1041,22 +1091,22 @@ export class SequencerPublisher {
1041
1091
  /** Simulates and enqueues a proposal for a checkpoint on L1 */ async enqueueProposeCheckpoint(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
1042
1092
  const checkpointHeader = checkpoint.header;
1043
1093
  const blobFields = checkpoint.toBlobFields();
1044
- const blobs = getBlobsPerL1Block(blobFields);
1094
+ const blobs = await getBlobsPerL1Block(blobFields);
1045
1095
  const proposeTxArgs = {
1046
1096
  header: checkpointHeader,
1047
1097
  archive: checkpoint.archive.root.toBuffer(),
1048
1098
  blobs,
1049
1099
  attestationsAndSigners,
1050
- attestationsAndSignersSignature
1100
+ attestationsAndSignersSignature,
1101
+ feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
1051
1102
  };
1052
- let ts;
1053
1103
  try {
1054
1104
  // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
1055
1105
  // This means that we can avoid the simulation issues in later checks.
1056
1106
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
1057
1107
  // make time consistency checks break.
1058
1108
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
1059
- ts = await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts);
1109
+ await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts);
1060
1110
  } catch (err) {
1061
1111
  this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
1062
1112
  ...checkpoint.getStats(),
@@ -1069,7 +1119,7 @@ export class SequencerPublisher {
1069
1119
  ...checkpoint.toCheckpointInfo(),
1070
1120
  ...opts
1071
1121
  });
1072
- await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
1122
+ await this.addProposeTx(checkpoint, proposeTxArgs, opts);
1073
1123
  }
1074
1124
  enqueueInvalidateCheckpoint(request, opts = {}) {
1075
1125
  if (!request) {
@@ -1110,7 +1160,8 @@ export class SequencerPublisher {
1110
1160
  }
1111
1161
  });
1112
1162
  }
1113
- async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber, timestamp) {
1163
+ async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber) {
1164
+ const timestamp = this.getSimulationTimestamp(slotNumber);
1114
1165
  const logData = {
1115
1166
  slotNumber,
1116
1167
  timestamp,
@@ -1124,27 +1175,37 @@ export class SequencerPublisher {
1124
1175
  this.lastActions[action] = slotNumber;
1125
1176
  this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
1126
1177
  let gasUsed;
1178
+ const simulateAbi = mergeAbis([
1179
+ request.abi ?? [],
1180
+ ErrorsAbi
1181
+ ]);
1127
1182
  try {
1128
1183
  ({ gasUsed } = await this.l1TxUtils.simulate(request, {
1129
1184
  time: timestamp
1130
- }, [], ErrorsAbi)); // TODO(palla/slash): Check the timestamp logic
1185
+ }, [], simulateAbi));
1131
1186
  this.log.verbose(`Simulation for ${action} succeeded`, {
1132
1187
  ...logData,
1133
1188
  request,
1134
1189
  gasUsed
1135
1190
  });
1136
1191
  } catch (err) {
1137
- const viemError = formatViemError(err);
1192
+ const viemError = formatViemError(err, simulateAbi);
1138
1193
  this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
1139
1194
  return false;
1140
1195
  }
1141
1196
  // We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
1142
1197
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(gasUsed) * 64 / 63)));
1143
1198
  logData.gasLimit = gasLimit;
1199
+ // Store the ABI used for simulation on the request so Multicall3.forward can decode errors
1200
+ // when the tx is sent and a revert is diagnosed via simulation.
1201
+ const requestWithAbi = {
1202
+ ...request,
1203
+ abi: simulateAbi
1204
+ };
1144
1205
  this.log.debug(`Enqueuing ${action}`, logData);
1145
1206
  this.addRequest({
1146
1207
  action,
1147
- request,
1208
+ request: requestWithAbi,
1148
1209
  gasConfig: {
1149
1210
  gasLimit
1150
1211
  },
@@ -1181,7 +1242,7 @@ export class SequencerPublisher {
1181
1242
  this.interrupted = false;
1182
1243
  this.l1TxUtils.restart();
1183
1244
  }
1184
- async prepareProposeTx(encodedData, timestamp, options) {
1245
+ async prepareProposeTx(encodedData, options) {
1185
1246
  const kzg = Blob.getViemKzgInstance();
1186
1247
  const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
1187
1248
  this.log.debug('Validating blob input', {
@@ -1222,8 +1283,7 @@ export class SequencerPublisher {
1222
1283
  header: encodedData.header.toViem(),
1223
1284
  archive: toHex(encodedData.archive),
1224
1285
  oracleInput: {
1225
- // We are currently not modifying these. See #9963
1226
- feeAssetPriceModifier: 0n
1286
+ feeAssetPriceModifier: encodedData.feeAssetPriceModifier
1227
1287
  }
1228
1288
  },
1229
1289
  encodedData.attestationsAndSigners.getPackedAttestations(),
@@ -1231,7 +1291,7 @@ export class SequencerPublisher {
1231
1291
  encodedData.attestationsAndSignersSignature.toViemSignature(),
1232
1292
  blobInput
1233
1293
  ];
1234
- const { rollupData, simulationResult } = await this.simulateProposeTx(args, timestamp, options);
1294
+ const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
1235
1295
  return {
1236
1296
  args,
1237
1297
  blobEvaluationGas,
@@ -1242,9 +1302,8 @@ export class SequencerPublisher {
1242
1302
  /**
1243
1303
  * Simulates the propose tx with eth_simulateV1
1244
1304
  * @param args - The propose tx args
1245
- * @param timestamp - The timestamp to simulate proposal at
1246
1305
  * @returns The simulation result
1247
- */ async simulateProposeTx(args, timestamp, options) {
1306
+ */ async simulateProposeTx(args, options) {
1248
1307
  const rollupData = encodeFunctionData({
1249
1308
  abi: RollupAbi,
1250
1309
  functionName: 'propose',
@@ -1272,21 +1331,21 @@ export class SequencerPublisher {
1272
1331
  balance: 10n * WEI_CONST * WEI_CONST
1273
1332
  });
1274
1333
  }
1334
+ const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
1275
1335
  const simulationResult = await this.l1TxUtils.simulate({
1276
1336
  to: this.rollupContract.address,
1277
1337
  data: rollupData,
1278
- gas: SequencerPublisher.PROPOSE_GAS_GUESS,
1338
+ gas: MAX_L1_TX_LIMIT,
1279
1339
  ...this.proposerAddressForSimulation && {
1280
1340
  from: this.proposerAddressForSimulation.toString()
1281
1341
  }
1282
1342
  }, {
1283
- // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
1284
- time: timestamp + 1n,
1343
+ time: simTs,
1285
1344
  // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
1286
- gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n
1345
+ gasLimit: MAX_L1_TX_LIMIT * 2n
1287
1346
  }, stateOverrides, RollupAbi, {
1288
1347
  // @note fallback gas estimate to use if the node doesn't support simulation API
1289
- fallbackGasEstimate: SequencerPublisher.PROPOSE_GAS_GUESS
1348
+ fallbackGasEstimate: MAX_L1_TX_LIMIT
1290
1349
  }).catch((err)=>{
1291
1350
  // In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
1292
1351
  const viemError = formatViemError(err);
@@ -1294,11 +1353,13 @@ export class SequencerPublisher {
1294
1353
  this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
1295
1354
  // Return a minimal simulation result with the fallback gas estimate
1296
1355
  return {
1297
- gasUsed: SequencerPublisher.PROPOSE_GAS_GUESS,
1356
+ gasUsed: MAX_L1_TX_LIMIT,
1298
1357
  logs: []
1299
1358
  };
1300
1359
  }
1301
- this.log.error(`Failed to simulate propose tx`, viemError);
1360
+ this.log.error(`Failed to simulate propose tx`, viemError, {
1361
+ simulationTimestamp: simTs
1362
+ });
1302
1363
  throw err;
1303
1364
  });
1304
1365
  return {
@@ -1306,11 +1367,11 @@ export class SequencerPublisher {
1306
1367
  simulationResult
1307
1368
  };
1308
1369
  }
1309
- async addProposeTx(checkpoint, encodedData, opts = {}, timestamp) {
1370
+ async addProposeTx(checkpoint, encodedData, opts = {}) {
1310
1371
  const slot = checkpoint.header.slotNumber;
1311
1372
  const timer = new Timer();
1312
1373
  const kzg = Blob.getViemKzgInstance();
1313
- const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, timestamp, opts);
1374
+ const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
1314
1375
  const startBlock = await this.l1TxUtils.getBlockNumber();
1315
1376
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(simulationResult.gasUsed) * 64 / 63)) + blobEvaluationGas + SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS);
1316
1377
  // Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
@@ -1375,4 +1436,13 @@ export class SequencerPublisher {
1375
1436
  }
1376
1437
  });
1377
1438
  }
1439
+ /** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
1440
+ * for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */ getSimulationTimestamp(slot) {
1441
+ const l1Constants = this.epochCache.getL1Constants();
1442
+ return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
1443
+ }
1444
+ /** Returns the timestamp of the next L1 slot boundary after now. */ getNextL1SlotTimestamp() {
1445
+ const l1Constants = this.epochCache.getL1Constants();
1446
+ return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
1447
+ }
1378
1448
  }
@@ -1,17 +1,18 @@
1
1
  import type { EpochCache } from '@aztec/epoch-cache';
2
- import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
2
+ import { BlockNumber, CheckpointNumber, EpochNumber, IndexWithinCheckpoint, SlotNumber } from '@aztec/foundation/branded-types';
3
3
  import { EthAddress } from '@aztec/foundation/eth-address';
4
4
  import { type Logger, type LoggerBindings } from '@aztec/foundation/log';
5
5
  import { type DateProvider } from '@aztec/foundation/timer';
6
6
  import { type TypedEventEmitter } from '@aztec/foundation/types';
7
7
  import type { P2P } from '@aztec/p2p';
8
8
  import type { SlasherClientInterface } from '@aztec/slasher';
9
- import { type L2BlockSink, type L2BlockSource } from '@aztec/stdlib/block';
10
- import type { Checkpoint } from '@aztec/stdlib/checkpoint';
11
- import type { ResolvedSequencerConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
9
+ import { L2Block, type L2BlockSink, type L2BlockSource } from '@aztec/stdlib/block';
10
+ import { type Checkpoint } from '@aztec/stdlib/checkpoint';
11
+ import { type ResolvedSequencerConfig, type WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
12
12
  import { type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
13
+ import { Tx } from '@aztec/stdlib/tx';
13
14
  import { type Traceable, type Tracer } from '@aztec/telemetry-client';
14
- import { type FullNodeCheckpointsBuilder, type ValidatorClient } from '@aztec/validator-client';
15
+ import { CheckpointBuilder, type FullNodeCheckpointsBuilder, type ValidatorClient } from '@aztec/validator-client';
15
16
  import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
16
17
  import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
17
18
  import type { SequencerEvents } from './events.js';
@@ -61,8 +62,24 @@ export declare class CheckpointProposalJob implements Traceable {
61
62
  execute(): Promise<Checkpoint | undefined>;
62
63
  private proposeCheckpoint;
63
64
  private buildBlocksForCheckpoint;
65
+ /** Creates a block proposal for a given block via the validator client (unless in fisherman mode) */
66
+ private createBlockProposal;
64
67
  private waitUntilNextSubslot;
65
- private buildSingleBlock;
68
+ /** Builds a single block. Called from the main block building loop. */
69
+ protected buildSingleBlock(checkpointBuilder: CheckpointBuilder, opts: {
70
+ forceCreate?: boolean;
71
+ blockTimestamp: bigint;
72
+ blockNumber: BlockNumber;
73
+ indexWithinCheckpoint: IndexWithinCheckpoint;
74
+ buildDeadline: Date | undefined;
75
+ txHashesAlreadyIncluded: Set<string>;
76
+ }): Promise<{
77
+ block: L2Block;
78
+ usedTxs: Tx[];
79
+ } | {
80
+ error: Error;
81
+ } | undefined>;
82
+ private buildSingleBlockWithCheckpointBuilder;
66
83
  private waitForMinTxs;
67
84
  private waitForAttestations;
68
85
  /** Breaks the attestations before publishing based on attack configs */
@@ -70,10 +87,16 @@ export declare class CheckpointProposalJob implements Traceable {
70
87
  private dropFailedTxsFromP2P;
71
88
  private syncProposedBlockToArchiver;
72
89
  private handleCheckpointEndAsFisherman;
90
+ /**
91
+ * Helper to handle HA double-signing errors. Returns true if the error was handled (caller should yield).
92
+ */
93
+ private handleHASigningError;
73
94
  /** Waits until a specific time within the current slot */
74
95
  protected waitUntilTimeInSlot(targetSecondsIntoSlot: number): Promise<void>;
96
+ /** Waits the polling interval for transactions. Extracted for test overriding. */
97
+ protected waitForTxsPollingInterval(): Promise<void>;
75
98
  private getSlotStartBuildTimestamp;
76
99
  private getSecondsIntoSlot;
77
100
  getPublisher(): SequencerPublisher;
78
101
  }
79
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2twb2ludF9wcm9wb3NhbF9qb2IuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXF1ZW5jZXIvY2hlY2twb2ludF9wcm9wb3NhbF9qb2IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUFFLFdBQVcsRUFBRSxnQkFBZ0IsRUFBRSxXQUFXLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFHekcsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBRzNELE9BQU8sRUFBRSxLQUFLLE1BQU0sRUFBRSxLQUFLLGNBQWMsRUFBZ0IsTUFBTSx1QkFBdUIsQ0FBQztBQUV2RixPQUFPLEVBQUUsS0FBSyxZQUFZLEVBQVMsTUFBTSx5QkFBeUIsQ0FBQztBQUNuRSxPQUFPLEVBQUUsS0FBSyxpQkFBaUIsRUFBWSxNQUFNLHlCQUF5QixDQUFDO0FBQzNFLE9BQU8sS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUN0QyxPQUFPLEtBQUssRUFBRSxzQkFBc0IsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzdELE9BQU8sRUFJTCxLQUFLLFdBQVcsRUFDaEIsS0FBSyxhQUFhLEVBRW5CLE1BQU0scUJBQXFCLENBQUM7QUFDN0IsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFHM0QsT0FBTyxLQUFLLEVBRVYsdUJBQXVCLEVBQ3ZCLHNCQUFzQixFQUN2QixNQUFNLGlDQUFpQyxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxLQUFLLG1CQUFtQixFQUFtQyxNQUFNLHlCQUF5QixDQUFDO0FBTXBHLE9BQU8sRUFBYyxLQUFLLFNBQVMsRUFBRSxLQUFLLE1BQU0sRUFBYSxNQUFNLHlCQUF5QixDQUFDO0FBQzdGLE9BQU8sRUFBcUIsS0FBSywwQkFBMEIsRUFBRSxL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
102
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2twb2ludF9wcm9wb3NhbF9qb2IuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXF1ZW5jZXIvY2hlY2twb2ludF9wcm9wb3NhbF9qb2IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUNMLFdBQVcsRUFDWCxnQkFBZ0IsRUFDaEIsV0FBVyxFQUNYLHFCQUFxQixFQUNyQixVQUFVLEVBQ1gsTUFBTSxpQ0FBaUMsQ0FBQztBQVF6QyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFHM0QsT0FBTyxFQUFFLEtBQUssTUFBTSxFQUFFLEtBQUssY0FBYyxFQUFnQixNQUFNLHVCQUF1QixDQUFDO0FBRXZGLE9BQU8sRUFBRSxLQUFLLFlBQVksRUFBUyxNQUFNLHlCQUF5QixDQUFDO0FBQ25FLE9BQU8sRUFBRSxLQUFLLGlCQUFpQixFQUEwQixNQUFNLHlCQUF5QixDQUFDO0FBQ3pGLE9BQU8sS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUN0QyxPQUFPLEtBQUssRUFBRSxzQkFBc0IsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzdELE9BQU8sRUFHTCxPQUFPLEVBQ1AsS0FBSyxXQUFXLEVBQ2hCLEtBQUssYUFBYSxFQUVuQixNQUFNLHFCQUFxQixDQUFDO0FBQzdCLE9BQU8sRUFBRSxLQUFLLFVBQVUsRUFBc0IsTUFBTSwwQkFBMEIsQ0FBQztBQUcvRSxPQUFPLEVBR0wsS0FBSyx1QkFBdUIsRUFDNUIsS0FBSyxzQkFBc0IsRUFDNUIsTUFBTSxpQ0FBaUMsQ0FBQztBQUN6QyxPQUFPLEVBQUUsS0FBSyxtQkFBbUIsRUFBbUMsTUFBTSx5QkFBeUIsQ0FBQztBQVNwRyxPQUFPLEVBQWlCLEVBQUUsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRXJELE9BQU8sRUFBYyxLQUFLLFNB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