Skip to content

feat(l1): raise the GSE proof-of-possession gas cap to 300k in the v6 payload - #25598

Merged
aminsammara merged 1 commit into
payload-v6from
spl/a-2299-raise-pop-gas-cap-v6-payload
Oct 6, 2026
Merged

aminsammara merged 1 commit into
payload-v6from
spl/a-2299-raise-pop-gas-cap-v6-payload

Conversation

@spalladino

Copy link
Copy Markdown
Contributor

Adds an optional action to the V6 upgrade payload that raises the GSE proof-of-possession gas cap from 250,000 to 300,000.

Why

The GSE checks each new validator's BLS proof of possession during the entry-queue flush, in a call capped at proofOfPossessionGasLimit. If verification does not fit, the deposit is refunded and the validator is not admitted. The cost varies per key, because hashToPoint is a rejection-sampling loop that calls modexp for a square root on some attempts. Osaka's EIP-7883 repriced modexp from 1,338 to 4,016 gas per call, and the 250k cap was not revisited.

Measured under Osaka pricing, which is unchanged on this path in the next fork:

Cap Honest keys whose verification exceeds the cap
250,000 ~1 in 28,600
300,000 ~1 in 2.5M

The cost of the raise: a deposit whose verification fails can burn at most 50k more gas in a flush. The GSE is shared, so the new cap applies to every instance from execution onward.

Changes

  • V6UpgradePayload: new immutable PROOF_OF_POSSESSION_GAS_LIMIT (10th constructor argument). 0 adds no action. A non-zero value must be strictly above the GSE's cap at deployment, so a payload cannot lower it. The action is GSE.setProofOfPossessionGasLimit(PROOF_OF_POSSESSION_GAS_LIMIT), placed last so the numbering of existing actions does not change. It depends on no other action.
  • DeployRollupForUpgradeV6.s.sol: Config.proofOfPossessionGasLimit = 300_000 in the mainnet table (Sepolia inherits it); passed to the payload and read back in verification.
  • V6UpgradeSimulation: asserts the cap after execution (or unchanged when 0).
  • V6UpgradePayload.md and V6_UPGRADE_RUNBOOK.md: the new action, config row, pre-flight checks (current cap and GSE owner) and after-execution check. Also adds the missing earmark/retune rows to the payload's action table.

Tests

  • Payload unit tests: action list shape and exact calldata with and without the action, 0 adds no action, fuzzed constructor bound, 0 never reads the GSE.
  • V6UpgradeAtomicity: executes the full payload through governance and asserts proofOfPossessionGasLimit() == 300_000 (red before the change: 250000 != 300000).
  • Deploy-script test: mainnet and Sepolia tables produce 300k as the last GSE action.
  • New test/fork/MainnetV6PopGasLimit.t.sol (skips without MAINNET_FORK_RPC_URL): the live GSE is owned by governance, and the action sets 300k.
  • Full forge test: 1168 passed. Mainnet dry run of the deploy script passes its simulation check.

Notes for #25496

  • test/script/DeployRollupForUpgradeV6.t.sol fails when run on its own on payload-v6 (RollupConfiguration: VK_TREE_ROOT is unset). It is unrelated to this PR: it needs the genesis vm.setEnv lines that DeployRollupForUpgrade.t.sol has.
  • This adds a constructor argument, so other constructor changes on payload-v6 will conflict.

Fixes A-2299

The GSE verifies each new attester's BLS proof of possession in a sub-call
capped at `proofOfPossessionGasLimit` (250k). The verification cost varies per
key because `hashToPoint` is a rejection-sampling loop, and since the Osaka
modexp repricing (EIP-7883) about 1 in 28.6k honestly generated keys cost more
than 250k. Their deposits are refunded and dropped from the entry queue. At
300k that falls to about 1 in 2.5M, and a failing entry can burn at most 50k
more gas in a flush.

The payload gains an optional last action,
`GSE.setProofOfPossessionGasLimit(PROOF_OF_POSSESSION_GAS_LIMIT)`, where zero
omits it. It has no ordering dependency on the registration actions, so it
goes last and the existing actions keep their positions. The constructor
rejects a non-zero value that does not exceed the GSE's current cap.

The deploy table sets 300_000 on both mainnet and Sepolia, `verify` reads it
back, and the fork simulation asserts the cap after execution. The runbook and
the payload doc list the action and the checks before and after execution.

Tests cover the action list with and without the action, its calldata, the
constructor bound, a full execution through governance, and a mainnet fork
test (skipped unless MAINNET_FORK_RPC_URL is set) that checks governance owns
the live GSE and can apply the encoded call.
@aminsammara
aminsammara merged commit ff9ad7a into payload-v6 Oct 6, 2026
11 checks passed
@aminsammara
aminsammara deleted the spl/a-2299-raise-pop-gas-cap-v6-payload branch October 6, 2026 07:06
spalladino added a commit that referenced this pull request Oct 8, 2026
Brings the v6 upgrade work onto the `v6` release line: the deploy
script, the governance payload, a simulation harness, an operator
runbook, and the pinned rollup verifier and protocol constants the
deploy script uses. 11 new files; the only existing file touched is
`l1-contracts/.gitignore`, which stops ignoring the pinned
`ConstantsGen.sol`.

Supersedes #25496, which targeted `next`. This is that branch's history
plus #25598, cherry-picked onto `v6` and squashed into one commit. `v6`
already carries the AZIP-31 stack (#25570–#25573), and
`l1-contracts/src` is byte-identical between `v6` and the base the
original commits were written against, so nothing needed adapting beyond
the reward-override reconciliation described below.

## What it does

`DeployRollupForUpgradeV6.s.sol` deploys the HonkVerifier, the Rollup
and its sub-contracts, the EscapeHatch and the `V6UpgradePayload`. Every
configuration value is a literal in `_config()`, so reviewing that one
function is sufficient to review the deployment — no env-var defaults
and no `network-defaults.json` fallbacks can change what gets deployed.
Mainnet is the literal; Sepolia is a short override list underneath it,
so the only thing to check for Sepolia is that list. A chain matching
neither reverts rather than silently deploying mainnet values.
`verify(address)` reads every value back off the deployed contracts, so
a typo in the table fails loudly rather than at the next governance
vote.

## Two things worth reviewing closely

**The rollup is constructed owned by governance, not transferred
afterwards.** The owner argument also becomes the Slasher's immutable
`GOVERNANCE`, which can execute any slash payload with no vote and no
delay. Constructing with the deploy key would hand that power to the key
permanently, because `transferOwnership` moves only the `Ownable` half.
That is why the EscapeHatch is built in the script but installed by the
payload, and why no owner-gated setup happens during deployment.

**The payload acts on the outgoing rollup before registering v6.** It
draws the reservation, forwards the earmark, retunes v5's reward split,
and only then calls `Registry.addRollup` and `GSE.addRollup` before
carrying the entry-queue flush incentive to a fresh FlushRewarder. The
distributor resolves the canonical rollup live, so once v6 is canonical
the old rollup can no longer reach the pool and a retune would be
settling a chain that has already stopped.

## No sequencer reward calculator at launch

`sequencerRewardCalculator` is set to `address(0)` on both chains, so
every proposer earns the default sequencer share. Under AZIP-31,
governance can install a calculator later via
`setSequencerRewardCalculator`, which needs no redeploy.

## The verifier and genesis roots are pinned

The rollup verifier is a function of the protocol circuits, and the
circuits this branch builds differ from the v6 release build, so a
verifier built here would reject every proof the v6 nodes produce. The
deploy script therefore deploys a pinned verifier, the same mechanism
`v5-next` uses for v5:

- `l1-contracts/script/deploy/HonkVerifier.sol` is the verifier from the
v6 release build at `42fae4eb3feb5a493f2e8a2baf78b4716e323c40` (`VK_HASH
0x1fd4eb1d14be45e05dc78c448eb4f29d7f63e4de95129c4a5e1058cd9837f8a6`),
with a provenance comment naming that commit.
`DeployRollupForUpgradeV6.s.sol` imports it as `./HonkVerifier.sol`
instead of `@generated/HonkVerifier.sol`.
- `l1-contracts/src/core/libraries/ConstantsGen.sol` is committed from
the same build.
- The three genesis roots in `_config()` come from the same build:

  | Root | Value |
  | --- | --- |
| `vkTreeRoot` |
`0x22fff5de6ce590153df4468f7f0b152188a803d91b3c9c648b91d93d5913f219` |
| `protocolContractsHash` |
`0x0f54271c52865841a77aaa66036eed901aff22fdce9f08e123ec8b205c9854d7` |
| `genesisArchiveRoot` |
`0x29eb2c527f8d45276430363214e6c8d709ef3f657a3670ebac3179373d41e5c4` |

`ConstantsGen.sol` matches what `v6` itself now generates, including the
genesis root from the contract address domain separator change (#25624),
so the existing test fixtures pass against it.

These four move together: re-pin all of them if the release build moves
to a commit that changes the protocol circuits or protocol contracts.
Because the verifier is pinned, the script deploys from a clean clone
with no circuits build; the runbook's Build section has the exact steps,
including running `forge script` rather than a whole-project `forge
build`.

The pin was exercised end to end on an isolated Sepolia network:
deployed from a clean clone of this branch, the deployed verifier's
bytecode matched the pinned file exactly and not this branch's own
build, all contracts verified on Etherscan, and the payload executed
through governance and moved all validators to v6.

## Fee asset price

`initialEthPerFeeAsset` is set to `5_934_240` on mainnet (0.0000059342
ETH per AZTEC), read from the AZTEC/ETH Uniswap v4 pool
`0xce2899b16743cfd5a954d8122d5e07f410305b1aebee39fd73d9f3b9ebf10c2f` at
mainnet block 26148995 (2026-10-08). It only sets the starting point the
fee oracle moves from. No configuration values remain outstanding.

---------

Co-authored-by: Santiago Palladino <santiago@aztec-labs.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants