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 }