Skip to content

Latest commit

 

History

History
179 lines (124 loc) · 9.06 KB

cip-0061.md

File metadata and controls

179 lines (124 loc) · 9.06 KB
cip title author discussions-to status type category created license
61
Restore BaseFee opcode/header field
Gastón Ponti (@gastonponti)
Final
Standards Track
Ring 0
2023-06-21
Apache 2.0

Simple Summary

Move the minimumGasPrice of Celo from our core contract to the header of the block, and activate the EIP-3198 (add the baseFee opcode)

Abstract

This CIP proposes to remove the minimumGasPrice update made by our consensus layer at the end of each block, to be calculated at the beginning of every block proposed, and storing it in the header. Also, the implementation of the EIP-3198 which adds the baseFee opcode to the EVM.

Motivation

This CIP aims to reduce the complexity of our consensus layer, and get us closer to the Ethereum’s implementation of the EIP-1559. This will facilitate future upstream merges and the plans to separate our execution and consensus layers.

Specification

Definitions

  • blockGasLimit is a governable parameter from the BlockchainParametersContract that represents the gas limit of the block
  • blockGasTotal is the gas used for a specific block
  • blockDensity is a number in the interval [0, 1] that represents the usage rate of the block ⇒ blockGasTotal / blockGasLimit
  • adjustmentSpeed is a governable parameter from the GasPriceMinimumContract that represents the speed of gas price minimum adjustment due to congestion
  • targetDensity is a governable parameter from the GasPriceMinimumContract that represents the block congestion level targeted by the gas price minimum calculation
  • gasPriceMinimumFloor is a governable parameter from the GasPriceMinimumContract that represents the minimum gas price threshold

Blockchain node

Add the baseFee field to the header structure.

Implement the EIP-3198 that adds the BASEFEE (0x48) opcode.

Switch the way we are storing the gasPriceMinimum (requires to be enabled during a hardfork)

Currently the GasPriceMinimum is modified and saved calling the GasPriceMinimum contract in the finalize stage of the consensus here. This must be avoided from the Hardfork Block.

Then, in the prepareBlock function (here) will need to

  • For the G_HARDFORK_BLOCK:

    Retrieve the gasPriceMinimum of the actual state (which was updated at the end of the G_HARDFORK_BLOCK - 1) and add that number as the baseFee of the header

  • From the G_HARDFORK_BLOCK + 1:

    The client must call the getUpdatedGasPriceMinimum of the modified gasPriceMinimum contract with the state of the parent block, to retrieve the new baseFee

Contracts

Before the hardfork, we will need to deploy a new version of the GasPriceMinimum contract with the following changes:

  • Rename the gasPriceMinimum variable to deprecated_gasPriceMinimum

    This step is required to be able to define a new function gasPriceMinimum that will return the renamed variable deprecated_gasPriceMinimum or the block.basefee depending on the G_HARDFORK_BLOCK

  • Change the way the gasPriceMinimum getter was defined and override it:

    gasPriceMinimum() view returns (uint256) {
        if (G_HARDFORK_BLOCK > 0 && block.number >= G_HARDFORK_BLOCK) {
          return block.baseFee;
        } else {
          return gasPriceMinimum;
        }
    }
  • Change the implementation of getGasPriceMinimum (line 116) to:

    function getGasPriceMinimum(address tokenAddress) external view returns (uint256) {
        if (
          tokenAddress == address(0) ||
          tokenAddress == registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)
        ) {
          return gasPriceMinimum();
        } else {
          ISortedOracles sortedOracles = ISortedOracles(
            registry.getAddressForOrDie(SORTED_ORACLES_REGISTRY_ID)
          );
          uint256 rateNumerator;
          uint256 rateDenominator;
          (rateNumerator, rateDenominator) = sortedOracles.medianRate(tokenAddress);
          return (gasPriceMinimum().mul(rateNumerator).div(rateDenominator));
        }
    }
  • Update the getUpdatedGasPriceMinimum function to maintain compatibility

    function getUpdatedGasPriceMinimum(uint256 blockGasTotal, uint256 blockGasLimit)
        public
        view
        returns (uint256)
      {
        FixidityLib.Fraction memory blockDensity = FixidityLib.newFixedFraction(
          blockGasTotal,
          blockGasLimit
        );
        bool densityGreaterThanTarget = blockDensity.gt(targetDensity);
        FixidityLib.Fraction memory densityDelta = densityGreaterThanTarget
          ? blockDensity.subtract(targetDensity)
          : targetDensity.subtract(blockDensity);
        FixidityLib.Fraction memory adjustment = densityGreaterThanTarget
          ? FixidityLib.fixed1().add(adjustmentSpeed.multiply(densityDelta))
          : FixidityLib.fixed1().subtract(adjustmentSpeed.multiply(densityDelta));
    
        uint256 newGasPriceMinimum = adjustment
          .multiply(FixidityLib.newFixed(gasPriceMinimum())) // This line
          .add(FixidityLib.fixed1())
          .fromFixed();
    
        return newGasPriceMinimum >= gasPriceMinimumFloor ? newGasPriceMinimum : gasPriceMinimumFloor;
      }
  • The updateGasPriceMinimum is only called by the VM, and must not call this function after the HardFork. There is no need to change it other than having a cleaner contract.

Rationale

The decision of moving the gasPriceMinimum to the header as the baseFee gives us a few benefits:

  • It will bring us closer to upstream, what will increase our compatibility with Ethereum, not only from our node perspective (it will simplify our client), but also for the tools of the ecosystem
  • It will simplify our consensus layer. Avoiding the update of the gasPriceMinimum variable in our core contract, it won’t modify the final state of the block, thus it won’t generate a block receipt, which is something we want to reduce as much as possible
  • The contract’s developers, will be able to use an opcode instead of a contract call to know the gasPriceMinimum of the block

Backwards Compatibility

This needs to be added as part of a fork. To maintain full backwards compatibility, we will also need to re deploy the GasPriceMinimum contract consuming the baseFee opcode.

The event GasPriceMinimumUpdated won't be emitted anymore after the fork. This is due to removing the logic of updating the state of the contract in our consensus. Adding a transaction to just emit that event, goes against the idea of this CIP of simplifying our consensus. As the information of the baseFee is not in the state anymore, checking that a change was performed, has been simplified (Header(N).baseFeePerGas != Header(N-1).baseFeePerGas).

Block Hash Changing

The datastructure that is passed into keccak256 to calculate the block hash is changing, and all applications that are validating blocks are valid or using the block hash to verify block contents will need to be adapted to support the new datastructure (one additional item). If you only take the block header bytes and hash them you should still correctly get a hash, but if you construct a block header from its constituent elements you will need to add in the new one at the end.

Test Cases

Nominal case

Assuming current block base fee is 7 wei. This should push the value 7 (left padded byte32) to the stack.

Bytecode: 0x4800 (BASEFEE, STOP)

Pc Op Cost Stack RStack
0 BASEFEE 2 [] []
1 STOP 0 [7] []

Output: 0x Consumed gas: 2

Implementation

Contract:

Client:

Security Considerations

Just as a consideration, even if it won’t be a big change, this CIP adds a field to the header which will increase its size.

License

This work is licensed under the Apache License, Version 2.0.