Skip to content

Comments

chore!: update base gas values#20569

Closed
LeilaWang wants to merge 10 commits intonextfrom
lw/gas_constants
Closed

chore!: update base gas values#20569
LeilaWang wants to merge 10 commits intonextfrom
lw/gas_constants

Conversation

@LeilaWang
Copy link
Contributor

Use the gas per ms of the AVM to measure the base L2 gas values.

Copy link
Contributor

@iAmMichaelConnor iAmMichaelConnor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for doing this, Leila.

I've added a couple of suggestions for comments.

You'll see I have lots of questions about the methodology, but much of that can be clarified later in a script that enables someone to recreate all of these numbers (as we've just discussed on slack together).

I have some doubts here and there, but having these numbers is better than 0's for alpha. We can iron out the doubts in time for the next release later in the year.

pub global MANA_PER_MS: u32 = 9083;
// The conversion rate from L1 gas to L2 gas is set to 1000.
pub global L1_TO_L2_GAS_CONVERSION_RATE: u32 = 1000;
// Some values are measured per checkpoint or per epoch and are distributed across all txs, assuming 1 TPS, which
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please could you explain why the choice of 1000?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is arbitrary 😅 But this makes the L1 cost take up only 0.01 of the total fixed l2 gas. Should we increase it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reckon we delete it.
I asked Claude for a refresher on fees, and specifically how the user is charged for their share of L1 gas if they only pay in L2 gas. Essentially, the user has to pay a minimum fee_per_gas, and it is this minimum fee_per_gas (gas price) which kind of covers the L1 component of the user's cost, instead of L1 gas metering.


Claude:

The minimum mana fee is deterministic and oracle-derived

The rollup contract computes a single deterministic fee per mana for each block. The sequencer has no discretion —
it's enforced as an exact equality on L1:

// ProposeLib.sol
require(_args.header.gasFees.feePerL2Gas == _args.manaMinFee, ...);

Not >=, but ==. The block's fee is exactly what the formula produces.

Two snapshots feed into it

The formula in FeeLib.getManaMinFeeComponentsAt() uses two oracle values:

  1. L1 gas prices — a lagged snapshot of block.basefee and blob base fee (2-5 slots stale)
  2. ETH/FeeJuice exchange rate (ethPerFeeAsset) — converts the ETH-denominated cost into Fee Juice terms

The computation is:

costInEth = (300K * L1_baseFee + 3 * 131K * L1_blobFee) / manaTarget [sequencer]
+ (1M * L1_baseFee / epochDuration) / manaTarget + param [prover]

totalInEth = costInEth * congestionMultiplier(excessMana)

feePerMana = totalInEth / ethPerFeeAsset [convert to Fee Juice]

The user's position

The user sets maxFeesPerGas when creating their tx. Their tx is only included in a block where:

block.feePerMana <= user.maxFeesPerGas

If the oracle-derived minimum exceeds the user's max, the tx sits in the mempool until either:

  • L1 fees drop, or
  • The exchange rate shifts

Once included, the user pays at least gasUsed * block.feePerMana. They can pay slightly more via priority fees, but
never more than their maxFeesPerGas.

So to answer directly

Yes — the user is forced to pay a minimum mana fee that is mechanically derived from two snapshots: the L1 gas price
snapshot and the ETH/FeeJuice exchange rate snapshot. Both are slightly stale. The user's only lever is to set
maxFeesPerGas high enough to be included, or wait for conditions to change.

// -----------
// The following values were calculated by running tests to benchmark AVM simulation and proving time
// (avm_opcode_spam.test.ts) and determining the average gas per second. Then, we ran the tx validation benchmark
// (tx_validation.test.ts) and multiplied the validation time by the average gas per second to estimate gas costs.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find this file. I found a tx_validator_bench.test.ts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooops yes that's the one!

// TODO: We need a test suite to formalize and validate these measurements.
//
// The fixed L2 gas consists of:
// - Tx validation (common checks for both private-only and public txs): 9.86ms
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this include proof verification, checking fee juice balance, duplicate nullifier checks, contract class log hashing? I'm surprised it's this quick. I'd have expected longer.
Also, shouldn't duplicate nullifier checks be dependent on the number of nullifiers; and contract class log hashing be dependent on the size of the contract class log?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: I can see the contract class log hashing is separate, below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That includes proof verification and checking fee juice balance.
You are right, checking duplicate nullifiers should be priced per nullifier. I left it as 0 as I thought it was negligible (< 0.25ms). But multiplying by the big mana per ms will actually result in thousands of gas. Will add it!

// === L2 gas for side effects from private-only txs ===
// The Gas for batch insert note hashes is included in the fixed L2 gas.
pub global L2_GAS_PER_NOTE_HASH: u32 = 0;
// The Gas for batch insert nullifiers is included in the fixed L2 gas.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// The Gas for batch insert nullifiers is included in the fixed L2 gas.
// The Gas for batch insert note hashes is included in the fixed L2 gas.
// The rollup always inserts a fixed-size subtree of nullifiers for each tx (even if there are no nullifiers to insert).

Comment on lines 1104 to 1105
// The time for validating duplicate nullifiers is negligible.
pub global L2_GAS_PER_NULLIFIER: u32 = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This surprises me (that the cost of validating duplicate nullifiers is negligible), because this is cited as a zcash scaling problem. Maybe the number of nullifiers in the test db wasn't large enough to trigger a slowdown. In a year, there could be 2GB of nullifiers (if all 64 are used every tx, which is of course unrealistic). Still... a DB of the order of GBs really should be used to test duplicate lookup times.

Comment on lines +1106 to +1108
// Gas for writing message on L1: 24011 L1 gas for inserting epoch out hash root to the outbox.
// Assuming 1 l2-to-l1 message per checkpoint.
pub global L2_GAS_PER_L2_TO_L1_MSG: u32 = 750 * L1_TO_L2_GAS_CONVERSION_RATE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does 750 come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

24011 / 32. 32 is the number of checkpoints per epoch.
Should we assume there will be more l2-to-l1 message per checkpoint/epoch?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can observe alpha and change it later :)

// - parity-base: 22084ms * 4
// - parity-root: 22825ms
// - tx-merge: 11892ms * 70
// - first-block-root: 17914ms
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// - first-block-root: 17914ms
// - first-block-root: 17914ms
// - This slightly over-estimates the cost, because subsequent block-root circuits don't have the overhead
// of verifying parity-root circuits (and other checks that only happen in the first-block-root).

// - parity-root: 22825ms
// - tx-merge: 11892ms * 70
// - first-block-root: 17914ms
// - checkpoint-root-single-block: 54345ms
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this similar in cost to the other checkpoint roots? I think it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other checkpoint root (for 2 blocks) is more expensive. But I picked these circuits to measure as if there's only 1 block per checkpoint. I will check if it's similar in cost if there are 2 blocks per checkpoint.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's 687ms less if we have 2 blocks per checkpoint.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely, that difference sounds broadly negligible, so probably fine to leave as it is? Wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Although might be worth writing a comment to explain so, if you're still adding extra comments)

Comment on lines 1088 to 1091
// - Cost on L1 (assuming 1tps):
// - Propose a checkpoint: 325171 L1 gas -> 4517 per tx
// - Prove an epoch: 885319 L1 gas -> 384 per tx
pub global FIXED_L2_GAS: u32 = 44374 * MANA_PER_MS + (4517 + 384) * L1_TO_L2_GAS_CONVERSION_RATE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See big claudey comment. I think we can kill the L1 component (i.e. the + (4517 + 384) * L1_TO_L2_GAS_CONVERSION_RATE; addend

@iAmMichaelConnor
Copy link
Contributor

I don't know how to undo my approval, but we're still discussing this and have doubts

@LeilaWang LeilaWang closed this Feb 23, 2026
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