@aztec-labs/aztec-node 6.0.0 → 6.1.0-nightly.20261024

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.
@@ -1,14 +1,27 @@
1
+ import { INITIAL_CHECKPOINT_NUMBER } from '@aztec-labs/constants';
1
2
  import { requireStableCommittee } from '@aztec-labs/epoch-cache';
2
- import { CheckpointProposalHash, EpochNumber, SlotNumber } from '@aztec-labs/foundation/branded-types';
3
+ import { CheckpointNumber, CheckpointProposalHash, EpochNumber, SlotNumber } from '@aztec-labs/foundation/branded-types';
3
4
  import { countWhile, filterAsync, fromEntries, getEntries, mapValues } from '@aztec-labs/foundation/collection';
4
5
  import { EthAddress } from '@aztec-labs/foundation/eth-address';
5
6
  import { createLogger } from '@aztec-labs/foundation/log';
6
7
  import { RunningPromise } from '@aztec-labs/foundation/running-promise';
7
8
  import { OffenseType, WANT_TO_SLASH_EVENT, getOffenseTypeName } from '@aztec-labs/slasher';
8
9
  import { getAttestationInfoFromPublishedCheckpoint } from '@aztec-labs/stdlib/block';
9
- import { getEpochAtSlot, getSlotRangeForEpoch, getTimestampForSlot } from '@aztec-labs/stdlib/epoch-helpers';
10
+ import { computeQuorum, getEpochAtSlot, getSlotRangeForEpoch, getTimestampForSlot } from '@aztec-labs/stdlib/epoch-helpers';
10
11
  import { ConsensusPayload } from '@aztec-labs/stdlib/p2p';
11
12
  import EventEmitter from 'node:events';
13
+ const ORPHANED_PROPOSAL_REASON_DESCRIPTIONS = {
14
+ 'parent-not-on-l1': 'the parent checkpoint it built on never landed on L1',
15
+ 'parent-hash-mismatch': 'a different checkpoint landed on L1 in place of the parent it built on',
16
+ 'unexpected-parent-appeared': 'a checkpoint from an earlier slot landed on L1 with its checkpoint number after it was built on the previous one'
17
+ };
18
+ /**
19
+ * Whether the classification shows the proposal conflicts with a checkpoint that landed on L1, either in its parent's
20
+ * position or in its own. A validator that already held the landed checkpoint's blocks rejects the proposal's blocks as
21
+ * conflicting, so it is right not to attest.
22
+ */ function excusesMissingAttestations(classification) {
23
+ return classification !== undefined && 'reason' in classification && (classification.reason === 'parent-hash-mismatch' || classification.reason === 'unexpected-parent-appeared');
24
+ }
12
25
  /** Maps a validator status to its category: proposer or attestation. */ function statusToCategory(status) {
13
26
  switch(status){
14
27
  case 'attestation-sent':
@@ -47,12 +60,19 @@ import EventEmitter from 'node:events';
47
60
  * ## Six-case taxonomy in `getSlotActivity`
48
61
  *
49
62
  * For each slot, the sentinel assigns the proposer one of six statuses, ranked highest-confidence
50
- * first:
63
+ * first, with case 5 refined into 5a and 5b:
51
64
  *
52
65
  * - `checkpoint-mined` — a checkpoint covering this slot has landed on L1
53
66
  * (fetched on demand via `archiver.getCheckpoint({ slot })`).
67
+ * - `checkpoint-unpublished` — (5a) a valid checkpoint proposal built on a parent that is on L1
68
+ * reached quorum, yet the proposer did not land it.
69
+ * - `checkpoint-orphaned` — (5b) a valid checkpoint proposal could not land because the parent
70
+ * it built on never reached L1 (the proposer pipelining cascade), or
71
+ * because an earlier slot's checkpoint landed with its number after it
72
+ * had built on the previous one.
54
73
  * - `checkpoint-valid` — the local node re-executed a checkpoint proposal for this slot
55
- * successfully (consulted via `CheckpointReexecutionTracker`).
74
+ * successfully (consulted via `CheckpointReexecutionTracker`), and it
75
+ * could not be refined into 5a or 5b.
56
76
  * - `checkpoint-invalid` — the local node re-executed a checkpoint proposal for this slot
57
77
  * and rejected it (e.g. header/archive/out-hash mismatch, limit
58
78
  * breach). Proposer-fault.
@@ -62,9 +82,15 @@ import EventEmitter from 'node:events';
62
82
  * - `checkpoint-missed` — block proposals seen on P2P but no checkpoint proposal at all.
63
83
  * - `blocks-missed` — no block proposals seen for this slot.
64
84
  *
65
- * Missing-attestor faults are recorded only in `checkpoint-mined` and `checkpoint-valid`, where
66
- * the local node has positive evidence the checkpoint was canonical or valid. In the other four
67
- * cases the proposer is at fault and no attestor penalty applies.
85
+ * Missing-attestor faults are recorded only in `checkpoint-mined` and the three valid-proposal
86
+ * statuses (`checkpoint-unpublished`, `checkpoint-orphaned`, `checkpoint-valid`), where the local
87
+ * node has positive evidence the checkpoint was canonical or valid. In the other four cases the
88
+ * proposer is at fault and no attestor penalty applies. A valid proposal that conflicts with a checkpoint
89
+ * that landed on L1, in its parent's position or its own, is also exempt: a validator holding the landed
90
+ * checkpoint's blocks rightly refuses it.
91
+ *
92
+ * `checkpoint-unpublished` and `checkpoint-orphaned` are neutral for the proposer, like
93
+ * `checkpoint-valid`: neither counts toward `missedProposals` or inactivity slashing.
68
94
  *
69
95
  * ## Re-execution tracker
70
96
  *
@@ -100,8 +126,13 @@ import EventEmitter from 'node:events';
100
126
  initialSlot;
101
127
  lastProcessedSlot;
102
128
  /** Largest epoch number for which the end-of-epoch aggregator has run. */ lastEvaluatedEpoch;
129
+ /** Bumped on every history write so cached stats are never served from before the write. */ historyGeneration;
130
+ /**
131
+ * Stats for the default window, shared by every caller until the next processed slot or history write. Computing them
132
+ * scans and deserializes the full history of every validator, while the inputs only change once per slot.
133
+ */ defaultStatsCache;
103
134
  constructor(epochCache, archiver, p2p, store, reexecutionTracker, config, logger = createLogger('node:sentinel')){
104
- super(), this.epochCache = epochCache, this.archiver = archiver, this.p2p = p2p, this.store = store, this.reexecutionTracker = reexecutionTracker, this.config = config, this.logger = logger;
135
+ super(), this.epochCache = epochCache, this.archiver = archiver, this.p2p = p2p, this.store = store, this.reexecutionTracker = reexecutionTracker, this.config = config, this.logger = logger, this.historyGeneration = 0;
105
136
  const interval = epochCache.getL1Constants().ethereumSlotDuration * 1000 / 4;
106
137
  this.runningPromise = new RunningPromise(this.work.bind(this), logger, interval);
107
138
  }
@@ -391,8 +422,12 @@ import EventEmitter from 'node:events';
391
422
  *
392
423
  * Proposer status:
393
424
  * - case 6 `checkpoint-mined` — a checkpoint covering this slot has landed on L1.
425
+ * - case 5a `checkpoint-unpublished` — a valid proposal with its parent on L1 reached quorum but
426
+ * was not landed by the proposer.
427
+ * - case 5b `checkpoint-orphaned` — a valid proposal could not land because its parent never
428
+ * reached L1, or because an earlier slot took its checkpoint number.
394
429
  * - case 5 `checkpoint-valid` — the local node re-executed a checkpoint proposal for this
395
- * slot successfully.
430
+ * slot successfully, and it could not be refined into 5a or 5b.
396
431
  * - case 4 `checkpoint-invalid` — the local node re-executed a checkpoint proposal for this
397
432
  * slot and rejected it.
398
433
  * - case 3 `checkpoint-unvalidated` — the local node observed a checkpoint proposal for this
@@ -400,8 +435,9 @@ import EventEmitter from 'node:events';
400
435
  * - case 2 `checkpoint-missed` — block proposals seen on P2P but no checkpoint proposal.
401
436
  * - case 1 `blocks-missed` — no block proposals seen for this slot.
402
437
  *
403
- * Missing-attestor penalties apply only in cases 5 and 6, where the local node has positive
404
- * evidence the checkpoint was valid or has been canonicalised on L1.
438
+ * Missing-attestor penalties apply only in cases 5 (including 5a and 5b) and 6, where the local
439
+ * node has positive evidence the checkpoint was valid or has been canonicalised on L1, and never when
440
+ * the proposal conflicts with a checkpoint that landed in its parent's position or its own.
405
441
  */ async getSlotActivity(slot, epoch, proposer, committee) {
406
442
  this.logger.debug(`Computing stats for slot ${slot} at epoch ${epoch}`, {
407
443
  slot,
@@ -419,13 +455,16 @@ import EventEmitter from 'node:events';
419
455
  ...p2pAttestors.map((a)=>a.toString()),
420
456
  ...checkpoint?.attestors.map((a)=>a.toString()) ?? []
421
457
  ].filter((addr)=>proposer.toString() !== addr));
422
- // Determine the proposer status from the six-case taxonomy.
458
+ // Determine the proposer status from the six-case taxonomy. A valid proposal that never reached L1 (case 5) is
459
+ // refined into `checkpoint-unpublished` (5a) or `checkpoint-orphaned` (5b) when the evidence allows it.
423
460
  const reexecutionOutcome = this.reexecutionTracker.getOutcomeForSlot(slot);
424
461
  let status;
462
+ let classification;
425
463
  if (checkpoint) {
426
464
  status = 'checkpoint-mined';
427
465
  } else if (reexecutionOutcome === 'valid') {
428
- status = 'checkpoint-valid';
466
+ classification = await this.classifyUnpublishedProposal(slot, proposer, committee, p2pAttested);
467
+ status = classification.status;
429
468
  } else if (reexecutionOutcome === 'invalid') {
430
469
  status = 'checkpoint-invalid';
431
470
  } else if (reexecutionOutcome === 'unvalidated') {
@@ -444,9 +483,10 @@ import EventEmitter from 'node:events';
444
483
  // A local `invalid` verdict vetoes them even for a mined checkpoint (an honest validator refuses
445
484
  // to sign what it re-executed as invalid), and so does a proposal equivocation in the slot: with
446
485
  // two conflicting proposals an honest attestor may have seen an invalid one and correctly
447
- // declined, so neither must count as a missed attestor (inactivity slashing).
486
+ // declined, so neither must count as a missed attestor (inactivity slashing). Nor does a proposal that conflicts
487
+ // with a checkpoint that landed on L1 (see excusesMissingAttestations).
448
488
  const hasEquivocation = this.reexecutionTracker.hasEquivocation(slot);
449
- const attestorsExpected = (status === 'checkpoint-mined' || status === 'checkpoint-valid') && reexecutionOutcome !== 'invalid' && !hasEquivocation;
489
+ const attestorsExpected = (status === 'checkpoint-mined' || status === 'checkpoint-unpublished' || status === 'checkpoint-orphaned' || status === 'checkpoint-valid') && reexecutionOutcome !== 'invalid' && !hasEquivocation && !excusesMissingAttestations(classification);
450
490
  const missedAttestors = new Set(attestorsExpected ? committee.filter((v)=>!attestors.has(v.toString()) && !proposer.equals(v)).map((v)=>v.toString()) : []);
451
491
  this.logger.debug(`Retrieved ${attestors.size} attestors out of ${committee.length} for slot ${slot}`, {
452
492
  status,
@@ -478,10 +518,151 @@ import EventEmitter from 'node:events';
478
518
  statusFor(who)
479
519
  ]));
480
520
  }
481
- /** Push the status for each slot for each validator. */ updateValidators(slot, stats) {
482
- return this.store.updateValidators(slot, stats);
521
+ /**
522
+ * Refines case 5 (a checkpoint proposal for the slot re-executed as valid, with no checkpoint for the slot on L1)
523
+ * into whose fault it was that the proposal did not land:
524
+ *
525
+ * - `checkpoint-orphaned` (5b) when other proposers prevented it: the parent it built on never landed on L1 (it is
526
+ * missing, or a different checkpoint holds the parent's position), or, with a quorum of attestations, an earlier
527
+ * slot's checkpoint landed with its number after it built on the previous one (`unexpected-parent-appeared`).
528
+ * The latter happens when the earlier slot's proposal reached this proposer too late to build on.
529
+ * - `checkpoint-unpublished` (5a) when its parent is on L1 and a quorum of the committee attested, so the proposer
530
+ * had everything it needed to publish. A later slot that lands the same checkpoint number does not exempt it:
531
+ * that slot only got the number because this proposer failed to land it.
532
+ * - `checkpoint-valid` (5) otherwise: the slot had an equivocation, the tracker has no parent or payload data to
533
+ * judge by, or attestations fell short of quorum. Short of quorum is a fault of the attestors, who are tagged
534
+ * `attestation-missed`, except after an unexpected parent appeared (`unexpected-parent-appeared`), when
535
+ * refusing to attest was correct.
536
+ *
537
+ * Only attestations for the proposal's own payload count toward quorum, since those are the only ones its proposer
538
+ * could have published.
539
+ */ async classifyUnpublishedProposal(slot, proposer, committee, p2pAttested) {
540
+ if (this.reexecutionTracker.hasEquivocation(slot)) {
541
+ return {
542
+ status: 'checkpoint-valid',
543
+ reason: 'equivocation'
544
+ };
545
+ }
546
+ const record = this.reexecutionTracker.getRecordForSlot(slot);
547
+ const checkpointNumber = record?.checkpointNumber;
548
+ const lastArchiveRoot = record?.lastArchiveRoot;
549
+ const payloadHash = record?.payloadHash;
550
+ if (checkpointNumber === undefined || lastArchiveRoot === undefined || payloadHash === undefined) {
551
+ return {
552
+ status: 'checkpoint-valid',
553
+ reason: 'no-proposal-data'
554
+ };
555
+ }
556
+ const logData = {
557
+ slot,
558
+ proposer: proposer.toString(),
559
+ checkpointNumber
560
+ };
561
+ const orphaned = (reason, extra = {})=>{
562
+ this.logger.verbose(`Valid checkpoint proposal for slot ${slot} was orphaned (${reason}): ${ORPHANED_PROPOSAL_REASON_DESCRIPTIONS[reason]}`, {
563
+ ...logData,
564
+ ...extra,
565
+ reason
566
+ });
567
+ return {
568
+ status: 'checkpoint-orphaned',
569
+ reason
570
+ };
571
+ };
572
+ // The first checkpoint builds on genesis, which is always on L1.
573
+ const parentNumber = checkpointNumber - 1;
574
+ if (parentNumber >= INITIAL_CHECKPOINT_NUMBER) {
575
+ const parent = await this.archiver.getCheckpointData({
576
+ number: CheckpointNumber(parentNumber)
577
+ });
578
+ if (!parent) {
579
+ return orphaned('parent-not-on-l1');
580
+ }
581
+ if (parent.archive.root.toString() !== lastArchiveRoot) {
582
+ return orphaned('parent-hash-mismatch');
583
+ }
584
+ }
585
+ // The proposer's own attestation counts, as it does when its sequencer collects attestations for L1.
586
+ const committeeMembers = new Set(committee.map((member)=>member.toString()));
587
+ const attestationCount = new Set(p2pAttested.filter((attestation)=>attestation.getPayloadHash() === payloadHash).map((attestation)=>attestation.getSender()?.toString()).filter((attestor)=>attestor !== undefined && committeeMembers.has(attestor))).size;
588
+ const quorum = computeQuorum(committee.length);
589
+ // A checkpoint landed for the slot itself would have made this case 6, so a holder of the same number comes from
590
+ // an earlier or a later slot. Only an earlier one can have stopped this proposal from landing.
591
+ const sameNumber = await this.archiver.getCheckpointData({
592
+ number: checkpointNumber
593
+ });
594
+ const sameNumberSlot = sameNumber?.header.slotNumber;
595
+ if (sameNumberSlot !== undefined && sameNumberSlot < slot) {
596
+ const conflictData = {
597
+ takenBySlot: sameNumberSlot,
598
+ attestationCount,
599
+ quorum
600
+ };
601
+ if (attestationCount < quorum) {
602
+ this.logger.verbose(`Valid checkpoint proposal for slot ${slot} fell short of quorum after checkpoint ${checkpointNumber} landed ` + `from earlier slot ${sameNumberSlot} (unexpected-parent-appeared), so missing attestations are not counted`, {
603
+ ...logData,
604
+ ...conflictData,
605
+ reason: 'unexpected-parent-appeared'
606
+ });
607
+ return {
608
+ status: 'checkpoint-valid',
609
+ reason: 'unexpected-parent-appeared'
610
+ };
611
+ }
612
+ return orphaned('unexpected-parent-appeared', conflictData);
613
+ }
614
+ if (attestationCount < quorum) {
615
+ return {
616
+ status: 'checkpoint-valid',
617
+ reason: 'below-quorum'
618
+ };
619
+ }
620
+ this.logger.info(`Proposer did not publish checkpoint ${checkpointNumber} for slot ${slot} despite quorum`, {
621
+ ...logData,
622
+ attestationCount,
623
+ quorum
624
+ });
625
+ return {
626
+ status: 'checkpoint-unpublished'
627
+ };
628
+ }
629
+ /** Push the status for each slot for each validator. */ async updateValidators(slot, stats) {
630
+ this.historyGeneration++;
631
+ try {
632
+ await this.store.updateValidators(slot, stats);
633
+ } finally{
634
+ this.historyGeneration++;
635
+ }
636
+ }
637
+ /**
638
+ * Computes stats to be returned based on stored data. Calls without arguments cover the default window ending at the
639
+ * last processed slot and are served from a cache that is refreshed whenever that slot or the stored history changes.
640
+ */ computeStats(args = {}) {
641
+ const isDefaultWindow = args.fromSlot === undefined && args.toSlot === undefined && args.validators === undefined;
642
+ // Before the first processed slot the default window ends at the wall-clock slot, which moves without any write.
643
+ if (!isDefaultWindow || this.lastProcessedSlot === undefined) {
644
+ return this.computeStatsUncached(args);
645
+ }
646
+ const { lastProcessedSlot, initialSlot, historyGeneration } = this;
647
+ if (this.defaultStatsCache?.lastProcessedSlot !== lastProcessedSlot || this.defaultStatsCache.initialSlot !== initialSlot || this.defaultStatsCache.historyGeneration !== historyGeneration) {
648
+ const stats = this.computeStatsUncached(args);
649
+ const entry = {
650
+ lastProcessedSlot,
651
+ initialSlot,
652
+ historyGeneration,
653
+ stats
654
+ };
655
+ this.defaultStatsCache = entry;
656
+ // A failed computation is dropped so the next caller retries instead of receiving the cached rejection.
657
+ stats.catch(()=>{
658
+ if (this.defaultStatsCache === entry) {
659
+ this.defaultStatsCache = undefined;
660
+ }
661
+ });
662
+ }
663
+ return this.defaultStatsCache.stats;
483
664
  }
484
- /** Computes stats to be returned based on stored data. */ async computeStats({ fromSlot, toSlot, validators } = {}) {
665
+ async computeStatsUncached({ fromSlot, toSlot, validators }) {
485
666
  const histories = validators ? fromEntries(await Promise.all(validators.map(async (v)=>[
486
667
  v.toString(),
487
668
  await this.store.getHistory(v)
@@ -551,7 +732,7 @@ import EventEmitter from 'node:events';
551
732
  computeStatsForValidator(address, allHistory, fromSlot, toSlot) {
552
733
  let history = fromSlot ? allHistory.filter((h)=>BigInt(h.slot) >= fromSlot) : allHistory;
553
734
  history = toSlot ? history.filter((h)=>BigInt(h.slot) <= toSlot) : history;
554
- const lastProposal = history.filter((h)=>h.status === 'checkpoint-valid' || h.status === 'checkpoint-mined').at(-1);
735
+ const lastProposal = history.filter((h)=>h.status === 'checkpoint-valid' || h.status === 'checkpoint-unpublished' || h.status === 'checkpoint-orphaned' || h.status === 'checkpoint-mined').at(-1);
555
736
  const lastAttestation = history.filter((h)=>h.status === 'attestation-sent').at(-1);
556
737
  return {
557
738
  address: EthAddress.fromString(address),
@@ -40,4 +40,4 @@ export declare class SentinelStore {
40
40
  private statusToNumber;
41
41
  private statusFromNumber;
42
42
  }
43
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmUuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZW50aW5lbC9zdG9yZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLFVBQVUsRUFBRSxNQUFNLHNDQUFzQyxDQUFDO0FBQy9FLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUVoRSxPQUFPLEtBQUssRUFBRSxpQkFBaUIsRUFBaUIsTUFBTSxzQkFBc0IsQ0FBQztBQUM3RSxPQUFPLEtBQUssRUFDVixzQkFBc0IsRUFDdEIscUJBQXFCLEVBQ3JCLDBCQUEwQixFQUMzQixNQUFNLCtCQUErQixDQUFDO0FBRXZDLHFCQUFhLGFBQWE7SUFXdEIsT0FBTyxDQUFDLEtBQUs7SUFDYixPQUFPLENBQUMsTUFBTTtJQVhoQixnQkFBdUIsY0FBYyxLQUFLO0lBRzFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUF1QztJQUlsRSxPQUFPLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBdUM7SUFFaEUsWUFDVSxLQUFLLEVBQUUsaUJBQWlCLEVBQ3hCLE1BQU0sRUFBRTtRQUFFLGFBQWEsRUFBRSxNQUFNLENBQUM7UUFBQyw4QkFBOEIsRUFBRSxNQUFNLENBQUE7S0FBRSxFQUlsRjtJQUVNLGdCQUFnQixXQUV0QjtJQUVNLGlDQUFpQyxXQUV2QztJQUVZLHNCQUFzQixDQUFDLEtBQUssRUFBRSxXQUFXLEVBQUUsV0FBVyxFQUFFLDBCQUEwQixpQkFNOUY7SUFFWSxtQkFBbUIsQ0FBQyxHQUFHLEVBQUUsVUFBVSxHQUFHLE9BQU8sQ0FBQztRQUFFLE1BQU0sRUFBRSxNQUFNLENBQUM7UUFBQyxLQUFLLEVBQUUsTUFBTSxDQUFDO1FBQUMsS0FBSyxFQUFFLFdBQVcsQ0FBQTtLQUFFLEVBQUUsQ0FBQyxDQUdsSDtJQUVELDhHQUE4RztJQUNqRyx3QkFBd0IsQ0FDbkMsU0FBUyxFQUFFLFVBQVUsRUFBRSxHQUN0QixPQUFPLENBQUM7UUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDO1FBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQztRQUFDLEtBQUssRUFBRSxXQUFXLENBQUE7S0FBRSxFQUFFLEVBQUUsQ0FBQyxDQUdwRTtZQUVhLDZCQUE2QjtJQTZCOUIsZ0JBQWdCLENBQUMsSUFBSSxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsTUFBTSxDQUFDLEtBQUssTUFBTSxFQUFFLEVBQUUscUJBQXFCLEdBQUcsU0FBUyxDQUFDLGlCQVFqSDtZQUVhLDBCQUEwQjtJQVEzQixZQUFZLElBQUksT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLE1BQU0sRUFBRSxFQUFFLHNCQUFzQixDQUFDLENBQUMsQ0FNbEY7SUFFWSxVQUFVLENBQUMsT0FBTyxFQUFFLFVBQVUsR0FBRyxPQUFPLENBQUMsc0JBQXNCLEdBQUcsU0FBUyxDQUFDLENBR3hGO0lBRUQsMEZBQTBGO0lBQzdFLGVBQWUsQ0FBQyxTQUFTLEVBQUUsVUFBVSxFQUFFLEdBQUcsT0FBTyxDQUFDLENBQUMsc0JBQXNCLEdBQUcsU0FBUyxDQUFDLEVBQUUsQ0FBQyxDQUdyRztJQUVELE9BQU8sQ0FBQyxvQkFBb0I7SUFNNUIsT0FBTyxDQUFDLHNCQUFzQjtJQWE5QixPQUFPLENBQUMsZ0JBQWdCO0lBTXhCLE9BQU8sQ0FBQyxrQkFBa0I7SUFXMUIsT0FBTyxDQUFDLGNBQWM7SUF5QnRCLE9BQU8sQ0FBQyxnQkFBZ0I7Q0FzQnpCIn0=
43
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmUuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZW50aW5lbC9zdG9yZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLFVBQVUsRUFBRSxNQUFNLHNDQUFzQyxDQUFDO0FBQy9FLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUVoRSxPQUFPLEtBQUssRUFBRSxpQkFBaUIsRUFBaUIsTUFBTSxzQkFBc0IsQ0FBQztBQUM3RSxPQUFPLEtBQUssRUFDVixzQkFBc0IsRUFDdEIscUJBQXFCLEVBQ3JCLDBCQUEwQixFQUMzQixNQUFNLCtCQUErQixDQUFDO0FBRXZDLHFCQUFhLGFBQWE7SUFXdEIsT0FBTyxDQUFDLEtBQUs7SUFDYixPQUFPLENBQUMsTUFBTTtJQVhoQixnQkFBdUIsY0FBYyxLQUFLO0lBRzFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUF1QztJQUlsRSxPQUFPLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBdUM7SUFFaEUsWUFDVSxLQUFLLEVBQUUsaUJBQWlCLEVBQ3hCLE1BQU0sRUFBRTtRQUFFLGFBQWEsRUFBRSxNQUFNLENBQUM7UUFBQyw4QkFBOEIsRUFBRSxNQUFNLENBQUE7S0FBRSxFQUlsRjtJQUVNLGdCQUFnQixXQUV0QjtJQUVNLGlDQUFpQyxXQUV2QztJQUVZLHNCQUFzQixDQUFDLEtBQUssRUFBRSxXQUFXLEVBQUUsV0FBVyxFQUFFLDBCQUEwQixpQkFNOUY7SUFFWSxtQkFBbUIsQ0FBQyxHQUFHLEVBQUUsVUFBVSxHQUFHLE9BQU8sQ0FBQztRQUFFLE1BQU0sRUFBRSxNQUFNLENBQUM7UUFBQyxLQUFLLEVBQUUsTUFBTSxDQUFDO1FBQUMsS0FBSyxFQUFFLFdBQVcsQ0FBQTtLQUFFLEVBQUUsQ0FBQyxDQUdsSDtJQUVELDhHQUE4RztJQUNqRyx3QkFBd0IsQ0FDbkMsU0FBUyxFQUFFLFVBQVUsRUFBRSxHQUN0QixPQUFPLENBQUM7UUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDO1FBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQztRQUFDLEtBQUssRUFBRSxXQUFXLENBQUE7S0FBRSxFQUFFLEVBQUUsQ0FBQyxDQUdwRTtZQUVhLDZCQUE2QjtJQTZCOUIsZ0JBQWdCLENBQUMsSUFBSSxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsTUFBTSxDQUFDLEtBQUssTUFBTSxFQUFFLEVBQUUscUJBQXFCLEdBQUcsU0FBUyxDQUFDLGlCQVFqSDtZQUVhLDBCQUEwQjtJQVEzQixZQUFZLElBQUksT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLE1BQU0sRUFBRSxFQUFFLHNCQUFzQixDQUFDLENBQUMsQ0FNbEY7SUFFWSxVQUFVLENBQUMsT0FBTyxFQUFFLFVBQVUsR0FBRyxPQUFPLENBQUMsc0JBQXNCLEdBQUcsU0FBUyxDQUFDLENBR3hGO0lBRUQsMEZBQTBGO0lBQzdFLGVBQWUsQ0FBQyxTQUFTLEVBQUUsVUFBVSxFQUFFLEdBQUcsT0FBTyxDQUFDLENBQUMsc0JBQXNCLEdBQUcsU0FBUyxDQUFDLEVBQUUsQ0FBQyxDQUdyRztJQUVELE9BQU8sQ0FBQyxvQkFBb0I7SUFNNUIsT0FBTyxDQUFDLHNCQUFzQjtJQWE5QixPQUFPLENBQUMsZ0JBQWdCO0lBTXhCLE9BQU8sQ0FBQyxrQkFBa0I7SUFXMUIsT0FBTyxDQUFDLGNBQWM7SUE2QnRCLE9BQU8sQ0FBQyxnQkFBZ0I7Q0EwQnpCIn0=
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/sentinel/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAEhE,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,sBAAsB,CAAC;AAC7E,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,+BAA+B,CAAC;AAEvC,qBAAa,aAAa;IAWtB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,MAAM;IAXhB,gBAAuB,cAAc,KAAK;IAG1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuC;IAIlE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAEhE,YACU,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,8BAA8B,EAAE,MAAM,CAAA;KAAE,EAIlF;IAEM,gBAAgB,WAEtB;IAEM,iCAAiC,WAEvC;IAEY,sBAAsB,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,0BAA0B,iBAM9F;IAEY,mBAAmB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,EAAE,CAAC,CAGlH;IAED,8GAA8G;IACjG,wBAAwB,CACnC,SAAS,EAAE,UAAU,EAAE,GACtB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,EAAE,EAAE,CAAC,CAGpE;YAEa,6BAA6B;IA6B9B,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,qBAAqB,GAAG,SAAS,CAAC,iBAQjH;YAEa,0BAA0B;IAQ3B,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC,CAMlF;IAEY,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAGxF;IAED,0FAA0F;IAC7E,eAAe,CAAC,SAAS,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,CAAC,sBAAsB,GAAG,SAAS,CAAC,EAAE,CAAC,CAGrG;IAED,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,gBAAgB;CAsBzB"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/sentinel/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAEhE,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,sBAAsB,CAAC;AAC7E,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,+BAA+B,CAAC;AAEvC,qBAAa,aAAa;IAWtB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,MAAM;IAXhB,gBAAuB,cAAc,KAAK;IAG1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuC;IAIlE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAEhE,YACU,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,8BAA8B,EAAE,MAAM,CAAA;KAAE,EAIlF;IAEM,gBAAgB,WAEtB;IAEM,iCAAiC,WAEvC;IAEY,sBAAsB,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,0BAA0B,iBAM9F;IAEY,mBAAmB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,EAAE,CAAC,CAGlH;IAED,8GAA8G;IACjG,wBAAwB,CACnC,SAAS,EAAE,UAAU,EAAE,GACtB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,EAAE,EAAE,CAAC,CAGpE;YAEa,6BAA6B;IA6B9B,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,qBAAqB,GAAG,SAAS,CAAC,iBAQjH;YAEa,0BAA0B;IAQ3B,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC,CAMlF;IAEY,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAGxF;IAED,0FAA0F;IAC7E,eAAe,CAAC,SAAS,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,CAAC,sBAAsB,GAAG,SAAS,CAAC,EAAE,CAAC,CAGrG;IAED,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,cAAc;IA6BtB,OAAO,CAAC,gBAAgB;CA0BzB"}
@@ -158,6 +158,10 @@ export class SentinelStore {
158
158
  return 7;
159
159
  case 'checkpoint-unvalidated':
160
160
  return 8;
161
+ case 'checkpoint-unpublished':
162
+ return 9;
163
+ case 'checkpoint-orphaned':
164
+ return 10;
161
165
  default:
162
166
  {
163
167
  const _exhaustive = status;
@@ -183,6 +187,10 @@ export class SentinelStore {
183
187
  return 'checkpoint-invalid';
184
188
  case 8:
185
189
  return 'checkpoint-unvalidated';
190
+ case 9:
191
+ return 'checkpoint-unpublished';
192
+ case 10:
193
+ return 'checkpoint-orphaned';
186
194
  default:
187
195
  throw new Error(`Unknown status: ${status}`);
188
196
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec-labs/aztec-node",
3
- "version": "6.0.0",
3
+ "version": "6.1.0-nightly.20261024",
4
4
  "main": "dest/index.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -65,38 +65,38 @@
65
65
  ]
66
66
  },
67
67
  "dependencies": {
68
- "@aztec-foundation/bb.js": "6.0.0",
69
- "@aztec-labs/archiver": "6.0.0",
70
- "@aztec-labs/bb-prover": "6.0.0",
71
- "@aztec-labs/blob-client": "6.0.0",
72
- "@aztec-labs/blob-lib": "6.0.0",
73
- "@aztec-labs/constants": "6.0.0",
74
- "@aztec-labs/epoch-cache": "6.0.0",
75
- "@aztec-labs/ethereum": "6.0.0",
76
- "@aztec-labs/foundation": "6.0.0",
77
- "@aztec-labs/kv-store": "6.0.0",
78
- "@aztec-labs/node-keystore": "6.0.0",
79
- "@aztec-labs/node-lib": "6.0.0",
80
- "@aztec-labs/p2p": "6.0.0",
81
- "@aztec-labs/protocol-contracts": "6.0.0",
82
- "@aztec-labs/prover-client": "6.0.0",
83
- "@aztec-labs/prover-node": "6.0.0",
84
- "@aztec-labs/sequencer-client": "6.0.0",
85
- "@aztec-labs/simulator": "6.0.0",
86
- "@aztec-labs/slasher": "6.0.0",
87
- "@aztec-labs/standard-contracts": "6.0.0",
88
- "@aztec-labs/stdlib": "6.0.0",
89
- "@aztec-labs/telemetry-client": "6.0.0",
90
- "@aztec-labs/validator-client": "6.0.0",
91
- "@aztec-labs/validator-ha-signer": "6.0.0",
92
- "@aztec-labs/world-state": "6.0.0",
68
+ "@aztec-foundation/bb.js": "6.0.0-rc.7",
69
+ "@aztec-labs/archiver": "6.1.0-nightly.20261024",
70
+ "@aztec-labs/bb-prover": "6.1.0-nightly.20261024",
71
+ "@aztec-labs/blob-client": "6.1.0-nightly.20261024",
72
+ "@aztec-labs/blob-lib": "6.1.0-nightly.20261024",
73
+ "@aztec-labs/constants": "6.1.0-nightly.20261024",
74
+ "@aztec-labs/epoch-cache": "6.1.0-nightly.20261024",
75
+ "@aztec-labs/ethereum": "6.1.0-nightly.20261024",
76
+ "@aztec-labs/foundation": "6.1.0-nightly.20261024",
77
+ "@aztec-labs/kv-store": "6.1.0-nightly.20261024",
78
+ "@aztec-labs/node-keystore": "6.1.0-nightly.20261024",
79
+ "@aztec-labs/node-lib": "6.1.0-nightly.20261024",
80
+ "@aztec-labs/p2p": "6.1.0-nightly.20261024",
81
+ "@aztec-labs/protocol-contracts": "6.1.0-nightly.20261024",
82
+ "@aztec-labs/prover-client": "6.1.0-nightly.20261024",
83
+ "@aztec-labs/prover-node": "6.1.0-nightly.20261024",
84
+ "@aztec-labs/sequencer-client": "6.1.0-nightly.20261024",
85
+ "@aztec-labs/simulator": "6.1.0-nightly.20261024",
86
+ "@aztec-labs/slasher": "6.1.0-nightly.20261024",
87
+ "@aztec-labs/standard-contracts": "6.1.0-nightly.20261024",
88
+ "@aztec-labs/stdlib": "6.1.0-nightly.20261024",
89
+ "@aztec-labs/telemetry-client": "6.1.0-nightly.20261024",
90
+ "@aztec-labs/validator-client": "6.1.0-nightly.20261024",
91
+ "@aztec-labs/validator-ha-signer": "6.1.0-nightly.20261024",
92
+ "@aztec-labs/world-state": "6.1.0-nightly.20261024",
93
93
  "koa": "^2.16.1",
94
94
  "koa-router": "^13.1.1",
95
95
  "tslib": "^2.4.0",
96
96
  "viem": "2.57.1"
97
97
  },
98
98
  "devDependencies": {
99
- "@aztec-labs/noir-protocol-circuits-types": "6.0.0",
99
+ "@aztec-labs/noir-protocol-circuits-types": "6.1.0-nightly.20261024",
100
100
  "@jest/globals": "^30.0.0",
101
101
  "@types/jest": "^30.0.0",
102
102
  "@types/node": "^22.15.17",
@@ -275,6 +275,17 @@ export class NextBlockFeeCache {
275
275
  return pinnedTips();
276
276
  }
277
277
 
278
+ // The plan's L1 reads are pinned to the archiver's sync point, which only exists once the archiver has run a
279
+ // sync pass. Until then there is no L1 block to read the parent's fee header at, so price on the checkpointed tip,
280
+ // as the fee simulation itself falls back to L1's head.
281
+ const l1SyncPoint = frontier.l1SyncPoint;
282
+ if (l1SyncPoint === undefined) {
283
+ this.log.debug(
284
+ `Pricing the next-block boundary on checkpointed tip ${checkpointedCheckpointNumber}: no L1 sync point yet`,
285
+ );
286
+ return pinnedTips();
287
+ }
288
+
278
289
  // The helper treats pipelining and invalidation as mutually exclusive; a proposed parent takes precedence.
279
290
  const validationStatus = frontier.pendingChainValidationStatus;
280
291
  const invalidateToPendingCheckpointNumber =
@@ -287,6 +298,8 @@ export class NextBlockFeeCache {
287
298
  invalidateToPendingCheckpointNumber,
288
299
  checkpointedCheckpointNumber,
289
300
  rollup,
301
+ // The same L1 block the fee simulation is pinned to, so the parent's fee header and the fee agree.
302
+ l1BlockNumber: l1SyncPoint.blockNumber,
290
303
  signatureContext: this.signatureContext,
291
304
  log: this.log,
292
305
  });
@@ -54,7 +54,12 @@ import type {
54
54
  ProtocolContractAddresses,
55
55
  } from '@aztec-labs/stdlib/contract';
56
56
  import type { L1RollupConstants } from '@aztec-labs/stdlib/epoch-helpers';
57
- import { GasFees, type ManaUsageEstimate, getNetworkTxGasLimits } from '@aztec-labs/stdlib/gas';
57
+ import {
58
+ GasFees,
59
+ type ManaUsageEstimate,
60
+ type RollupFeeConfigProvider,
61
+ getNetworkTxGasLimits,
62
+ } from '@aztec-labs/stdlib/gas';
58
63
  import type {
59
64
  AztecNode,
60
65
  AztecNodeAdmin,
@@ -146,6 +151,8 @@ export interface AztecNodeServiceDeps {
146
151
  rollupContract: RollupContract | undefined;
147
152
  feeProvider: FeeProvider;
148
153
  nextBlockPredictor: NextBlockPredictor;
154
+ /** Live rollup fee settings, for the mana limit tx gas limits are derived from. Stopped with the node. */
155
+ feeConfigProvider: RollupFeeConfigProvider;
149
156
  epochCache: EpochCacheInterface;
150
157
  packageVersion: string;
151
158
  peerProofVerifier: ClientProtocolCircuitVerifier;
@@ -199,6 +206,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
199
206
  protected readonly rollupContract: RollupContract | undefined;
200
207
  protected readonly feeProvider: FeeProvider;
201
208
  protected readonly nextBlockPredictor: NextBlockPredictor;
209
+ protected readonly feeConfigProvider: RollupFeeConfigProvider;
202
210
  protected readonly epochCache: EpochCacheInterface;
203
211
  protected readonly packageVersion: string;
204
212
  private peerProofVerifier: ClientProtocolCircuitVerifier;
@@ -231,6 +239,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
231
239
  this.rollupContract = deps.rollupContract;
232
240
  this.feeProvider = deps.feeProvider;
233
241
  this.nextBlockPredictor = deps.nextBlockPredictor;
242
+ this.feeConfigProvider = deps.feeConfigProvider;
234
243
  this.epochCache = deps.epochCache;
235
244
  this.packageVersion = deps.packageVersion;
236
245
  this.peerProofVerifier = deps.peerProofVerifier;
@@ -313,8 +322,15 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
313
322
  return this.blockSource.getL2Tips();
314
323
  }
315
324
 
316
- public getL1Constants(): Promise<L1RollupConstants> {
317
- return this.blockSource.getL1Constants();
325
+ public async getL1Constants(): Promise<L1RollupConstants> {
326
+ const constants = await this.blockSource.getL1Constants();
327
+ // Older clients' schemas still require `rollupManaLimit`, so keep sending it, now with the live value. Newer
328
+ // clients' schemas strip the extra key.
329
+ const response: L1RollupConstants & { rollupManaLimit: number } = {
330
+ ...constants,
331
+ rollupManaLimit: Number(this.feeConfigProvider.getFeeConfig().manaLimit),
332
+ };
333
+ return response;
318
334
  }
319
335
 
320
336
  public getSyncedL2SlotNumber() {
@@ -465,7 +481,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
465
481
  // timetable's blocks-per-checkpoint and the network-minimum per-block multipliers) — never this node's
466
482
  // local caps or configured multipliers, which can make the node stricter at block-building time but
467
483
  // cannot define what the network accepts for relay. Clients read txsLimits to set fallback gas limits.
468
- const maxTxGas = getNetworkTxGasLimits(this.config, l1Constants);
484
+ const maxTxGas = getNetworkTxGasLimits(this.config, l1Constants, this.feeConfigProvider.getFeeConfig());
469
485
 
470
486
  const nodeInfo: NodeInfo = {
471
487
  nodeVersion,
@@ -632,6 +648,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
632
648
  await tryStop(this.p2pClient);
633
649
  await tryStop(this.nextBlockPredictor);
634
650
  await tryStop(this.feeProvider);
651
+ await tryStop(this.feeConfigProvider);
635
652
  // Dispose the AVM backend before world state: it kills the bb-avm-sim processes and closes the CDB IPC
636
653
  // server, releasing their connections to the WSDB so it shuts down cleanly (and freeing the
637
654
  // Server/Socket/ChildProcess handles that would otherwise keep the process alive after teardown).
@@ -827,7 +844,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
827
844
  const l1Constants = await this.blockSource.getL1Constants();
828
845
  // Enforce the same network admission limit the node advertises in getNodeInfo (network-wide, not this
829
846
  // node's local caps), so a tx the wallet sized against txsLimits is not rejected here.
830
- const networkTxGasLimits = getNetworkTxGasLimits(this.config, l1Constants);
847
+ const networkTxGasLimits = getNetworkTxGasLimits(this.config, l1Constants, this.feeConfigProvider.getFeeConfig());
831
848
  const validator = createTxValidatorForAcceptingTxsOverRPC(
832
849
  db,
833
850
  this.contractDataSource,
package/src/factory.ts CHANGED
@@ -39,6 +39,7 @@ import {
39
39
  createSlasher,
40
40
  } from '@aztec-labs/slasher';
41
41
  import { CheckpointReexecutionTracker } from '@aztec-labs/stdlib/checkpoint';
42
+ import { RollupFeeConfigTracker } from '@aztec-labs/stdlib/gas';
42
43
  import { type ClientProtocolCircuitVerifier, tryStop } from '@aztec-labs/stdlib/interfaces/server';
43
44
  import { type DebugLogStore, InMemoryDebugLogStore, NullDebugLogStore } from '@aztec-labs/stdlib/logs';
44
45
  import { getPackageVersion } from '@aztec-labs/stdlib/update-checker';
@@ -185,12 +186,11 @@ export async function createAztecNodeService(
185
186
  // Read-only Inbox handle: proposal validation confirms a checkpoint's final message position against the live
186
187
  // bucket ring before accepting it, so every node needs one, validator or not.
187
188
  const inboxContract = new InboxContract(publicClient, config.inboxAddress);
188
- const [l1GenesisTime, slotDuration, epochDuration, rollupVersionFromRollup, rollupManaLimit] = await Promise.all([
189
+ const [l1GenesisTime, slotDuration, epochDuration, rollupVersionFromRollup] = await Promise.all([
189
190
  rollupContract.getL1GenesisTime(),
190
191
  rollupContract.getSlotDuration(),
191
192
  rollupContract.getEpochDuration(),
192
193
  rollupContract.getVersion(),
193
- rollupContract.getManaLimit().then(Number),
194
194
  ] as const);
195
195
 
196
196
  config.rollupVersion ??= Number(rollupVersionFromRollup);
@@ -211,6 +211,14 @@ export async function createAztecNodeService(
211
211
  // Track started resources so we can clean up on partial failure during node creation.
212
212
  const started: { stop?(): Promise<void> | void }[] = [];
213
213
  try {
214
+ // Governance can change the rollup's fee settings (the mana target and limit among them) at any time, so every
215
+ // component that needs them reads them from this tracker rather than from a copy taken at startup.
216
+ const feeConfigTracker = await RollupFeeConfigTracker.create(rollupContract, {
217
+ logger: log.createChild('rollup-fee-config'),
218
+ });
219
+ feeConfigTracker.start();
220
+ started.push(feeConfigTracker);
221
+
214
222
  config.skipOrphanProposedBlockPruning ||= !!config.useAutomineSequencer;
215
223
 
216
224
  checkConfigMatchesRollup(config, {
@@ -234,7 +242,7 @@ export async function createAztecNodeService(
234
242
  started.push({ stop: () => avmSimulator[Symbol.asyncDispose]() });
235
243
  const archiver = await createArchiver(
236
244
  config,
237
- { blobClient, epochCache, telemetry, dateProvider },
245
+ { blobClient, epochCache, telemetry, dateProvider, feeConfigProvider: feeConfigTracker },
238
246
  { blockUntilSync: !config.skipArchiverInitialSync },
239
247
  initialHeader,
240
248
  initialBlockHash,
@@ -321,6 +329,7 @@ export async function createAztecNodeService(
321
329
  worldStateSynchronizer,
322
330
  epochCache,
323
331
  new AdmissionMinFeesProvider(nextBlockPredictor, feeProvider),
332
+ feeConfigTracker,
324
333
  packageVersion,
325
334
  dateProvider,
326
335
  telemetry,
@@ -340,13 +349,13 @@ export async function createAztecNodeService(
340
349
  ...config,
341
350
  l1GenesisTime,
342
351
  slotDuration: Number(slotDuration),
343
- rollupManaLimit,
344
352
  maxTxsPerCheckpoint: config.validateMaxTxsPerCheckpoint,
345
353
  },
346
354
  worldStateSynchronizer,
347
355
  archiver,
348
356
  dateProvider,
349
357
  avmSimulator,
358
+ feeConfigTracker,
350
359
  telemetry,
351
360
  );
352
361
 
@@ -563,11 +572,12 @@ export async function createAztecNodeService(
563
572
 
564
573
  // Create and start the sequencer client
565
574
  const checkpointsBuilder = new CheckpointsBuilder(
566
- { ...config, l1GenesisTime, slotDuration: Number(slotDuration), rollupManaLimit },
575
+ { ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
567
576
  worldStateSynchronizer,
568
577
  archiver,
569
578
  dateProvider,
570
579
  avmSimulator,
580
+ feeConfigTracker,
571
581
  telemetry,
572
582
  debugLogStore,
573
583
  );
@@ -596,7 +606,6 @@ export async function createAztecNodeService(
596
606
  l1GenesisTime,
597
607
  slotDuration: Number(slotDuration),
598
608
  ethereumSlotDuration: config.ethereumSlotDuration,
599
- rollupManaLimit,
600
609
  },
601
610
  autoSettle: config.automineEnableProveEpoch,
602
611
  log,
@@ -612,6 +621,7 @@ export async function createAztecNodeService(
612
621
  worldStateSynchronizer,
613
622
  slasherClient,
614
623
  checkpointsBuilder,
624
+ feeConfigProvider: feeConfigTracker,
615
625
  l2BlockSource: archiver,
616
626
  l1ToL2MessageSource: archiver,
617
627
  telemetry,
@@ -683,6 +693,7 @@ export async function createAztecNodeService(
683
693
  rollupContract,
684
694
  feeProvider,
685
695
  nextBlockPredictor,
696
+ feeConfigProvider: feeConfigTracker,
686
697
  epochCache,
687
698
  packageVersion,
688
699
  peerProofVerifier,