From eaee0b0f2fcd6d605e09ff79754ae48fbcad5058 Mon Sep 17 00:00:00 2001 From: Niran Babalola Date: Fri, 4 Jul 2025 00:40:19 -0500 Subject: [PATCH 1/2] design doc: multiple gas schedules --- protocol/multiple-gas-schedules.md | 74 ++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 protocol/multiple-gas-schedules.md diff --git a/protocol/multiple-gas-schedules.md b/protocol/multiple-gas-schedules.md new file mode 100644 index 00000000..1ede22eb --- /dev/null +++ b/protocol/multiple-gas-schedules.md @@ -0,0 +1,74 @@ +# Multiple Gas Schedules + +| | | +| ------------------ | -------------------------------------------------- | +| Author | @niran | +| Created at | 2025-07-03 | + +--- + +# Purpose + +This document proposes offering multiple gas schedules in OP Stack for rollup operators to choose from at runtime via `SystemConfig`. + +# Summary + +We introduce a new configuration value that can be modified via `SystemConfig`: + +| Name | Type | Default | Meaning | +|------------------|---------|----------|---------| +| `gasScheduleId` | `bytes2` | `0x0000` | The ID of the gas schedule to use | + +This value is updated via a new function in `SystemConfig`: + +```solidity +function setGasScheduleId(bytes2 gasScheduleId) external onlyOwner; +``` + +This function will emit a `ConfigUpdate` log-event, with a new `UpdateType`: `UpdateType.GAS_SCHEDULE_ID`. + +Gas schedule ID `0x0000` represents the default gas schedule inherited from Ethereum. Each additional gas schedule is identified by a unique ID determined by given the gas schedule a human-readable label, calculating the keccak256 hash of the label, and taking the first two bytes of the hash. + +Each OP Stack hard fork can update each gas schedule. Typical forks will just inherit any repricings that have occurred on Ethereum. However, when broad repricings like [EIP 7904](https://eips.ethereum.org/EIPS/eip-7904) occur, the corresponding OP Stack hard fork will need to update each gas schedule to match the new gas costs. + +The initial alternative gas schedule is called `target-25Mgps-accounts-500M` and has the ID `0x3e3a`. Its gas prices are optimized for a chain with a gas target equivalent to 25M gas per second and 500M accounts in its state. Such a chain should have opcodes priced such that the base fee increases when state trie insertions take longer than half of a block time to process. In total, three gas schedules would be added to start with: + +| Label | ID | Gas target (Mgas/sec) | Accounts (M) | +|-------------------------------|----------|-----------------------|--------------| +| `target-25Mgps-accounts-500M` | `0x3e3a` | 25 | 500 | +| `target-15Mgps-accounts-500M` | `0x688a` | 15 | 500 | +| `target-5Mgps-accounts-500M` | `0x70fd` | 5 | 500 | + +## Gas Cost Changes in `target-25Mgps-accounts-500M` + +| Opcode | Standard Gas Cost | New Gas Cost | Notes | +|--------|--------------|--------------|-------| +| `CREATE` | 32,000 | 480,000 | 15× increase | +| `CALL` (to new account) | 25,000 | 509,100 | 15× of the previous total (25,000 creation + 9,000 value transfer) while leaving the 9,000 value-transfer cost unchanged | + +## Gas Cost Changes in `target-15Mgps-accounts-500M` + +| Opcode | Standard Gas Cost | New Gas Cost | Notes | +|--------|--------------|--------------|-------| +| `CREATE` | 32,000 | 288,000 | 9× increase | +| `CALL` (to new account) | 25,000 | 297,000 | 9× of the previous total (25,000 creation + 9,000 value transfer) while leaving the 9,000 value-transfer cost unchanged | + +## Gas Cost Changes in `target-5Mgps-accounts-500M` + +| Opcode | Standard Gas Cost | New Gas Cost | Notes | +|--------|--------------|--------------|-------| +| `CREATE` | 32,000 | 96,000 | 3× increase | +| `CALL` (to new account) | 25,000 | 93,000 | 3× of the previous total (25,000 creation + 9,000 value transfer) while leaving the 9,000 value-transfer cost unchanged | + +# Problem Statement + Context + +Gas prices in Ethereum are designed to prevent strain on the network by capping the usage rate for each operation (at `GAS_LIMIT / BLOCK_TIME / OPERATION_COST`). This has worked well for Ethereum mainnet with minor adjustments to the gas schedule over time. However, rollups use significantly shorter block times and aspire to much higher gas targets: Ethereum targets 1.5 Mgas/sec today while World Chain targets 9 Mgas/sec and Base targets 25 Mgas/sec. They've begun to run into scenarios that demonstrate wildly mispriced operations, especially when inserting into the state trie. + +At 25 Mgas/sec, [Base's benchmarks](https://github.com/base/benchmark) show that calculating the state root for a block full of `CALL`s to new accounts takes 5.5 seconds during a GetPayload call that is sent two seconds after geth begins processing the block. This time increases roughly linearly with the number of accounts created: at 12.5 Mgas/sec, GetPayload takes 2.2 seconds, and at 37.5 Mgas/sec, GetPayload takes 8.2 seconds. To ensure that the sequencer can consistently produce blocks within two seconds, we'd like to start increasing the base fee when the state root calculation exceeds 500ms, so we increase the gas cost of account creation by 15×. This also allows for an EIP 1559 multiplier of up to 4 before it becomes possible to create blocks that take longer than two seconds to calculate the state root. + +Each time an OP Stack rollup increases its gas target, the operator should consider updating the gas schedule to restrict state root calculation time. + +# Alternatives Considered + +* **Use a single custom gas schedule for all OP Stack chains**. Most OP Stack chains already have gas targets high enough to be worth repricing account creation, so repricing for everyone would be a reasonable option. But since the state root calculation time depends on gas targets and state sizes that change for chains over time, it's more useful to give operators a bit more flexibility. +* **Directly configure gas costs in `SystemConfig`**. This is the most flexible option, but since many opcodes have customized formulas with multiple parameters, storing them all in `SystemConfig` would get messy. This would also make mistakes during forks more likely since each rollup operator would need to evaluate their customizations to make sure they make sense for each fork. From 3a0d792fac93b83ab050166f314f21e526e292de Mon Sep 17 00:00:00 2001 From: Niran Babalola Date: Wed, 16 Jul 2025 00:25:51 -0500 Subject: [PATCH 2/2] Update the gas schedule proposal to be chain-specific --- protocol/chain-specific-gas.md | 135 +++++++++++++++++++++++++++++ protocol/multiple-gas-schedules.md | 74 ---------------- 2 files changed, 135 insertions(+), 74 deletions(-) create mode 100644 protocol/chain-specific-gas.md delete mode 100644 protocol/multiple-gas-schedules.md diff --git a/protocol/chain-specific-gas.md b/protocol/chain-specific-gas.md new file mode 100644 index 00000000..e57e702c --- /dev/null +++ b/protocol/chain-specific-gas.md @@ -0,0 +1,135 @@ +# Chain-Specific Gas Schedules + +| | | +| ------------------ | -------------------------------------------------- | +| Author | @niran | +| Created at | 2025-07-03 | + +--- + +# Purpose + +This document proposes allowing rollup operators to specify their own gas schedule in OP Stack as part of each hard fork. + +# Summary + +Execution clients currently select the correct jump table for opcodes and their prices based on the active Ethereum execution layer fork: + +```go +// NewEVMInterpreter returns a new instance of the Interpreter. +func NewEVMInterpreter(evm *EVM) *EVMInterpreter { + var table *JumpTable + switch { + case evm.chainRules.IsPrague: + table = &pragueInstructionSet + case evm.chainRules.IsCancun: + table = &cancunInstructionSet + // ... +``` + +> _We're using the `geth` codebase for illustration. `reth` uses a different approach where the current fork is checked for each opcode rather than once for the entire jump table._ + +We establish a new pattern for Superchain rollups to specify their own gas schedule as part of each hard fork: + +```go +// NewEVMInterpreter returns a new instance of the Interpreter. +func NewEVMInterpreter(evm *EVM) *EVMInterpreter { + var table *JumpTable + switch { + case evm.chainRules.IsOptimismJovian && evm.chainRules.IsOptimismBase: + table = &jovianInstructionSetForBase + case evm.chainRules.IsPrague: + table = &pragueInstructionSet + // ... +``` + +Superchain rollups are identified by their chain IDs to set corresponding chain rules for the EVM: + +```go +IsOptimismBase: isMerge && (c.ChainID == new(big.Int).SetUint64(8453) || c.ChainID == new(big.Int).SetUint64(84532)) +``` + +The gas schedule customizations can be implemented by patching the default jump table for the latest fork: + +```go +func newJovianInstructionSetForBase() JumpTable { + instructionSet := newPragueInstructionSet() + + createOp := *instructionSet[CREATE] + createOp.constantGas = 480_000 + instructionSet[CREATE] = &createOp + + create2Op := *instructionSet[CREATE2] + create2Op.constantGas = 480_000 + instructionSet[CREATE2] = &create2Op + + callOp := *instructionSet[CALL] + callOp.dynamicGas = gasCallJovianBase // 509_100 + instructionSet[CALL] = &callOp + + return validate(instructionSet) +} +``` + +Each OP Stack hard fork can potentially update the underlying gas schedule from Ethereum, like the Isthmus fork did for Pectra's gas schedule changes. By default, these changes would override any customizations made for a specific chain. To maintain gas schedule customizations, rollups must re-evaluate their customizations with each hard fork and reapply them when underlying changes occur. + +Eventually, we'd like to remove chain-specific configuration from the codebase of execution clients and move them to the Superchain configs for each rollup. + +```toml +[hardforks] + canyon_time = 1704992401 # Thu 11 Jan 2024 17:00:01 UTC + delta_time = 1708560000 # Thu 22 Feb 2024 00:00:00 UTC + ecotone_time = 1710374401 # Thu 14 Mar 2024 00:00:01 UTC + fjord_time = 1720627201 # Wed 10 Jul 2024 16:00:01 UTC + granite_time = 1726070401 # Wed 11 Sep 2024 16:00:01 UTC + holocene_time = 1736445601 # Thu 9 Jan 2025 18:00:01 UTC + isthmus_time = 1746806401 # Fri 9 May 2025 16:00:01 UTC + jovian_time = 1756702801 # Mon Sep 01 2025 05:00:01 UTC + + [hardforks.jovian] + create_gas = 480_000 + create2_gas = 480_000 + call_new_account_gas = 509_100 +``` + +The main blocker for this config-oriented approach is defining a configuration format that is flexible enough for the use cases we envision in the near term. We can simply pick a handful of parameters that we want to make configurable and commit to supporting those going forward, or we can try to shift the way the ecosystem thinks about gas cost EIPs and execution client implementations to expect all parameters to be configurable. An ecosystem-wide approach would require more work up front with the benefit of minimizing OP Stack's diffs from the standard execution clients. + +This proposal recommends implementing chain-specific gas schedules directly in the execution client codebase for now, with the caveat that any customizations should be equivalent to pure parameter changes. This excludes any custom logic that cannot be expressed in a configuration file. + +# Problem Statement + Context + +Gas prices in Ethereum are designed to prevent strain on the network by capping the usage rate for each operation (at `GAS_LIMIT / BLOCK_TIME / OPERATION_COST`). This has worked well for Ethereum mainnet with minor adjustments to the gas schedule over time. However, rollups use significantly shorter block times and aspire to much higher gas targets: Ethereum targets 1.5 Mgas/sec today while World Chain targets 9 Mgas/sec and Base targets 25 Mgas/sec. They've begun to run into scenarios that demonstrate wildly mispriced operations, especially when inserting into the state trie. + +At 25 Mgas/sec, [Base's benchmarks](https://github.com/base/benchmark) show that calculating the state root for a block full of `CALL`s to new accounts takes 5.5 seconds during a GetPayload call that is sent two seconds after geth begins processing the block. This time increases roughly linearly with the number of accounts created: at 12.5 Mgas/sec, GetPayload takes 2.2 seconds, and at 37.5 Mgas/sec, GetPayload takes 8.2 seconds. To ensure that the sequencer can consistently produce blocks within two seconds, we'd like to start increasing the base fee when the state root calculation exceeds 500ms, so we increase the gas cost of account creation by 15×. This also allows for an EIP 1559 multiplier of up to 4 before it becomes possible to create blocks that take longer than two seconds to calculate the state root. + +Each time an OP Stack rollup increases its gas target, the operator should consider updating the gas schedule to restrict state root calculation time. + +## Example Gas Schedule Changes + +### 25 Mgas/sec with 500M Accounts + +| Opcode | Standard Gas Cost | New Gas Cost | Notes | +|--------|--------------|--------------|-------| +| `CREATE` | 32,000 | 480,000 | 15× increase | +| `CALL` (to new account) | 25,000 | 509,100 | 15× of the previous total (25,000 creation + 9,000 value transfer) while leaving the 9,000 value-transfer cost unchanged | + +### 15 Mgas/sec with 500M Accounts + +| Opcode | Standard Gas Cost | New Gas Cost | Notes | +|--------|--------------|--------------|-------| +| `CREATE` | 32,000 | 288,000 | 9× increase | +| `CALL` (to new account) | 25,000 | 297,000 | 9× of the previous total (25,000 creation + 9,000 value transfer) while leaving the 9,000 value-transfer cost unchanged | + +### 5 Mgas/sec with 500M Accounts + +| Opcode | Standard Gas Cost | New Gas Cost | Notes | +|--------|--------------|--------------|-------| +| `CREATE` | 32,000 | 96,000 | 3× increase | +| `CALL` (to new account) | 25,000 | 93,000 | 3× of the previous total (25,000 creation + 9,000 value transfer) while leaving the 9,000 value-transfer cost unchanged | + + +# Alternatives Considered + +* **Use a single custom gas schedule for all OP Stack chains**. Most OP Stack chains already have gas targets high enough to be worth repricing account creation, so repricing for everyone would be a reasonable option. But since the state root calculation time depends on gas targets and state sizes that change for chains over time, it's more useful to give operators a bit more flexibility. +* **Offer a set of pre-defined gas schedules**. This would allow rollups to choose from a set of common gas schedules that are optimized for different gas targets and state sizes. This would remove the need to configure specific gas cost parameters in the config file. Instead, the rollup operator would just specify the label for the gas schedule they want to use. But this approach would put the burden of maintaining these gas schedules and coordinating changes on the OP Stack team, and the code needed to implement this would be difficult to merge into upstream execution clients. +* **Directly configure gas costs in `SystemConfig`**. This is the most flexible option, but since many opcodes have customized formulas with multiple parameters, storing them all in `SystemConfig` would get messy. This would also make mistakes during forks more likely since each rollup operator would need to evaluate their customizations to make sure they make sense for each fork. diff --git a/protocol/multiple-gas-schedules.md b/protocol/multiple-gas-schedules.md deleted file mode 100644 index 1ede22eb..00000000 --- a/protocol/multiple-gas-schedules.md +++ /dev/null @@ -1,74 +0,0 @@ -# Multiple Gas Schedules - -| | | -| ------------------ | -------------------------------------------------- | -| Author | @niran | -| Created at | 2025-07-03 | - ---- - -# Purpose - -This document proposes offering multiple gas schedules in OP Stack for rollup operators to choose from at runtime via `SystemConfig`. - -# Summary - -We introduce a new configuration value that can be modified via `SystemConfig`: - -| Name | Type | Default | Meaning | -|------------------|---------|----------|---------| -| `gasScheduleId` | `bytes2` | `0x0000` | The ID of the gas schedule to use | - -This value is updated via a new function in `SystemConfig`: - -```solidity -function setGasScheduleId(bytes2 gasScheduleId) external onlyOwner; -``` - -This function will emit a `ConfigUpdate` log-event, with a new `UpdateType`: `UpdateType.GAS_SCHEDULE_ID`. - -Gas schedule ID `0x0000` represents the default gas schedule inherited from Ethereum. Each additional gas schedule is identified by a unique ID determined by given the gas schedule a human-readable label, calculating the keccak256 hash of the label, and taking the first two bytes of the hash. - -Each OP Stack hard fork can update each gas schedule. Typical forks will just inherit any repricings that have occurred on Ethereum. However, when broad repricings like [EIP 7904](https://eips.ethereum.org/EIPS/eip-7904) occur, the corresponding OP Stack hard fork will need to update each gas schedule to match the new gas costs. - -The initial alternative gas schedule is called `target-25Mgps-accounts-500M` and has the ID `0x3e3a`. Its gas prices are optimized for a chain with a gas target equivalent to 25M gas per second and 500M accounts in its state. Such a chain should have opcodes priced such that the base fee increases when state trie insertions take longer than half of a block time to process. In total, three gas schedules would be added to start with: - -| Label | ID | Gas target (Mgas/sec) | Accounts (M) | -|-------------------------------|----------|-----------------------|--------------| -| `target-25Mgps-accounts-500M` | `0x3e3a` | 25 | 500 | -| `target-15Mgps-accounts-500M` | `0x688a` | 15 | 500 | -| `target-5Mgps-accounts-500M` | `0x70fd` | 5 | 500 | - -## Gas Cost Changes in `target-25Mgps-accounts-500M` - -| Opcode | Standard Gas Cost | New Gas Cost | Notes | -|--------|--------------|--------------|-------| -| `CREATE` | 32,000 | 480,000 | 15× increase | -| `CALL` (to new account) | 25,000 | 509,100 | 15× of the previous total (25,000 creation + 9,000 value transfer) while leaving the 9,000 value-transfer cost unchanged | - -## Gas Cost Changes in `target-15Mgps-accounts-500M` - -| Opcode | Standard Gas Cost | New Gas Cost | Notes | -|--------|--------------|--------------|-------| -| `CREATE` | 32,000 | 288,000 | 9× increase | -| `CALL` (to new account) | 25,000 | 297,000 | 9× of the previous total (25,000 creation + 9,000 value transfer) while leaving the 9,000 value-transfer cost unchanged | - -## Gas Cost Changes in `target-5Mgps-accounts-500M` - -| Opcode | Standard Gas Cost | New Gas Cost | Notes | -|--------|--------------|--------------|-------| -| `CREATE` | 32,000 | 96,000 | 3× increase | -| `CALL` (to new account) | 25,000 | 93,000 | 3× of the previous total (25,000 creation + 9,000 value transfer) while leaving the 9,000 value-transfer cost unchanged | - -# Problem Statement + Context - -Gas prices in Ethereum are designed to prevent strain on the network by capping the usage rate for each operation (at `GAS_LIMIT / BLOCK_TIME / OPERATION_COST`). This has worked well for Ethereum mainnet with minor adjustments to the gas schedule over time. However, rollups use significantly shorter block times and aspire to much higher gas targets: Ethereum targets 1.5 Mgas/sec today while World Chain targets 9 Mgas/sec and Base targets 25 Mgas/sec. They've begun to run into scenarios that demonstrate wildly mispriced operations, especially when inserting into the state trie. - -At 25 Mgas/sec, [Base's benchmarks](https://github.com/base/benchmark) show that calculating the state root for a block full of `CALL`s to new accounts takes 5.5 seconds during a GetPayload call that is sent two seconds after geth begins processing the block. This time increases roughly linearly with the number of accounts created: at 12.5 Mgas/sec, GetPayload takes 2.2 seconds, and at 37.5 Mgas/sec, GetPayload takes 8.2 seconds. To ensure that the sequencer can consistently produce blocks within two seconds, we'd like to start increasing the base fee when the state root calculation exceeds 500ms, so we increase the gas cost of account creation by 15×. This also allows for an EIP 1559 multiplier of up to 4 before it becomes possible to create blocks that take longer than two seconds to calculate the state root. - -Each time an OP Stack rollup increases its gas target, the operator should consider updating the gas schedule to restrict state root calculation time. - -# Alternatives Considered - -* **Use a single custom gas schedule for all OP Stack chains**. Most OP Stack chains already have gas targets high enough to be worth repricing account creation, so repricing for everyone would be a reasonable option. But since the state root calculation time depends on gas targets and state sizes that change for chains over time, it's more useful to give operators a bit more flexibility. -* **Directly configure gas costs in `SystemConfig`**. This is the most flexible option, but since many opcodes have customized formulas with multiple parameters, storing them all in `SystemConfig` would get messy. This would also make mistakes during forks more likely since each rollup operator would need to evaluate their customizations to make sure they make sense for each fork.