Skip to content

Simplifying Base Fee Calculation #19160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
dantaik opened this issue Mar 25, 2025 · 9 comments
Open

Simplifying Base Fee Calculation #19160

dantaik opened this issue Mar 25, 2025 · 9 comments
Assignees

Comments

@dantaik
Copy link
Contributor

dantaik commented Mar 25, 2025

Background

In Taiko, the L2 base fee is determined by the protocol contract within TaikoAnchor's anchor transactions. The current mathematical approach is a derivative of Vitalik's concept of using an AMM curve for base fee calculations. However, the existing smart contract implementation is intricate and challenging to comprehend.

Proposed Simplified Mathematics

Below is the Ethereum EIP-1559 formula, where Ethereum's gas target presumes a block time of 12 seconds:

$$ B_{\text{current}} = B_{\text{parent}} + B_{\text{parent}} \times \left( \frac{G_{\text{parent}}}{G_{\text{target}}} - 1 \right) \times \frac{1}{D} $$

To accommodate blockchains with variable block times, we can generalize the formula as follows:

$$ B_{\text{current}} = B_{\text{parent}} + B_{\text{parent}} \times \left( \frac{G_{\text{parent}}}{G_{\text{target}}} - \frac{T_{\text{parent}}}{12} \right) \times \frac{1}{D} $$

It is crucial to recognize that this generalized formula incentivizes block builders to create larger blocks rather than splitting them into smaller ones, even when a portion of the base fee is given to the block builder. This can be demonstrated through the following example:

Example: Comparing One Large Block vs Two Smaller Ones

Consider a scenario where the parent block has a base fee of 100 gwei, a gas target of 10,000,000 gas, and a block time of 12 seconds.

Single Block Scenario

  • Gas used: 10,000,000
  • Base fee calculation:

$$ B_{\text{current}} = 100 + 100 \times \left( \frac{10,000,000}{10,000,000} - \frac{0}{12} \right) \times \frac{1}{8} = 100 + 100 \times \left( 1 - 0 \right) \times \frac{1}{8} = 100 + 12.5 = 112.5 \text{ gwei} $$

Total base fee collected: 112.5 gwei × 10,000,000 gas = 1,125,000,000 gwei

Split Block Scenario

Now, let's split this block into two smaller blocks with zero block time between them.

Block 1:

  • Gas used: 5,000,000
  • Base fee calculation:

$$ B_{\text{current1}} = 100 + 100 \times \left( \frac{5,000,000}{10,000,000} - \frac{0}{12} \right) \times \frac{1}{8} = 100 + 100 \times \left( 0.5 - 0 \right) \times \frac{1}{8} = 100 + 6.25 = 106.25 \text{ gwei} $$

Base fee collected in first block: 106.25 gwei × 5,000,000 gas = 531,250,000 gwei

Block 2:

  • Gas used: 5,000,000
  • Parent base fee: 106.25 gwei (from Block 1)
  • Base fee calculation:

$$ B_{\text{current2}} = 106.25 + 106.25 \times \left( \frac{5,000,000}{10,000,000} - \frac{0}{12} \right) \times \frac{1}{8} = 106.25 + 106.25 \times 0.5 \times \frac{1}{8} = 106.25 + 6.64 = 112.89 \text{ gwei} $$

Base fee collected in second block: 112.89 gwei × 5,000,000 gas = 564,450,000 gwei

Comparison and Profitability Analysis

  • Single Block Total Base Fee: 1,125,000,000 gwei
  • Split Blocks Total Base Fee: 531,250,000 + 564,450,000 = 1,095,700,000 gwei

When a portion of the base fee is allocated to the block builder, creating a single larger block results in approximately 29,300,000 gwei more in base fees compared to splitting the gas usage into two smaller blocks. This indicates that block builders are naturally incentivized to create larger blocks to maximize their profits. While this aligns with network efficiency goals, it may discourage preconfirmation practices.

@dantaik dantaik changed the title Simplifying Base Fee Calculation and Transitioning to Node-Based Computation Simplifying Base Fee Calculation Mar 26, 2025
@AnshuJalan
Copy link
Collaborator

AnshuJalan commented Mar 26, 2025

My understanding is that the generalised base-fee formula for a blockchain with a variable block time should be:

$$ B_{current} = B_{parent} + B_{parent} \times (\frac{G_{parent}}{G^\prime_{target} \times T_{parent}} - 1) \times \frac{1}{D} $$

Where, $G^\prime_{target}$ is the per second gas target.

I have some concerns with the provided examples:

  • The gas target tends to always be fixed even when the block time is 0s, so the amount of gas issued by the system depends on how fast the blocks are being built?
  • The formula allows for base fee to keep increasing even when the gas utilisation of the block is well below the target.

@dantaik
Copy link
Contributor Author

dantaik commented Mar 26, 2025

I think you are right @AnshuJalan

@dantaik
Copy link
Contributor Author

dantaik commented Mar 31, 2025

Given that $$T_{parent}$$ can be 0, I think the following is what we should use:

$$ B_{current} = B_{parent} + B_{parent} \times \left( \frac{G_{parent}}{\max(G^\prime_{target} \times T_{parent}, 1)} - 1 \right) \times \frac{1}{D} $$

We can also cap T_{parent} with a constant C to make sure when the protocol is halted for some reason, we do not push the base fee to zero.

$$ B_{current} = B_{parent} + B_{parent} \times \left( \frac{G_{parent}}{\max(G^\prime_{target} \times \min(T_{parent}, C), 1)} - 1 \right) \times \frac{1}{D} $$

If $$B_{current} $$ has somehow become zero then it will remain zero for ever. We can apply a minimal base fee $$M$$ to the math:

$$ B_{current} = \max\left( M,\ B_{parent} + B_{parent} \times \left( \frac{G_{parent}}{\max(G^\prime_{target} \times \min(T_{parent}, C),\ 1)} - 1 \right) \times \frac{1}{D} \right) $$

@adaki2004
Copy link
Contributor

adaki2004 commented Apr 3, 2025

@dantaik I tried to follow along your latest forumla as you mentioned that is implemented in that PR.
I might be wrong but I think when Tparent = 0, the current proposal still leads to massive base fee increases. (because it tries to get a min of the (T_{parent}, C), then it will be 0, but changed to 1 (when maxed) before the division.. Let me explain.

So if we take this:

$$B_{current} = \max(M, B_{parent} + B_{parent} \times (\frac{G_{parent}}{\max(G_{target} \times \min(T_{parent}, C), 1)} - 1) \times \frac{1}{D})$$

Example:

- Gtarget = 10,000,000 gas/second
- D = 8
- Bparent = 100
- Gparent = 5,000,000
- Tparent = 0
- M = 5 (minimum base fee)

Calculation:

$$B_{current} = \max(5, 100 + 100 \times (\frac{5,000,000}{\max(10,000,000 \times \min(0, 1), 1)} - 1) \times \frac{1}{8})$$

$$B_{current} = \max(5, 100 + 100 \times (\frac{5,000,000}{\max(0, 1)} - 1) \times \frac{1}{8})$$

$$B_{current} = \max(5, 100 + 100 \times (\frac{5,000,000}{1} - 1) \times \frac{1}{8})$$

$$B_{current} = \max(5, 100 + 100 \times (5,000,000 - 1) \times 0.125)$$

$$B_{current} = \max(5, 100 + 100 \times 4,999,999 \times 0.125)$$

$$B_{current} = \max(5, 100 + 62,499,987.5)$$

$$B_{current} = \max(5, 62,500,087.5)$$

$$B_{current} = 62,500,087.5$$

If i'm not wrong, this is the only scenario which is an edge case, cause coming blocks other than 0, is properly calculated.

  • How about capping T{parent}_ to be at least T{min}_ ?

Like:
$$B_{current} = B_{parent} + B_{parent} \times (\frac{G_{parent}}{\max(G_{target} \times \max(T_{parent}, T_{min}), 1)} - 1) \times \frac{1}{D}$$

  • Or how about a special case if T{parent}_ = 0 then B{current}_ = B{parent}_ ?

@dantaik
Copy link
Contributor Author

dantaik commented Apr 4, 2025

Thank you @adaki2004 for doing the analysis, you are right about it. And I think in the PR, I actually used keep the base fee unchanged if the block time is zero.

    function calculateClassicBaseFee(
        uint256 _parentBasefee,
        uint256 _parentGasUsed,
        uint256 _adjustmentQuotient,
        uint256 _gasPerSeconds,
        uint256 _blockTime
    )
        internal
        pure
        returns (uint256)
    {
        uint256 gasIssuance = _gasPerSeconds * _blockTime.min(GAS_ISSUANCE_TIME_CAP);
        if (gasIssuance == 0) { // <--- HERE
            return _parentBasefee;
        }
        return _calculateClassicBaseFee(
            _parentBasefee, _parentGasUsed, _adjustmentQuotient, gasIssuance
        ).max(MIN_BASE_FEE);
    }

Updated forumla should be:

$$ B_{current} = \begin{cases} B_{parent}, & \text{if } T_{parent} = 0 \\ \max\left( M,\ B_{parent} + B_{parent} \times \left( \frac{G_{parent}}{\max(G^\prime_{target} \times T_{parent},\ 1)} - 1 \right) \times \frac{1}{D} \right), & \text{otherwise} \end{cases} $$

@dantaik
Copy link
Contributor Author

dantaik commented Apr 5, 2025

Given the following block sequence:

[A] ──10s──▶ [B] ──0s──▶ [C] ──2s──▶ [D]
              ╰─────┬──────╯
                  'parent' gas used for block D

When calculating the base fee for block D, we should consider the combined gas used by both blocks B and C as the “parent gas used” (implemented by this PR), instead of just using block C’s gas usage. Otherwise, the gas used by block B would be incorrectly excluded from the base fee calculation.

We can update the formula by replacing $$G_{parent}$$ with $$G_{accumulatedAncestors}$$:

$$ B_{current} = \begin{cases} B_{parent}, & \text{if } T_{parent} = 0 \\ \max\left( M,\ B_{parent} + B_{parent} \times \left( \frac{G_{accumulatedAncestors}}{\max(G^\prime_{target} \times T_{parent},\ 1)} - 1 \right) \times \frac{1}{D} \right), & \text{otherwise} \end{cases} $$

@dantaik
Copy link
Contributor Author

dantaik commented Apr 5, 2025

The formula above has one remaining issue: the base fee change denominator $$D$$ is fixed, regardless of the actual block time. However, we want the base fee to adjust more if blocks are spaced further apart.

To address this, we introduce $$D^\prime$$ — the base fee change per-second denominator. This allows us to scale the fee change proportionally with block time. We determine $$D^\prime$$ by solving:

$$ (1 + \frac{1}{D^\prime})^{12} ≈ (1 + \frac{1}{D}) $$

On Ethereum, where $$D = 8$$, we get $$D^\prime ≈ 101$$. For simplicity, we’ll use $$D^\prime = 100$$ in the calculations below.

We then modify the base fee update formula as follows:

$$ B_{current} = \begin{cases} B_{parent}, & \text{if } T_{parent} = 0 \\ \max\left( M,\ B_{parent} + B_{parent} \times \left( \frac{G_{accumulatedAncestors}}{\max(G^\prime_{target} \times T_{parent},\ 1)} - 1 \right) \times \left(\left({1+\frac{1}{D^\prime}}\right)^{T_{parent}} -1\right) \right), & \text{otherwise} \end{cases} $$

Let’s evaluate how well this per-second adjustment (using $$D^\prime = 100$$) holds across different block times by simulating the compound growth of the base fee over a 12-second period.

Simulation Table

Block Time (T) Blocks in 12s Per-block Factor (1 + 1/100)^T Compound Over 12s Total % Change
1 12 1.0100 1.0100^12 = 1.1268 +12.68%
2 6 1.0201 1.0201^6 = 1.1262 +12.62%
3 4 1.0303 1.0303^4 = 1.1261 +12.61%
4 3 1.0406 1.0406^3 = 1.1262 +12.62%
6 2 1.0615 1.0615^2 = 1.1268 +12.68%
12 1 1.1268 +12.68%

The result is a consistent ~12.6–12.7% total base fee increase over any 12-second interval, independent of block times.

@dantaik
Copy link
Contributor Author

dantaik commented Apr 5, 2025

#19224 attempts to implement this improvement.

@adaki2004
Copy link
Contributor

adaki2004 commented Apr 6, 2025

Given the following block sequence:

[A] ──10s──▶ [B] ──0s──▶ [C] ──2s──▶ [D]
              ╰─────┬──────╯
                  'parent' gas used for block D

When calculating the base fee for block D, we should consider the combined gas used by both blocks B and C as the “parent gas used” (implemented by this PR), instead of just using block C’s gas usage. Otherwise, the gas used by block B would be incorrectly excluded from the base fee calculation.

We can update the formula by replacing G p a r e n t with G a c c u m u l a t e d A n c e s t o r s :

B c u r r e n t = { B p a r e n t , if T p a r e n t = 0 max ( M , B p a r e n t + B p a r e n t × ( G a c c u m u l a t e d A n c e s t o r s max ( G t a r g e t ′ × T p a r e n t , 1 ) − 1 ) × 1 D ) , otherwise

Hmm, are you sure this is what we want/need ? For example there is a surge in demand for taiko blockspace and a preconfer simply batches a lot of blocks with same T, making B, C, D and E coming at the same time.

This would mean that numerator is very high for block F, so the base fee can see a multiple X in relatively short period of time, resulting in higher tx fees (hence maybe a possibility for an attack vector when we go with permissionless preconfers).

To me, 1 block -> 1 parent is better (and simpler) too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants