@aztec-labs/stdlib 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.
Files changed (47) hide show
  1. package/dest/avm/avm.d.ts +210 -210
  2. package/dest/checkpoint/checkpoint_reexecution_tracker.d.ts +25 -3
  3. package/dest/checkpoint/checkpoint_reexecution_tracker.d.ts.map +1 -1
  4. package/dest/checkpoint/checkpoint_reexecution_tracker.js +22 -1
  5. package/dest/checkpoint/simulation_overrides.d.ts +8 -1
  6. package/dest/checkpoint/simulation_overrides.d.ts.map +1 -1
  7. package/dest/checkpoint/simulation_overrides.js +6 -2
  8. package/dest/epoch-helpers/index.d.ts +1 -3
  9. package/dest/epoch-helpers/index.d.ts.map +1 -1
  10. package/dest/epoch-helpers/index.js +2 -4
  11. package/dest/gas/index.d.ts +2 -1
  12. package/dest/gas/index.d.ts.map +1 -1
  13. package/dest/gas/index.js +1 -0
  14. package/dest/gas/rollup_fee_config.d.ts +50 -0
  15. package/dest/gas/rollup_fee_config.d.ts.map +1 -0
  16. package/dest/gas/rollup_fee_config.js +105 -0
  17. package/dest/gas/tx_gas_limits.d.ts +9 -9
  18. package/dest/gas/tx_gas_limits.d.ts.map +1 -1
  19. package/dest/gas/tx_gas_limits.js +8 -7
  20. package/dest/interfaces/block-builder.d.ts +2 -2
  21. package/dest/interfaces/block-builder.d.ts.map +1 -1
  22. package/dest/interfaces/block-builder.js +1 -2
  23. package/dest/p2p/checkpoint_proposal.d.ts +1 -1
  24. package/dest/p2p/checkpoint_proposal.d.ts.map +1 -1
  25. package/dest/p2p/checkpoint_proposal.js +5 -3
  26. package/dest/rollup/checkpoint_rollup_public_inputs.d.ts +1 -1
  27. package/dest/validators/errors.d.ts +4 -2
  28. package/dest/validators/errors.d.ts.map +1 -1
  29. package/dest/validators/errors.js +6 -2
  30. package/dest/validators/schemas.d.ts +15 -1
  31. package/dest/validators/schemas.d.ts.map +1 -1
  32. package/dest/validators/schemas.js +2 -0
  33. package/dest/validators/types.d.ts +10 -4
  34. package/dest/validators/types.d.ts.map +1 -1
  35. package/package.json +7 -7
  36. package/src/checkpoint/checkpoint_reexecution_tracker.ts +48 -2
  37. package/src/checkpoint/simulation_overrides.ts +11 -2
  38. package/src/epoch-helpers/index.ts +0 -3
  39. package/src/gas/README.md +3 -3
  40. package/src/gas/index.ts +1 -0
  41. package/src/gas/rollup_fee_config.ts +141 -0
  42. package/src/gas/tx_gas_limits.ts +10 -7
  43. package/src/interfaces/block-builder.ts +1 -2
  44. package/src/p2p/checkpoint_proposal.ts +5 -3
  45. package/src/validators/errors.ts +4 -0
  46. package/src/validators/schemas.ts +2 -0
  47. package/src/validators/types.ts +10 -2
@@ -0,0 +1,141 @@
1
+ import type { RollupContract } from '@aztec-labs/ethereum/contracts';
2
+ import { type Logger, createLogger } from '@aztec-labs/foundation/log';
3
+ import { RunningPromise } from '@aztec-labs/foundation/running-promise';
4
+
5
+ /** Rollup fee settings that governance can change on L1, as read at one L1 block. */
6
+ export type RollupFeeConfig = {
7
+ manaTarget: bigint;
8
+ /** Read from L1 (getManaLimit), not derived as 2 x target. */
9
+ manaLimit: bigint;
10
+ provingCostPerManaInEth: bigint;
11
+ protocolFeeMarginBps: number;
12
+ /** L1 block the values were read at. */
13
+ l1BlockNumber: bigint;
14
+ };
15
+
16
+ /** Source of the rollup's live fee settings. */
17
+ export interface RollupFeeConfigProvider {
18
+ /**
19
+ * Last config successfully read from L1. Usually at most one polling interval old, but it stays at the last good
20
+ * read for as long as L1 reads keep failing.
21
+ */
22
+ getFeeConfig(): RollupFeeConfig;
23
+ }
24
+
25
+ /**
26
+ * Default polling interval. Each pass reads L1's block number through the client's cache, and reads the fee settings
27
+ * only when that block is newer than the last read, so it is cheap.
28
+ */
29
+ const DEFAULT_POLLING_INTERVAL_MS = 1_000;
30
+
31
+ /** Keeps a RollupFeeConfig in step with L1 by polling the rollup. */
32
+ export class RollupFeeConfigTracker implements RollupFeeConfigProvider {
33
+ private readonly loop: RunningPromise;
34
+
35
+ private constructor(
36
+ private readonly rollup: RollupContract,
37
+ private latest: RollupFeeConfig,
38
+ pollingIntervalMs: number,
39
+ private readonly log: Logger,
40
+ ) {
41
+ this.loop = new RunningPromise(() => this.poll(), log, pollingIntervalMs);
42
+ }
43
+
44
+ /** Does the first read, so getFeeConfig() always has a value. */
45
+ public static async create(
46
+ rollup: RollupContract,
47
+ opts: { pollingIntervalMs?: number; logger?: Logger } = {},
48
+ ): Promise<RollupFeeConfigTracker> {
49
+ const log = opts.logger ?? createLogger('rollup-fee-config');
50
+ const l1BlockNumber = await rollup.client.getBlockNumber();
51
+ const initial = await readRollupFeeConfig(rollup, l1BlockNumber);
52
+ log.info(`Read rollup fee config at L1 block ${l1BlockNumber}`, initial);
53
+ return new RollupFeeConfigTracker(rollup, initial, opts.pollingIntervalMs ?? DEFAULT_POLLING_INTERVAL_MS, log);
54
+ }
55
+
56
+ public start(): void {
57
+ this.loop.start();
58
+ }
59
+
60
+ public async stop(): Promise<void> {
61
+ await this.loop.stop();
62
+ }
63
+
64
+ public getFeeConfig(): RollupFeeConfig {
65
+ return this.latest;
66
+ }
67
+
68
+ /**
69
+ * One pass of the polling loop: reads the config at L1's latest block. It takes the client's cached block number and
70
+ * skips blocks it has already read, so a pass within the cache window costs no new reads, and a lagging RPC node
71
+ * cannot move the value back. A failed read only logs: the last value stays until a later pass succeeds.
72
+ */
73
+ private async poll(): Promise<void> {
74
+ try {
75
+ const l1BlockNumber = await this.rollup.client.getBlockNumber();
76
+ if (l1BlockNumber <= this.latest.l1BlockNumber) {
77
+ return;
78
+ }
79
+ this.update(await readRollupFeeConfig(this.rollup, l1BlockNumber));
80
+ } catch (err) {
81
+ this.log.warn(`Failed to refresh rollup fee config from L1, keeping the last value`, {
82
+ err,
83
+ l1BlockNumber: this.latest.l1BlockNumber,
84
+ });
85
+ }
86
+ }
87
+
88
+ private update(config: RollupFeeConfig): void {
89
+ const previous = this.latest;
90
+ this.latest = config;
91
+ if (
92
+ config.manaTarget !== previous.manaTarget ||
93
+ config.manaLimit !== previous.manaLimit ||
94
+ config.provingCostPerManaInEth !== previous.provingCostPerManaInEth ||
95
+ config.protocolFeeMarginBps !== previous.protocolFeeMarginBps
96
+ ) {
97
+ this.log.info(`Rollup fee config changed at L1 block ${config.l1BlockNumber}`, config);
98
+ }
99
+ }
100
+ }
101
+
102
+ /**
103
+ * A provider that never reads L1, for tests and for nodes that run without one. The default leaves the mana limit
104
+ * effectively unbounded.
105
+ */
106
+ export class TestRollupFeeConfigProvider implements RollupFeeConfigProvider {
107
+ private config: RollupFeeConfig;
108
+
109
+ constructor(config: Partial<RollupFeeConfig> = {}) {
110
+ this.config = { ...DEFAULT_TEST_ROLLUP_FEE_CONFIG, ...config };
111
+ }
112
+
113
+ /** Replaces some of the values, as a governance update would. */
114
+ public setFeeConfig(config: Partial<RollupFeeConfig>): void {
115
+ this.config = { ...this.config, ...config };
116
+ }
117
+
118
+ public getFeeConfig(): RollupFeeConfig {
119
+ return this.config;
120
+ }
121
+ }
122
+
123
+ const DEFAULT_TEST_ROLLUP_FEE_CONFIG: RollupFeeConfig = {
124
+ manaTarget: BigInt(Math.floor(Number.MAX_SAFE_INTEGER / 2)),
125
+ manaLimit: BigInt(Number.MAX_SAFE_INTEGER),
126
+ provingCostPerManaInEth: 0n,
127
+ protocolFeeMarginBps: 0,
128
+ l1BlockNumber: 0n,
129
+ };
130
+
131
+ /** Reads every fee setting at the same L1 block, so the values describe one state. */
132
+ async function readRollupFeeConfig(rollup: RollupContract, l1BlockNumber: bigint): Promise<RollupFeeConfig> {
133
+ const opts = { blockNumber: l1BlockNumber };
134
+ const [manaTarget, manaLimit, provingCostPerManaInEth, protocolFeeMarginBps] = await Promise.all([
135
+ rollup.getManaTarget(opts),
136
+ rollup.getManaLimit(opts),
137
+ rollup.getProvingCostPerMana(opts),
138
+ rollup.getProtocolFeeMargin(opts),
139
+ ]);
140
+ return { manaTarget, manaLimit, provingCostPerManaInEth, protocolFeeMarginBps, l1BlockNumber };
141
+ }
@@ -10,6 +10,7 @@ import {
10
10
  import { type ProposerTimetableConfig, buildProposerTimetable } from '../timetable/build_proposer_timetable.js';
11
11
  import type { SlotTimingConstants } from '../timetable/consensus_timetable.js';
12
12
  import { Gas } from './gas.js';
13
+ import type { RollupFeeConfig } from './rollup_fee_config.js';
13
14
 
14
15
  /**
15
16
  * Network-minimum per-block budget multiplier for L2 gas and tx-count allocation. A block packer must
@@ -75,7 +76,7 @@ export function getDaCheckpointBudgetForTxs(maxBlocksPerCheckpoint: number): num
75
76
  * raw blob capacity net of encoding overhead for every block — so the admission limit is consistent with the
76
77
  * builder's blob-field cap.
77
78
  *
78
- * @param manaCheckpointBudget - L2 (mana) budget per checkpoint (`rollupManaLimit`).
79
+ * @param manaCheckpointBudget - L2 (mana) budget per checkpoint (the rollup's live mana limit).
79
80
  */
80
81
  export function computeNetworkTxGasLimits(opts: { maxBlocksPerCheckpoint: number; manaCheckpointBudget: number }): Gas {
81
82
  const blocks = Math.max(1, opts.maxBlocksPerCheckpoint);
@@ -101,15 +102,17 @@ export function computeNetworkTxGasLimits(opts: { maxBlocksPerCheckpoint: number
101
102
  }
102
103
 
103
104
  /**
104
- * Network tx gas limits derived from a sequencer/p2p config and the L1 slot-timing + mana constants. The
105
- * single source of truth shared by `getNodeInfo` (advertising) and the RPC/gossip/pool gas validators
106
- * (enforcing), so a node never rejects a tx it advertised as admissible. Always uses the network-minimum
107
- * multipliers, never the node's (possibly higher) configured multipliers.
105
+ * Network tx gas limits derived from a sequencer/p2p config, the L1 slot-timing constants and the rollup's live
106
+ * mana limit. The single source of truth shared by `getNodeInfo` (advertising) and the RPC/gossip/pool gas
107
+ * validators (enforcing), so a node never rejects a tx it advertised as admissible. Always uses the network-minimum
108
+ * multipliers, never the node's (possibly higher) configured multipliers. Callers pass the fee config they read for
109
+ * this call, so a mana limit raised by governance takes effect without a restart.
108
110
  */
109
111
  export function getNetworkTxGasLimits(
110
112
  config: ProposerTimetableConfig,
111
- l1Constants: SlotTimingConstants & { rollupManaLimit: number },
113
+ l1Constants: SlotTimingConstants,
114
+ feeConfig: Pick<RollupFeeConfig, 'manaLimit'>,
112
115
  ): Gas {
113
116
  const maxBlocksPerCheckpoint = buildProposerTimetable(config, l1Constants).getMaxBlocksPerCheckpoint();
114
- return computeNetworkTxGasLimits({ maxBlocksPerCheckpoint, manaCheckpointBudget: l1Constants.rollupManaLimit });
117
+ return computeNetworkTxGasLimits({ maxBlocksPerCheckpoint, manaCheckpointBudget: Number(feeConfig.manaLimit) });
115
118
  }
@@ -87,7 +87,7 @@ export interface PublicProcessorValidator {
87
87
  nullifierCache?: { addNullifiers: (nullifiers: Buffer[]) => void };
88
88
  }
89
89
 
90
- export type FullNodeBlockBuilderConfig = Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration' | 'rollupManaLimit'> &
90
+ export type FullNodeBlockBuilderConfig = Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration'> &
91
91
  Pick<ChainConfig, 'l1ChainId' | 'rollupVersion'> &
92
92
  Pick<
93
93
  SequencerConfig,
@@ -112,7 +112,6 @@ export const FullNodeBlockBuilderConfigKeys: (keyof FullNodeBlockBuilderConfig)[
112
112
  'maxTxsPerCheckpoint',
113
113
  'maxL2BlockGas',
114
114
  'maxDABlockGas',
115
- 'rollupManaLimit',
116
115
  ] as const;
117
116
 
118
117
  /** Thrown when the number of successfully processed transactions is below the required minimum. */
@@ -310,9 +310,11 @@ export class CheckpointProposal extends Gossipable implements Signable {
310
310
 
311
311
  // Decoding is lenient on purpose, for forward compatibility: trailing bytes are ignored and any non-zero
312
312
  // presence flag reads as present, so a newer version can append fields that older nodes skip over. Rejecting
313
- // them would make every wire extension a coordinated upgrade. Lenient decoding does not let one proposal take
314
- // several identities, since the P2P message identifier and the signature both cover the signed payload rather
315
- // than the raw bytes.
313
+ // them would make every wire extension a coordinated upgrade. One signed payload can therefore reach the
314
+ // network under several encodings, each with its own P2P message identifier, which hashes the raw bytes. In
315
+ // particular the signature and the payload hash cover the checkpoint header, archive and fee modifier but not
316
+ // the embedded last block, so the same signed payload can travel with or without that block. The gossip layer
317
+ // must not treat a stored payload hash as covering the embedded block.
316
318
 
317
319
  const checkpointHeader = reader.readObject(CheckpointHeader);
318
320
  const archive = reader.readObject(Fr);
@@ -66,6 +66,10 @@ export class ReExStateMismatchError extends ValidatorError {
66
66
  constructor(
67
67
  public readonly expectedArchiveRoot: Fr,
68
68
  public readonly actualArchiveRoot: Fr,
69
+ // Which of the two comparisons failed. Only an archive-only mismatch binds a checkpoint that commits to this block
70
+ // as the committed sequence's last; see checkpointCommitsToRejectedBlock.
71
+ public readonly headerMismatch: boolean,
72
+ public readonly archiveMismatch: boolean,
69
73
  ) {
70
74
  super('Re-execution state mismatch');
71
75
  }
@@ -13,6 +13,8 @@ import type {
13
13
  export const ValidatorStatusInSlotSchema = zodFor<ValidatorStatusInSlot>()(
14
14
  z.enum([
15
15
  'checkpoint-mined',
16
+ 'checkpoint-unpublished',
17
+ 'checkpoint-orphaned',
16
18
  'checkpoint-valid',
17
19
  'checkpoint-invalid',
18
20
  'checkpoint-unvalidated',
@@ -6,13 +6,19 @@ export type ValidatorStatusType = 'proposer' | 'attestation';
6
6
  /**
7
7
  * Per-slot status for a validator.
8
8
  *
9
- * Proposer statuses (six-case taxonomy):
9
+ * Proposer statuses (six-case taxonomy, with case 5 refined into 5a and 5b):
10
10
  * - `blocks-missed` — no block proposals seen for the slot (case 1).
11
11
  * - `checkpoint-missed` — block proposals seen but no checkpoint proposal (case 2).
12
12
  * - `checkpoint-unvalidated` — checkpoint proposal seen but local re-execution couldn't
13
13
  * validate (missing txs, timeouts, etc.) (case 3).
14
14
  * - `checkpoint-invalid` — checkpoint proposal re-executed and rejected as invalid (case 4).
15
- * - `checkpoint-valid` — checkpoint proposal re-executed locally as valid (case 5).
15
+ * - `checkpoint-valid` — checkpoint proposal re-executed locally as valid, but it did not reach L1 and
16
+ * could not be refined into 5a or 5b (e.g. below quorum, equivocation) (case 5).
17
+ * - `checkpoint-orphaned` — valid checkpoint proposal that could not land because the parent it built on never
18
+ * landed on L1, or because an earlier slot landed its checkpoint number after it reached
19
+ * quorum (case 5b).
20
+ * - `checkpoint-unpublished` — valid checkpoint proposal with its parent on L1 and a quorum of attestations,
21
+ * which the proposer still failed to land on L1 (case 5a).
16
22
  * - `checkpoint-mined` — checkpoint published on L1 (case 6).
17
23
  *
18
24
  * Attestor statuses:
@@ -22,6 +28,8 @@ export type ValidatorStatusType = 'proposer' | 'attestation';
22
28
  */
23
29
  export type ValidatorStatusInSlot =
24
30
  | 'checkpoint-mined'
31
+ | 'checkpoint-unpublished'
32
+ | 'checkpoint-orphaned'
25
33
  | 'checkpoint-valid'
26
34
  | 'checkpoint-invalid'
27
35
  | 'checkpoint-unvalidated'