feat: merge-train/spartan-v5 - #23936
Merged
Merged
Conversation
So we can dump in there tmp files like plans, analysis, etc.
…23933) ## Motivation `GasLimitsValidator` caps a tx's declared DA gas limit at `min(MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT, maxBlockDAGas ?? Infinity)`. With no per-block DA cap configured (the default), this allows a tx to declare up to the full checkpoint DA limit of 786,432 DA gas. However, a single tx's effects cannot encode more than `MAX_TX_BLOB_DATA_SIZE_IN_FIELDS` (8475) fields in a blob, and DA gas is charged per field at `DA_GAS_PER_FIELD` (32), so the most DA gas any tx can actually consume is 271,200 — roughly 1/3 of what inbound validation currently permits. The sequencer uses declared tx gas limits pessimistically during proposal building, before processing txs. A tx that declares an unnecessarily high DA gas limit can reserve a disproportionate share of the block/checkpoint DA budget in the pre-processing admission check even though it can never use that much DA, hurting block packing and creating an avoidable griefing vector where valid txs get skipped during block construction. ## Approach Cap the declared DA gas limit at the maximum a tx can physically consume: ``` min(MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT, maxBlockDAGas, MAX_TX_BLOB_DATA_SIZE_IN_FIELDS * DA_GAS_PER_FIELD) ``` `MAX_TX_BLOB_DATA_SIZE_IN_FIELDS` lives in `tx_blob_data.nr` (alongside circuit code), not in `constants.nr`, so it was not exported to the TS constants. Rather than duplicate the value, the constants generator now supports pulling specific constants from additional Noir files via an explicit allowlist, so `MAX_TX_BLOB_DATA_SIZE_IN_FIELDS` is generated from its single Noir source of truth with no risk of drift. - **Generator**: `ADDITIONAL_NOIR_CONSTANT_FILES` lists extra files and the exact constant names to extract from each. `parseNoirFile` now returns raw (unevaluated) expressions so cross-file references resolve in a single evaluation pass; an opt-in line-comment stripper handles inline `//` comments in the blob-size definition. The generator throws if a listed constant is missing, guarding against silent drift on rename. - The new constant binds below the checkpoint cap but above the default `fallback()` gas settings (196,608 DA), and estimation settings use `skipTxValidation`, so normal tx flows are unaffected. This is sound because DA gas is uniformly charged as `(fields written to the tx blob) × DA_GAS_PER_FIELD` at every layer (private kernel `gas_meter.nr`, AVM per-opcode, and the settled on-chain block DA in `l2_block.ts`), and the tx blob is a fixed-size `[Field; MAX_TX_BLOB_DATA_SIZE_IN_FIELDS]` array — a hard ceiling. The cap only rejects over-provisioned declarations a tx could never use. ### Defense-in-depth: per-tx blob field check in the public processor The public processor already skips a processed tx whose blob fields would overflow the remaining *checkpoint* budget, but that does not enforce the *per-tx* `MAX_TX_BLOB_DATA_SIZE_IN_FIELDS` ceiling. The per-category side-effect limits (note hashes, nullifiers, L2→L1, public data writes, private/public/contract-class logs) already guarantee a single tx cannot exceed the per-tx blob size, so this is unreachable in practice. As defense-in-depth — and because an oversized tx effect would otherwise poison proving (the rollup circuit cannot encode it) rather than fail gracefully — the processor now rejects any tx whose resulting effects exceed `MAX_TX_BLOB_DATA_SIZE_IN_FIELDS` as invalid (not a silent skip), since no valid tx should ever produce that many fields. ### Wallet: bound gas-estimate padding to the cap Wallets pad simulated gas by a buffer (default 10%) before sending. Since the unpadded DA estimate can be at most `MAX_TX_BLOB_DATA_SIZE_IN_FIELDS × DA_GAS_PER_FIELD`, the padding could otherwise push the declared DA limit over the new inbound cap and get the tx rejected. `getGasLimits` now clamps the padded DA gas (total and teardown) to the per-tx max, relying on the generated constants. The unpadded estimate never exceeds the cap, so clamping never under-provisions a tx. ## Changes - **constants**: generator (`constants.in.ts`) extended to extract constants from additional Noir files; regenerated `constants.gen.ts` now exports `MAX_TX_BLOB_DATA_SIZE_IN_FIELDS`. No changes to the generated Solidity/C++/PIL constants. - **p2p**: `GasLimitsValidator` caps the effective DA gas limit by the per-tx blob size. - **simulator**: `PublicProcessor` rejects (as invalid) any tx whose resulting effects exceed `MAX_TX_BLOB_DATA_SIZE_IN_FIELDS`. - **aztec.js**: `getGasLimits` clamps padded DA gas to the per-tx max so the estimation buffer can't push a tx over the inbound cap. - **tests**: `gas_validator.test.ts`, `tx_pool_v2.test.ts`, `public_processor.test.ts`, `get_gas_limits.test.ts` updated/extended; `e2e_bot` and `e2e_sequencer_config` set `daGasLimit` to the new per-tx max so their txs remain admissible. Fixes A-1162
PhilWindle
requested review from
IlyasRidhuan,
MirandaWood and
jeanmon
as code owners
June 8, 2026 20:35
…3934) ## Motivation A single constant, `MAX_BLOCKS_PER_CHECKPOINT = 72`, was doing two unrelated jobs: it bounded checkpoint **deserialization** (`Checkpoint.fromBuffer`) and **L1-sync ingest** validation, *and* it served as the build/attest policy limit. The protocol and L1 impose no per-checkpoint block-count cap — the real ceiling is the blob-field budget (`BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB = 24,576` fields, ~2457 minimally-sized blocks). So a checkpoint that L1 accepts with more than 72 small blocks could not be deserialized or validated on ingest: the archiver throws, never advances past that L1 block, and wedges permanently — a liveness failure for any node that hits such a checkpoint. ## Approach Split the conflated constant into two, each with one job: - `MAX_CAPACITY_BLOCKS_PER_CHECKPOINT = 2457` — the exact blob-capacity ceiling. Used for deserialization and when ingesting checkpoints already accepted by L1. - `MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT = 72` — the conservative policy for what we build or attest to, and the **default** block-count limit for `validateCheckpoint`. `validateCheckpoint` / `validateCheckpointStructure` gain a `maxBlocksPerCheckpoint?` option that defaults to the attestable limit (fail-closed). The L1-sync ingest path explicitly opts into the capacity ceiling; build and proposal-validation use the default and set nothing. The p2p block-proposal validator now enforces the attestable ceiling as a hard, always-on bound (a lower configured `maxBlocksPerCheckpoint` still tightens it). `indexWithinCheckpoint` is 0-based, so a proposal for the 73rd block (index 72) is rejected at gossip ingress with a peer penalty — previously the check was skipped entirely when the config was unset. The 2457 value is the exact blob ceiling, pinned by a unit test that re-derives it from the blob constants so any change to those forces a re-derivation. ## API changes - `stdlib` (`@aztec/stdlib/deserialization`): removed `MAX_BLOCKS_PER_CHECKPOINT`; added `MAX_CAPACITY_BLOCKS_PER_CHECKPOINT` and `MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT`. - `validateCheckpoint` and `validateCheckpointStructure` accept an optional `maxBlocksPerCheckpoint` (defaults to the attestable limit). ## Changes - **stdlib**: split the constant; `Checkpoint.fromBuffer` deserializes up to the capacity ceiling; `validateCheckpoint`/`validateCheckpointStructure` parameterize the block-count limit, defaulting to the attestable limit. - **archiver**: the L1-sync ingest path (`data_store_updater`) validates checkpoints against the capacity ceiling. - **p2p**: the proposal validator enforces the attestable ceiling on block proposals unconditionally at gossip ingress. - **stdlib (tests)**: new constant-invariant test pinning 2457 to the blob budget; new >72-block serialization round-trip; `validate` tests for default-rejects / capacity-accepts / explicit-limit. - **p2p (tests)**: 73rd-block rejection holds even when `maxBlocksPerCheckpoint` is unset. Fixes A-1156
Validators were skipping processing of `CheckpointProposal`s coming from their own keys. In an HA setup, this means the checkpoint proposal produced by another node in their same setup was never synced, so the archiver ended up pruning blocks without corresponding checkpoint proposal. ## Issue In an HA deployment, peer nodes share validator signing keys for redundancy. When the active proposer broadcasts a checkpoint proposal, its HA peer receives the proposal over gossip and classifies it as "own" — it owns the signing key — so the all-nodes checkpoint handler returned early without recording the proposed-checkpoint metadata in its archiver. That early return was written on the assumption that an own checkpoint proposal can only originate from this node's own sequencer, which already pushed the proposed checkpoint locally before broadcasting. That assumption breaks for HA peers: the peer never built the checkpoint, so it lacked the proposed-checkpoint metadata needed to build the next slot on top of the still-proposed checkpoint. When that peer had to take over for the following slot, it **pruned the proposed block as an orphan**, rebuilt the same checkpoint, and clashed instead of producing the next checkpoint. ## Approach For own checkpoint proposals, the all-nodes handler now inspects the proposed checkpoint already stored for that slot: - **Matching archive** — idempotent no-op. This is the true local proposer (or an already-hydrated peer); nothing to validate or write. - **Conflicting archive** — log a structured warning and do not overwrite. Duplicate/equivocation handling remains the responsibility of the existing detection paths. - **Missing** — the HA peer that received the proposal only via gossip. Wait for the last block to sync (using the same publish-deadline-bounded wait as the foreign-validation path) and hydrate the proposed-checkpoint metadata from local block data. The block-sync wait (`syncImmediate` + deadline-bounded retry) is extracted into a shared helper so the own/HA hydration path has the same reliability as foreign validation, rather than a single no-wait fetch that could silently miss a not-yet-synced block. Checkpoint attestation behavior is unchanged: a validator still ignores proposals from its own keys and does not produce duplicate HA attestations. Block proposal handling is also unchanged. ## Changes - **validator-client**: Rework the `isOwnProposal` branch of the all-nodes checkpoint proposal handler to hydrate missing proposed-checkpoint metadata for HA peers while keeping the local-proposer fast path idempotent. Widen the handler's archiver dependency to include `getProposedCheckpointData`. Extract a shared `waitForLastBlockData` helper reused by the foreign-validation path; `setProposedCheckpointFromValidation` now takes the already-synced block. - **validator-client (tests)**: Replace the unit test that asserted own proposals never touch the archiver. Add coverage for own-proposal matching/missing/conflicting cases, the block-sync wait (missing-then-appears), and the never-syncs degraded case. - **end-to-end (tests)**: Add `e2e_epochs/epochs_ha_checkpoint_handoff` reproducing the handoff: it routes two consecutive slots to one HA pair, has the builder propose the first slot, and asserts the HA peer records the proposed checkpoint and produces the next slot's checkpoint. The primary assertion (peer records the proposed-checkpoint metadata) is timing-independent. Fixes A-1165
AztecBot
enabled auto-merge
June 9, 2026 01:35
Collaborator
Author
|
🤖 Auto-merge enabled after 4 hours of inactivity. This PR will be merged automatically once all checks pass. |
Collaborator
Author
Flakey Tests🤖 says: This CI run detected 2 tests that failed, but were tolerated due to a .test_patterns.yml entry. |
Collaborator
Author
|
🤖 Auto-merge enabled after 4 hours of inactivity. This PR will be merged automatically once all checks pass. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BEGIN_COMMIT_OVERRIDE
chore: add tmp folder in yarn-project (#23932)
fix(p2p): cap inbound tx DA gas limit by max tx blob size (A-1162) (#23933)
fix: ingest L1 checkpoints up to the blob block capacity (A-1156) (#23934)
fix: sync proposed checkpoint on HA peers (A-1165) (#23940)
END_COMMIT_OVERRIDE