github.com/MetalBlockchain/subnet-evm@v0.4.9/consensus/dummy/dynamic_fees.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package dummy
     5  
     6  import (
     7  	"encoding/binary"
     8  	"fmt"
     9  	"math/big"
    10  
    11  	"github.com/MetalBlockchain/metalgo/utils/wrappers"
    12  	"github.com/MetalBlockchain/subnet-evm/commontype"
    13  	"github.com/MetalBlockchain/subnet-evm/core/types"
    14  	"github.com/MetalBlockchain/subnet-evm/params"
    15  	"github.com/ethereum/go-ethereum/common"
    16  	"github.com/ethereum/go-ethereum/common/math"
    17  )
    18  
    19  // CalcBaseFee takes the previous header and the timestamp of its child block
    20  // and calculates the expected base fee as well as the encoding of the past
    21  // pricing information for the child block.
    22  func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, parent *types.Header, timestamp uint64) ([]byte, *big.Int, error) {
    23  	// If the current block is the first EIP-1559 block, or it is the genesis block
    24  	// return the initial slice and initial base fee.
    25  	isSubnetEVM := config.IsSubnetEVM(new(big.Int).SetUint64(parent.Time))
    26  	extraDataSize := params.ExtraDataSize
    27  
    28  	if !isSubnetEVM || parent.Number.Cmp(common.Big0) == 0 {
    29  		initialSlice := make([]byte, extraDataSize)
    30  		return initialSlice, feeConfig.MinBaseFee, nil
    31  	}
    32  	if len(parent.Extra) != extraDataSize {
    33  		return nil, nil, fmt.Errorf("expected length of parent extra data to be %d, but found %d", extraDataSize, len(parent.Extra))
    34  	}
    35  
    36  	if timestamp < parent.Time {
    37  		return nil, nil, fmt.Errorf("cannot calculate base fee for timestamp (%d) prior to parent timestamp (%d)", timestamp, parent.Time)
    38  	}
    39  	roll := timestamp - parent.Time
    40  
    41  	// roll the window over by the difference between the timestamps to generate
    42  	// the new rollup window.
    43  	newRollupWindow, err := rollLongWindow(parent.Extra, int(roll))
    44  	if err != nil {
    45  		return nil, nil, err
    46  	}
    47  
    48  	// start off with parent's base fee
    49  	baseFee := new(big.Int).Set(parent.BaseFee)
    50  	baseFeeChangeDenominator := feeConfig.BaseFeeChangeDenominator
    51  
    52  	parentGasTargetBig := feeConfig.TargetGas
    53  	parentGasTarget := parentGasTargetBig.Uint64()
    54  
    55  	// Add in the gas used by the parent block in the correct place
    56  	// If the parent consumed gas within the rollup window, add the consumed
    57  	// gas in.
    58  	expectedRollUp := params.RollupWindow
    59  	if roll < expectedRollUp {
    60  		slot := expectedRollUp - 1 - roll
    61  		start := slot * wrappers.LongLen
    62  		updateLongWindow(newRollupWindow, start, parent.GasUsed)
    63  	}
    64  
    65  	// Calculate the amount of gas consumed within the rollup window.
    66  	totalGas := sumLongWindow(newRollupWindow, int(expectedRollUp))
    67  
    68  	if totalGas == parentGasTarget {
    69  		return newRollupWindow, baseFee, nil
    70  	}
    71  
    72  	if totalGas > parentGasTarget {
    73  		// If the parent block used more gas than its target, the baseFee should increase.
    74  		gasUsedDelta := new(big.Int).SetUint64(totalGas - parentGasTarget)
    75  		x := new(big.Int).Mul(parent.BaseFee, gasUsedDelta)
    76  		y := x.Div(x, parentGasTargetBig)
    77  		baseFeeDelta := math.BigMax(
    78  			x.Div(y, baseFeeChangeDenominator),
    79  			common.Big1,
    80  		)
    81  
    82  		baseFee.Add(baseFee, baseFeeDelta)
    83  	} else {
    84  		// Otherwise if the parent block used less gas than its target, the baseFee should decrease.
    85  		gasUsedDelta := new(big.Int).SetUint64(parentGasTarget - totalGas)
    86  		x := new(big.Int).Mul(parent.BaseFee, gasUsedDelta)
    87  		y := x.Div(x, parentGasTargetBig)
    88  		baseFeeDelta := math.BigMax(
    89  			x.Div(y, baseFeeChangeDenominator),
    90  			common.Big1,
    91  		)
    92  
    93  		// If [roll] is greater than [rollupWindow], apply the state transition to the base fee to account
    94  		// for the interval during which no blocks were produced.
    95  		// We use roll/rollupWindow, so that the transition is applied for every [rollupWindow] seconds
    96  		// that has elapsed between the parent and this block.
    97  		if roll > expectedRollUp {
    98  			// Note: roll/params.RollupWindow must be greater than 1 since we've checked that roll > params.RollupWindow
    99  			baseFeeDelta = baseFeeDelta.Mul(baseFeeDelta, new(big.Int).SetUint64(roll/expectedRollUp))
   100  		}
   101  		baseFee.Sub(baseFee, baseFeeDelta)
   102  	}
   103  
   104  	baseFee = selectBigWithinBounds(feeConfig.MinBaseFee, baseFee, nil)
   105  
   106  	return newRollupWindow, baseFee, nil
   107  }
   108  
   109  // EstiamteNextBaseFee attempts to estimate the next base fee based on a block with [parent] being built at
   110  // [timestamp].
   111  // If [timestamp] is less than the timestamp of [parent], then it uses the same timestamp as parent.
   112  // Warning: This function should only be used in estimation and should not be used when calculating the canonical
   113  // base fee for a subsequent block.
   114  func EstimateNextBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, parent *types.Header, timestamp uint64) ([]byte, *big.Int, error) {
   115  	if timestamp < parent.Time {
   116  		timestamp = parent.Time
   117  	}
   118  	return CalcBaseFee(config, feeConfig, parent, timestamp)
   119  }
   120  
   121  // selectBigWithinBounds returns [value] if it is within the bounds:
   122  // lowerBound <= value <= upperBound or the bound at either end if [value]
   123  // is outside of the defined boundaries.
   124  func selectBigWithinBounds(lowerBound, value, upperBound *big.Int) *big.Int {
   125  	switch {
   126  	case lowerBound != nil && value.Cmp(lowerBound) < 0:
   127  		return new(big.Int).Set(lowerBound)
   128  	case upperBound != nil && value.Cmp(upperBound) > 0:
   129  		return new(big.Int).Set(upperBound)
   130  	default:
   131  		return value
   132  	}
   133  }
   134  
   135  // rollWindow rolls the longs within [consumptionWindow] over by [roll] places.
   136  // For example, if there are 4 longs encoded in a 32 byte slice, rollWindow would
   137  // have the following effect:
   138  // Original:
   139  // [1, 2, 3, 4]
   140  // Roll = 0
   141  // [1, 2, 3, 4]
   142  // Roll = 1
   143  // [2, 3, 4, 0]
   144  // Roll = 2
   145  // [3, 4, 0, 0]
   146  // Roll = 3
   147  // [4, 0, 0, 0]
   148  // Roll >= 4
   149  // [0, 0, 0, 0]
   150  // Assumes that [roll] is greater than or equal to 0
   151  func rollWindow(consumptionWindow []byte, size, roll int) ([]byte, error) {
   152  	if len(consumptionWindow)%size != 0 {
   153  		return nil, fmt.Errorf("expected consumption window length (%d) to be a multiple of size (%d)", len(consumptionWindow), size)
   154  	}
   155  
   156  	// Note: make allocates a zeroed array, so we are guaranteed
   157  	// that what we do not copy into, will be set to 0
   158  	res := make([]byte, len(consumptionWindow))
   159  	bound := roll * size
   160  	if bound > len(consumptionWindow) {
   161  		return res, nil
   162  	}
   163  	copy(res[:], consumptionWindow[roll*size:])
   164  	return res, nil
   165  }
   166  
   167  func rollLongWindow(consumptionWindow []byte, roll int) ([]byte, error) {
   168  	// Passes in [wrappers.LongLen] as the size of the individual value to be rolled over
   169  	// so that it can be used to roll an array of long values.
   170  	return rollWindow(consumptionWindow, wrappers.LongLen, roll)
   171  }
   172  
   173  // sumLongWindow sums [numLongs] encoded in [window]. Assumes that the length of [window]
   174  // is sufficient to contain [numLongs] or else this function panics.
   175  // If an overflow occurs, while summing the contents, the maximum uint64 value is returned.
   176  func sumLongWindow(window []byte, numLongs int) uint64 {
   177  	var (
   178  		sum      uint64 = 0
   179  		overflow bool
   180  	)
   181  	for i := 0; i < numLongs; i++ {
   182  		// If an overflow occurs while summing the elements of the window, return the maximum
   183  		// uint64 value immediately.
   184  		sum, overflow = math.SafeAdd(sum, binary.BigEndian.Uint64(window[wrappers.LongLen*i:]))
   185  		if overflow {
   186  			return math.MaxUint64
   187  		}
   188  	}
   189  	return sum
   190  }
   191  
   192  // updateLongWindow adds [gasConsumed] in at index within [window].
   193  // Assumes that [index] has already been validated.
   194  // If an overflow occurs, the maximum uint64 value is used.
   195  func updateLongWindow(window []byte, start uint64, gasConsumed uint64) {
   196  	prevGasConsumed := binary.BigEndian.Uint64(window[start:])
   197  
   198  	totalGasConsumed, overflow := math.SafeAdd(prevGasConsumed, gasConsumed)
   199  	if overflow {
   200  		totalGasConsumed = math.MaxUint64
   201  	}
   202  	binary.BigEndian.PutUint64(window[start:], totalGasConsumed)
   203  }
   204  
   205  // calcBlockGasCost calculates the required block gas cost. If [parentTime]
   206  // > [currentTime], the timeElapsed will be treated as 0.
   207  func calcBlockGasCost(
   208  	targetBlockRate uint64,
   209  	minBlockGasCost *big.Int,
   210  	maxBlockGasCost *big.Int,
   211  	blockGasCostStep *big.Int,
   212  	parentBlockGasCost *big.Int,
   213  	parentTime, currentTime uint64,
   214  ) *big.Int {
   215  	// Handle Subnet EVM boundary by returning the minimum value as the boundary.
   216  	if parentBlockGasCost == nil {
   217  		return new(big.Int).Set(minBlockGasCost)
   218  	}
   219  
   220  	// Treat an invalid parent/current time combination as 0 elapsed time.
   221  	var timeElapsed uint64
   222  	if parentTime <= currentTime {
   223  		timeElapsed = currentTime - parentTime
   224  	}
   225  
   226  	var blockGasCost *big.Int
   227  	if timeElapsed < targetBlockRate {
   228  		blockGasCostDelta := new(big.Int).Mul(blockGasCostStep, new(big.Int).SetUint64(targetBlockRate-timeElapsed))
   229  		blockGasCost = new(big.Int).Add(parentBlockGasCost, blockGasCostDelta)
   230  	} else {
   231  		blockGasCostDelta := new(big.Int).Mul(blockGasCostStep, new(big.Int).SetUint64(timeElapsed-targetBlockRate))
   232  		blockGasCost = new(big.Int).Sub(parentBlockGasCost, blockGasCostDelta)
   233  	}
   234  
   235  	blockGasCost = selectBigWithinBounds(minBlockGasCost, blockGasCost, maxBlockGasCost)
   236  	if !blockGasCost.IsUint64() {
   237  		blockGasCost = new(big.Int).SetUint64(math.MaxUint64)
   238  	}
   239  	return blockGasCost
   240  }
   241  
   242  // MinRequiredTip is the estimated minimum tip a transaction would have
   243  // needed to pay to be included in a given block (assuming it paid a tip
   244  // proportional to its gas usage). In reality, there is no minimum tip that
   245  // is enforced by the consensus engine and high tip paying transactions can
   246  // subsidize the inclusion of low tip paying transactions. The only
   247  // correctness check performed is that the sum of all tips is >= the
   248  // required block fee.
   249  //
   250  // This function will return nil for all return values prior to Subnet EVM.
   251  func MinRequiredTip(config *params.ChainConfig, header *types.Header) (*big.Int, error) {
   252  	if !config.IsSubnetEVM(new(big.Int).SetUint64(header.Time)) {
   253  		return nil, nil
   254  	}
   255  	if header.BaseFee == nil {
   256  		return nil, errBaseFeeNil
   257  	}
   258  	if header.BlockGasCost == nil {
   259  		return nil, errBlockGasCostNil
   260  	}
   261  
   262  	// minTip = requiredBlockFee/blockGasUsage
   263  	requiredBlockFee := new(big.Int).Mul(
   264  		header.BlockGasCost,
   265  		header.BaseFee,
   266  	)
   267  	return new(big.Int).Div(requiredBlockFee, new(big.Int).SetUint64(header.GasUsed)), nil
   268  }