@aztec/sequencer-client 0.0.1-commit.fffb133c → 0.0.1-dev

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 (66) 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-metrics.d.ts +1 -1
  19. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  20. package/dest/publisher/sequencer-publisher-metrics.js +12 -4
  21. package/dest/publisher/sequencer-publisher.d.ts +28 -14
  22. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  23. package/dest/publisher/sequencer-publisher.js +137 -74
  24. package/dest/sequencer/checkpoint_proposal_job.d.ts +35 -10
  25. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  26. package/dest/sequencer/checkpoint_proposal_job.js +212 -137
  27. package/dest/sequencer/checkpoint_voter.d.ts +1 -2
  28. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  29. package/dest/sequencer/checkpoint_voter.js +2 -5
  30. package/dest/sequencer/metrics.d.ts +17 -5
  31. package/dest/sequencer/metrics.d.ts.map +1 -1
  32. package/dest/sequencer/metrics.js +111 -30
  33. package/dest/sequencer/sequencer.d.ts +29 -13
  34. package/dest/sequencer/sequencer.d.ts.map +1 -1
  35. package/dest/sequencer/sequencer.js +48 -44
  36. package/dest/sequencer/timetable.d.ts +4 -6
  37. package/dest/sequencer/timetable.d.ts.map +1 -1
  38. package/dest/sequencer/timetable.js +7 -11
  39. package/dest/sequencer/types.d.ts +2 -2
  40. package/dest/sequencer/types.d.ts.map +1 -1
  41. package/dest/test/index.d.ts +3 -5
  42. package/dest/test/index.d.ts.map +1 -1
  43. package/dest/test/mock_checkpoint_builder.d.ts +12 -12
  44. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  45. package/dest/test/mock_checkpoint_builder.js +47 -36
  46. package/dest/test/utils.d.ts +3 -3
  47. package/dest/test/utils.d.ts.map +1 -1
  48. package/dest/test/utils.js +5 -4
  49. package/package.json +28 -28
  50. package/src/client/sequencer-client.ts +78 -21
  51. package/src/config.ts +61 -38
  52. package/src/global_variable_builder/global_builder.ts +23 -24
  53. package/src/global_variable_builder/index.ts +1 -1
  54. package/src/publisher/config.ts +112 -43
  55. package/src/publisher/sequencer-publisher-factory.ts +23 -6
  56. package/src/publisher/sequencer-publisher-metrics.ts +7 -3
  57. package/src/publisher/sequencer-publisher.ts +149 -90
  58. package/src/sequencer/checkpoint_proposal_job.ts +309 -186
  59. package/src/sequencer/checkpoint_voter.ts +1 -12
  60. package/src/sequencer/metrics.ts +124 -32
  61. package/src/sequencer/sequencer.ts +61 -50
  62. package/src/sequencer/timetable.ts +13 -12
  63. package/src/sequencer/types.ts +1 -1
  64. package/src/test/index.ts +2 -4
  65. package/src/test/mock_checkpoint_builder.ts +67 -53
  66. package/src/test/utils.ts +5 -2
@@ -372,13 +372,14 @@ 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';
@@ -388,6 +389,7 @@ import { Timer } from '@aztec/foundation/timer';
388
389
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
389
390
  import { encodeSlashConsensusVotes } from '@aztec/slasher';
390
391
  import { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
392
+ import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
391
393
  import { getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
392
394
  import { encodeFunctionData, toHex } from 'viem';
393
395
  import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
@@ -435,13 +437,12 @@ export class SequencerPublisher {
435
437
  isPayloadEmptyCache;
436
438
  log;
437
439
  ethereumSlotDuration;
440
+ aztecSlotDuration;
441
+ dateProvider;
438
442
  blobClient;
439
443
  /** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
440
444
  /** 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;
445
+ /** Fee asset price oracle for computing price modifiers from Uniswap V4 */ feeAssetPriceOracle;
445
446
  // A CALL to a cold address is 2700 gas
446
447
  static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
447
448
  // Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
@@ -463,6 +464,8 @@ export class SequencerPublisher {
463
464
  this.requests = [];
464
465
  this.log = deps.log ?? createLogger('sequencer:publisher');
465
466
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
467
+ this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
468
+ this.dateProvider = deps.dateProvider;
466
469
  this.epochCache = deps.epochCache;
467
470
  this.lastActions = deps.lastActions;
468
471
  this.blobClient = deps.blobClient;
@@ -483,10 +486,18 @@ export class SequencerPublisher {
483
486
  if (config.fishermanMode) {
484
487
  this.l1FeeAnalyzer = new L1FeeAnalyzer(this.l1TxUtils.client, deps.dateProvider, createLogger('sequencer:publisher:fee-analyzer'));
485
488
  }
489
+ // Initialize fee asset price oracle
490
+ this.feeAssetPriceOracle = new FeeAssetPriceOracle(this.l1TxUtils.client, this.rollupContract, createLogger('sequencer:publisher:price-oracle'));
486
491
  }
487
492
  getRollupContract() {
488
493
  return this.rollupContract;
489
494
  }
495
+ /**
496
+ * Gets the fee asset price modifier from the oracle.
497
+ * Returns 0n if the oracle query fails.
498
+ */ getFeeAssetPriceModifier() {
499
+ return this.feeAssetPriceOracle.computePriceModifier();
500
+ }
490
501
  getSenderAddress() {
491
502
  return this.l1TxUtils.getSenderAddress();
492
503
  }
@@ -544,7 +555,7 @@ export class SequencerPublisher {
544
555
  // Get the transaction requests
545
556
  const l1Requests = requestsToAnalyze.map((r)=>r.request);
546
557
  // Start the analysis
547
- const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit : SequencerPublisher.PROPOSE_GAS_GUESS, l1Requests, blobConfig, onComplete);
558
+ const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT, l1Requests, blobConfig, onComplete);
548
559
  this.log.info('Started L1 fee analysis', {
549
560
  analysisId,
550
561
  l2SlotNumber: l2SlotNumber.toString(),
@@ -594,15 +605,24 @@ export class SequencerPublisher {
594
605
  // @note - we can only have one blob config per bundle
595
606
  // find requests with gas and blob configs
596
607
  // 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);
608
+ const gasConfigs = validRequests.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
609
+ const blobConfigs = validRequests.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
599
610
  if (blobConfigs.length > 1) {
600
611
  throw new Error('Multiple blob configs found');
601
612
  }
602
613
  const blobConfig = blobConfigs[0];
603
614
  // Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
604
615
  const gasLimits = gasConfigs.map((g)=>g?.gasLimit).filter((g)=>g !== undefined);
605
- const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
616
+ let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
617
+ // Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
618
+ const maxGas = MAX_L1_TX_LIMIT;
619
+ if (gasLimit !== undefined && gasLimit > maxGas) {
620
+ this.log.debug('Capping bundled tx gas limit to L1 max', {
621
+ requested: gasLimit,
622
+ capped: maxGas
623
+ });
624
+ gasLimit = maxGas;
625
+ }
606
626
  const txTimeoutAts = gasConfigs.map((g)=>g?.txTimeoutAt).filter((g)=>g !== undefined);
607
627
  const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map((g)=>g.getTime()))) : undefined; // earliest
608
628
  const txConfig = {
@@ -648,7 +668,16 @@ export class SequencerPublisher {
648
668
  } else {
649
669
  this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
650
670
  result,
651
- requests
671
+ requests: requests.map((r)=>({
672
+ ...r,
673
+ // Avoid logging large blob data
674
+ blobConfig: r.blobConfig ? {
675
+ ...r.blobConfig,
676
+ blobs: r.blobConfig.blobs.map((b)=>({
677
+ size: trimmedBytesLength(b)
678
+ }))
679
+ } : undefined
680
+ }))
652
681
  });
653
682
  const successfulActions = [];
654
683
  const failedActions = [];
@@ -666,17 +695,20 @@ export class SequencerPublisher {
666
695
  }
667
696
  }
668
697
  /**
669
- * @notice Will call `canProposeAtNextEthBlock` to make sure that it is possible to propose
698
+ * @notice Will call `canProposeAt` to make sure that it is possible to propose
670
699
  * @param tipArchive - The archive to check
671
700
  * @returns The slot and block number if it is possible to propose, undefined otherwise
672
- */ canProposeAtNextEthBlock(tipArchive, msgSender, opts = {}) {
701
+ */ canProposeAt(tipArchive, msgSender, opts = {}) {
673
702
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
674
703
  const ignoredErrors = [
675
704
  'SlotAlreadyInChain',
676
705
  'InvalidProposer',
677
706
  'InvalidArchive'
678
707
  ];
679
- return this.rollupContract.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
708
+ const pipelined = opts.pipelined ?? false;
709
+ const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
710
+ const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
711
+ return this.rollupContract.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
680
712
  forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber
681
713
  }).catch((err)=>{
682
714
  if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
@@ -708,7 +740,7 @@ export class SequencerPublisher {
708
740
  header.blobsHash.toString(),
709
741
  flags
710
742
  ];
711
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
743
+ const ts = this.getSimulationTimestamp(header.slotNumber);
712
744
  const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(opts?.forcePendingCheckpointNumber);
713
745
  let balance = 0n;
714
746
  if (this.config.fishermanMode) {
@@ -731,7 +763,7 @@ export class SequencerPublisher {
731
763
  }),
732
764
  from: MULTI_CALL_3_ADDRESS
733
765
  }, {
734
- time: ts + 1n
766
+ time: ts
735
767
  }, stateOverrides);
736
768
  this.log.debug(`Simulated validateHeader`);
737
769
  }
@@ -762,7 +794,10 @@ export class SequencerPublisher {
762
794
  request
763
795
  });
764
796
  try {
765
- const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
797
+ const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, mergeAbis([
798
+ request.abi ?? [],
799
+ ErrorsAbi
800
+ ]));
766
801
  this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
767
802
  ...logData,
768
803
  request,
@@ -779,7 +814,7 @@ export class SequencerPublisher {
779
814
  const viemError = formatViemError(err);
780
815
  // If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
781
816
  // we can safely ignore it and return undefined so we go ahead with checkpoint building.
782
- if (viemError.message?.includes('Rollup__BlockNotInPendingChain')) {
817
+ if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
783
818
  this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`, {
784
819
  ...logData,
785
820
  request,
@@ -826,30 +861,15 @@ export class SequencerPublisher {
826
861
  }
827
862
  }
828
863
  /** 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
864
  const blobFields = checkpoint.toBlobFields();
845
- const blobs = getBlobsPerL1Block(blobFields);
865
+ const blobs = await getBlobsPerL1Block(blobFields);
846
866
  const blobInput = getPrefixedEthBlobCommitments(blobs);
847
867
  const args = [
848
868
  {
849
869
  header: checkpoint.header.toViem(),
850
870
  archive: toHex(checkpoint.archive.root.toBuffer()),
851
871
  oracleInput: {
852
- feeAssetPriceModifier: 0n
872
+ feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
853
873
  }
854
874
  },
855
875
  attestationsAndSigners.getPackedAttestations(),
@@ -857,10 +877,9 @@ export class SequencerPublisher {
857
877
  attestationsAndSignersSignature.toViemSignature(),
858
878
  blobInput
859
879
  ];
860
- await this.simulateProposeTx(args, ts, options);
861
- return ts;
880
+ await this.simulateProposeTx(args, options);
862
881
  }
863
- async enqueueCastSignalHelper(slotNumber, timestamp, signalType, payload, base, signerAddress, signer) {
882
+ async enqueueCastSignalHelper(slotNumber, signalType, payload, base, signerAddress, signer) {
864
883
  if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
865
884
  this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
866
885
  return false;
@@ -884,6 +903,24 @@ export class SequencerPublisher {
884
903
  this.log.warn(`Skipping vote cast for payload with empty code`);
885
904
  return false;
886
905
  }
906
+ // Skip signaling if there is already a live (non-terminal) Governance proposal for this
907
+ // payload. This is intentionally not cached: a previously-live proposal may transition to
908
+ // a terminal state (Dropped/Rejected/Expired/Executed), at which point we may want to re-signal
909
+ // the same payload in a future round.
910
+ let proposed = false;
911
+ try {
912
+ proposed = await base.hasActiveProposalWithPayload(payload.toString());
913
+ } catch (err) {
914
+ // We deliberately swallow the error and proceed to signal. Failing closed (skipping the
915
+ // signal) on transient RPC errors would let a flaky L1 endpoint silence governance
916
+ // participation entirely; failing open at worst produces a duplicate signal that the
917
+ // contract will simply count alongside others in the round.
918
+ this.log.error(`Failed to check if payload ${payload} was already proposed (signalling anyway)`, err);
919
+ }
920
+ if (proposed) {
921
+ this.log.info(`Payload ${payload} has a live governance proposal, stopping signals`);
922
+ return false;
923
+ }
887
924
  const cachedLastVote = this.lastActions[signalType];
888
925
  this.lastActions[signalType] = slotNumber;
889
926
  const action = signalType;
@@ -894,15 +931,22 @@ export class SequencerPublisher {
894
931
  signer: this.l1TxUtils.client.account?.address,
895
932
  lastValidL2Slot: slotNumber
896
933
  });
934
+ const timestamp = this.getSimulationTimestamp(slotNumber);
897
935
  try {
898
936
  await this.l1TxUtils.simulate(request, {
899
937
  time: timestamp
900
- }, [], ErrorsAbi);
938
+ }, [], mergeAbis([
939
+ request.abi ?? [],
940
+ ErrorsAbi
941
+ ]));
901
942
  this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, {
902
943
  request
903
944
  });
904
945
  } catch (err) {
905
- this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, err);
946
+ const viemError = formatViemError(err);
947
+ this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
948
+ simulationTimestamp: timestamp
949
+ });
906
950
  // Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
907
951
  }
908
952
  // TODO(palla/slash): All votes (governance and slashing) should txTimeoutAt at the end of the slot.
@@ -946,12 +990,11 @@ export class SequencerPublisher {
946
990
  /**
947
991
  * Enqueues a governance castSignal transaction to cast a signal for a given slot number.
948
992
  * @param slotNumber - The slot number to cast a signal for.
949
- * @param timestamp - The timestamp of the slot to cast a signal for.
950
993
  * @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);
994
+ */ enqueueGovernanceCastSignal(governancePayload, slotNumber, signerAddress, signer) {
995
+ return this.enqueueCastSignalHelper(slotNumber, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
953
996
  }
954
- /** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, timestamp, signerAddress, signer) {
997
+ /** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, signerAddress, signer) {
955
998
  if (actions.length === 0) {
956
999
  this.log.debug(`No slashing actions to enqueue for slot ${slotNumber}`);
957
1000
  return false;
@@ -967,7 +1010,7 @@ export class SequencerPublisher {
967
1010
  this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
968
1011
  signerAddress
969
1012
  });
970
- await this.enqueueCastSignalHelper(slotNumber, timestamp, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
1013
+ await this.enqueueCastSignalHelper(slotNumber, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
971
1014
  break;
972
1015
  }
973
1016
  case 'create-empire-payload':
@@ -977,7 +1020,7 @@ export class SequencerPublisher {
977
1020
  signerAddress
978
1021
  });
979
1022
  const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
980
- await this.simulateAndEnqueueRequest('create-empire-payload', request, (receipt)=>!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs), slotNumber, timestamp);
1023
+ await this.simulateAndEnqueueRequest('create-empire-payload', request, (receipt)=>!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs), slotNumber);
981
1024
  break;
982
1025
  }
983
1026
  case 'execute-empire-payload':
@@ -992,7 +1035,7 @@ export class SequencerPublisher {
992
1035
  }
993
1036
  const empireSlashingProposer = this.slashingProposerContract;
994
1037
  const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
995
- await this.simulateAndEnqueueRequest('execute-empire-payload', request, (receipt)=>!!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs), slotNumber, timestamp);
1038
+ await this.simulateAndEnqueueRequest('execute-empire-payload', request, (receipt)=>!!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs), slotNumber);
996
1039
  break;
997
1040
  }
998
1041
  case 'vote-offenses':
@@ -1010,7 +1053,7 @@ export class SequencerPublisher {
1010
1053
  const tallySlashingProposer = this.slashingProposerContract;
1011
1054
  const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
1012
1055
  const request = await tallySlashingProposer.buildVoteRequestFromSigner(votes, slotNumber, signer);
1013
- await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs), slotNumber, timestamp);
1056
+ await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs), slotNumber);
1014
1057
  break;
1015
1058
  }
1016
1059
  case 'execute-slash':
@@ -1026,7 +1069,7 @@ export class SequencerPublisher {
1026
1069
  }
1027
1070
  const tallySlashingProposer = this.slashingProposerContract;
1028
1071
  const request = tallySlashingProposer.buildExecuteRoundRequest(action.round, action.committees);
1029
- await this.simulateAndEnqueueRequest('execute-slash', request, (receipt)=>!!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs), slotNumber, timestamp);
1072
+ await this.simulateAndEnqueueRequest('execute-slash', request, (receipt)=>!!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs), slotNumber);
1030
1073
  break;
1031
1074
  }
1032
1075
  default:
@@ -1041,22 +1084,22 @@ export class SequencerPublisher {
1041
1084
  /** Simulates and enqueues a proposal for a checkpoint on L1 */ async enqueueProposeCheckpoint(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
1042
1085
  const checkpointHeader = checkpoint.header;
1043
1086
  const blobFields = checkpoint.toBlobFields();
1044
- const blobs = getBlobsPerL1Block(blobFields);
1087
+ const blobs = await getBlobsPerL1Block(blobFields);
1045
1088
  const proposeTxArgs = {
1046
1089
  header: checkpointHeader,
1047
1090
  archive: checkpoint.archive.root.toBuffer(),
1048
1091
  blobs,
1049
1092
  attestationsAndSigners,
1050
- attestationsAndSignersSignature
1093
+ attestationsAndSignersSignature,
1094
+ feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
1051
1095
  };
1052
- let ts;
1053
1096
  try {
1054
1097
  // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
1055
1098
  // This means that we can avoid the simulation issues in later checks.
1056
1099
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
1057
1100
  // make time consistency checks break.
1058
1101
  // 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);
1102
+ await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts);
1060
1103
  } catch (err) {
1061
1104
  this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
1062
1105
  ...checkpoint.getStats(),
@@ -1069,7 +1112,7 @@ export class SequencerPublisher {
1069
1112
  ...checkpoint.toCheckpointInfo(),
1070
1113
  ...opts
1071
1114
  });
1072
- await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
1115
+ await this.addProposeTx(checkpoint, proposeTxArgs, opts);
1073
1116
  }
1074
1117
  enqueueInvalidateCheckpoint(request, opts = {}) {
1075
1118
  if (!request) {
@@ -1110,7 +1153,8 @@ export class SequencerPublisher {
1110
1153
  }
1111
1154
  });
1112
1155
  }
1113
- async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber, timestamp) {
1156
+ async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber) {
1157
+ const timestamp = this.getSimulationTimestamp(slotNumber);
1114
1158
  const logData = {
1115
1159
  slotNumber,
1116
1160
  timestamp,
@@ -1124,27 +1168,37 @@ export class SequencerPublisher {
1124
1168
  this.lastActions[action] = slotNumber;
1125
1169
  this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
1126
1170
  let gasUsed;
1171
+ const simulateAbi = mergeAbis([
1172
+ request.abi ?? [],
1173
+ ErrorsAbi
1174
+ ]);
1127
1175
  try {
1128
1176
  ({ gasUsed } = await this.l1TxUtils.simulate(request, {
1129
1177
  time: timestamp
1130
- }, [], ErrorsAbi)); // TODO(palla/slash): Check the timestamp logic
1178
+ }, [], simulateAbi));
1131
1179
  this.log.verbose(`Simulation for ${action} succeeded`, {
1132
1180
  ...logData,
1133
1181
  request,
1134
1182
  gasUsed
1135
1183
  });
1136
1184
  } catch (err) {
1137
- const viemError = formatViemError(err);
1185
+ const viemError = formatViemError(err, simulateAbi);
1138
1186
  this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
1139
1187
  return false;
1140
1188
  }
1141
1189
  // We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
1142
1190
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(gasUsed) * 64 / 63)));
1143
1191
  logData.gasLimit = gasLimit;
1192
+ // Store the ABI used for simulation on the request so Multicall3.forward can decode errors
1193
+ // when the tx is sent and a revert is diagnosed via simulation.
1194
+ const requestWithAbi = {
1195
+ ...request,
1196
+ abi: simulateAbi
1197
+ };
1144
1198
  this.log.debug(`Enqueuing ${action}`, logData);
1145
1199
  this.addRequest({
1146
1200
  action,
1147
- request,
1201
+ request: requestWithAbi,
1148
1202
  gasConfig: {
1149
1203
  gasLimit
1150
1204
  },
@@ -1181,7 +1235,7 @@ export class SequencerPublisher {
1181
1235
  this.interrupted = false;
1182
1236
  this.l1TxUtils.restart();
1183
1237
  }
1184
- async prepareProposeTx(encodedData, timestamp, options) {
1238
+ async prepareProposeTx(encodedData, options) {
1185
1239
  const kzg = Blob.getViemKzgInstance();
1186
1240
  const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
1187
1241
  this.log.debug('Validating blob input', {
@@ -1222,8 +1276,7 @@ export class SequencerPublisher {
1222
1276
  header: encodedData.header.toViem(),
1223
1277
  archive: toHex(encodedData.archive),
1224
1278
  oracleInput: {
1225
- // We are currently not modifying these. See #9963
1226
- feeAssetPriceModifier: 0n
1279
+ feeAssetPriceModifier: encodedData.feeAssetPriceModifier
1227
1280
  }
1228
1281
  },
1229
1282
  encodedData.attestationsAndSigners.getPackedAttestations(),
@@ -1231,7 +1284,7 @@ export class SequencerPublisher {
1231
1284
  encodedData.attestationsAndSignersSignature.toViemSignature(),
1232
1285
  blobInput
1233
1286
  ];
1234
- const { rollupData, simulationResult } = await this.simulateProposeTx(args, timestamp, options);
1287
+ const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
1235
1288
  return {
1236
1289
  args,
1237
1290
  blobEvaluationGas,
@@ -1242,9 +1295,8 @@ export class SequencerPublisher {
1242
1295
  /**
1243
1296
  * Simulates the propose tx with eth_simulateV1
1244
1297
  * @param args - The propose tx args
1245
- * @param timestamp - The timestamp to simulate proposal at
1246
1298
  * @returns The simulation result
1247
- */ async simulateProposeTx(args, timestamp, options) {
1299
+ */ async simulateProposeTx(args, options) {
1248
1300
  const rollupData = encodeFunctionData({
1249
1301
  abi: RollupAbi,
1250
1302
  functionName: 'propose',
@@ -1272,21 +1324,21 @@ export class SequencerPublisher {
1272
1324
  balance: 10n * WEI_CONST * WEI_CONST
1273
1325
  });
1274
1326
  }
1327
+ const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
1275
1328
  const simulationResult = await this.l1TxUtils.simulate({
1276
1329
  to: this.rollupContract.address,
1277
1330
  data: rollupData,
1278
- gas: SequencerPublisher.PROPOSE_GAS_GUESS,
1331
+ gas: MAX_L1_TX_LIMIT,
1279
1332
  ...this.proposerAddressForSimulation && {
1280
1333
  from: this.proposerAddressForSimulation.toString()
1281
1334
  }
1282
1335
  }, {
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,
1336
+ time: simTs,
1285
1337
  // @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
1338
+ gasLimit: MAX_L1_TX_LIMIT * 2n
1287
1339
  }, stateOverrides, RollupAbi, {
1288
1340
  // @note fallback gas estimate to use if the node doesn't support simulation API
1289
- fallbackGasEstimate: SequencerPublisher.PROPOSE_GAS_GUESS
1341
+ fallbackGasEstimate: MAX_L1_TX_LIMIT
1290
1342
  }).catch((err)=>{
1291
1343
  // In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
1292
1344
  const viemError = formatViemError(err);
@@ -1294,11 +1346,13 @@ export class SequencerPublisher {
1294
1346
  this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
1295
1347
  // Return a minimal simulation result with the fallback gas estimate
1296
1348
  return {
1297
- gasUsed: SequencerPublisher.PROPOSE_GAS_GUESS,
1349
+ gasUsed: MAX_L1_TX_LIMIT,
1298
1350
  logs: []
1299
1351
  };
1300
1352
  }
1301
- this.log.error(`Failed to simulate propose tx`, viemError);
1353
+ this.log.error(`Failed to simulate propose tx`, viemError, {
1354
+ simulationTimestamp: simTs
1355
+ });
1302
1356
  throw err;
1303
1357
  });
1304
1358
  return {
@@ -1306,11 +1360,11 @@ export class SequencerPublisher {
1306
1360
  simulationResult
1307
1361
  };
1308
1362
  }
1309
- async addProposeTx(checkpoint, encodedData, opts = {}, timestamp) {
1363
+ async addProposeTx(checkpoint, encodedData, opts = {}) {
1310
1364
  const slot = checkpoint.header.slotNumber;
1311
1365
  const timer = new Timer();
1312
1366
  const kzg = Blob.getViemKzgInstance();
1313
- const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, timestamp, opts);
1367
+ const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
1314
1368
  const startBlock = await this.l1TxUtils.getBlockNumber();
1315
1369
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(simulationResult.gasUsed) * 64 / 63)) + blobEvaluationGas + SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS);
1316
1370
  // Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
@@ -1375,4 +1429,13 @@ export class SequencerPublisher {
1375
1429
  }
1376
1430
  });
1377
1431
  }
1432
+ /** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
1433
+ * for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */ getSimulationTimestamp(slot) {
1434
+ const l1Constants = this.epochCache.getL1Constants();
1435
+ return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
1436
+ }
1437
+ /** Returns the timestamp of the next L1 slot boundary after now. */ getNextL1SlotTimestamp() {
1438
+ const l1Constants = this.epochCache.getL1Constants();
1439
+ return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
1440
+ }
1378
1441
  }
@@ -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
- import type { Logger } from '@aztec/foundation/log';
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';
@@ -51,9 +52,9 @@ export declare class CheckpointProposalJob implements Traceable {
51
52
  private readonly metrics;
52
53
  private readonly eventEmitter;
53
54
  private readonly setStateFn;
54
- protected readonly log: Logger;
55
55
  readonly tracer: Tracer;
56
- 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, log: Logger, tracer: Tracer);
56
+ 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);
57
58
  /**
58
59
  * Executes the checkpoint proposal job.
59
60
  * Returns the published checkpoint if successful, undefined otherwise.
@@ -61,8 +62,26 @@ 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
+ failure: 'insufficient-txs' | 'insufficient-valid-txs';
81
+ } | {
82
+ error: Error;
83
+ }>;
84
+ private buildSingleBlockWithCheckpointBuilder;
66
85
  private waitForMinTxs;
67
86
  private waitForAttestations;
68
87
  /** Breaks the attestations before publishing based on attack configs */
@@ -70,10 +89,16 @@ export declare class CheckpointProposalJob implements Traceable {
70
89
  private dropFailedTxsFromP2P;
71
90
  private syncProposedBlockToArchiver;
72
91
  private handleCheckpointEndAsFisherman;
92
+ /**
93
+ * Helper to handle HA double-signing errors. Returns true if the error was handled (caller should yield).
94
+ */
95
+ private handleHASigningError;
73
96
  /** Waits until a specific time within the current slot */
74
97
  protected waitUntilTimeInSlot(targetSecondsIntoSlot: number): Promise<void>;
98
+ /** Waits the polling interval for transactions. Extracted for test overriding. */
99
+ protected waitForTxsPollingInterval(): Promise<void>;
75
100
  private getSlotStartBuildTimestamp;
76
101
  private getSecondsIntoSlot;
77
102
  getPublisher(): SequencerPublisher;
78
103
  }
79
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2twb2ludF9wcm9wb3NhbF9qb2IuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXF1ZW5jZXIvY2hlY2twb2ludF9wcm9wb3NhbF9qb2IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUFFLFdBQVcsRUFBRSxnQkFBZ0IsRUFBRSxXQUFXLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFHekcsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBRzNELE9BQU8sS0FBSyxFQUFFLE1BQU0sRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBRXBELE9BQU8sRUFBRSxLQUFLLFlBQVksRUFBUyxNQUFNLHlCQUF5QixDQUFDO0FBQ25FLE9BQU8sRUFBRSxLQUFLLGlCQUFpQixFQUFZLE1BQU0seUJBQXlCLENBQUM7QUFDM0UsT0FBTyxLQUFLLEVBQUUsR0FBRyxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxFQUFFLHNCQUFzQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDN0QsT0FBTyxFQUlMLEtBQUssV0FBVyxFQUNoQixLQUFLLGFBQWEsRUFFbkIsTUFBTSxxQkFBcUIsQ0FBQztBQUM3QixPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUczRCxPQUFPLEtBQUssRUFFVix1QkFBdUIsRUFDdkIsc0JBQXNCLEVBQ3ZCLE1BQU0saUNBQWlDLENBQUM7QUFDekMsT0FBTyxFQUFFLEtBQUssbUJBQW1CLEVBQW1DLE1BQU0seUJBQXlCLENBQUM7QUFNcEcsT0FBTyxFQUFjLEtBQUssU0FBUyxFQUFFLEtBQUssTUFBTSxFQUFhLE1BQU0seUJBQXlCLENBQUM7QUFDN0YsT0FBTyxFQUFxQixLQUFLLDBCQUEwQixFQUFFLEtBQUssZUFBZSxFQUFFLE1B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
104
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2twb2ludF9wcm9wb3NhbF9qb2IuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXF1ZW5jZXIvY2hlY2twb2ludF9wcm9wb3NhbF9qb2IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUNMLFdBQVcsRUFDWCxnQkFBZ0IsRUFDaEIsV0FBVyxFQUNYLHFCQUFxQixFQUNyQixVQUFVLEVBQ1gsTUFBTSxpQ0FBaUMsQ0FBQztBQVF6QyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFHM0QsT0FBTyxFQUFFLEtBQUssTUFBTSxFQUFFLEtBQUssY0FBYyxFQUFnQixNQUFNLHVCQUF1QixDQUFDO0FBRXZGLE9BQU8sRUFBRSxLQUFLLFlBQVksRUFBUyxNQUFNLHlCQUF5QixDQUFDO0FBQ25FLE9BQU8sRUFBRSxLQUFLLGlCQUFpQixFQUEwQixNQUFNLHlCQUF5QixDQUFDO0FBQ3pGLE9BQU8sS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUN0QyxPQUFPLEtBQUssRUFBRSxzQkFBc0IsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzdELE9BQU8sRUFHTCxPQUFPLEVBQ1AsS0FBSyxXQUFXLEVBQ2hCLEtBQUssYUFBYSxFQUVuQixNQUFNLHFCQUFxQixDQUFDO0FBQzdCLE9BQU8sRUFBRSxLQUFLLFVBQVUsRUFBc0IsTUFBTSwwQkFBMEIsQ0FBQztBQUcvRSxPQUFPLEVBR0wsS0FBSyx1QkFBdUIsRUFDNUIsS0FBSyxzQkFBc0IsRUFDNUIsTUFBTSxpQ0FBaUMsQ0FBQztBQUN6QyxPQUFPLEVBQUUsS0FBSyxtQkFBbUIsRUFBbUMsTUFBTSx5QkFBeUIsQ0FBQztBQVNwRyxPQUFPLEVBQWlCLEVBQUUsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRXJELE9BQU8sRUFBYyxLQUFLLFNB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