github.com/dim4egster/coreth@v0.10.2/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/dim4egster/qmallgo/utils/wrappers" 12 "github.com/dim4egster/coreth/core/types" 13 "github.com/dim4egster/coreth/params" 14 "github.com/ethereum/go-ethereum/common" 15 "github.com/ethereum/go-ethereum/common/math" 16 ) 17 18 var ( 19 ApricotPhase3MinBaseFee = big.NewInt(params.ApricotPhase3MinBaseFee) 20 ApricotPhase3MaxBaseFee = big.NewInt(params.ApricotPhase3MaxBaseFee) 21 ApricotPhase4MinBaseFee = big.NewInt(params.ApricotPhase4MinBaseFee) 22 ApricotPhase4MaxBaseFee = big.NewInt(params.ApricotPhase4MaxBaseFee) 23 24 ApricotPhase4BaseFeeChangeDenominator = new(big.Int).SetUint64(params.ApricotPhase4BaseFeeChangeDenominator) 25 ApricotPhase5BaseFeeChangeDenominator = new(big.Int).SetUint64(params.ApricotPhase5BaseFeeChangeDenominator) 26 27 ApricotPhase3BlockGasFee uint64 = 1_000_000 28 ApricotPhase4MinBlockGasCost = new(big.Int).Set(common.Big0) 29 ApricotPhase4MaxBlockGasCost = big.NewInt(1_000_000) 30 ApricotPhase4BlockGasCostStep = big.NewInt(50_000) 31 ApricotPhase4TargetBlockRate uint64 = 2 // in seconds 32 ApricotPhase5BlockGasCostStep = big.NewInt(200_000) 33 rollupWindow uint64 = 10 34 ) 35 36 // CalcBaseFee takes the previous header and the timestamp of its child block 37 // and calculates the expected base fee as well as the encoding of the past 38 // pricing information for the child block. 39 // CalcBaseFee should only be called if [timestamp] >= [config.ApricotPhase3Timestamp] 40 func CalcBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uint64) ([]byte, *big.Int, error) { 41 // If the current block is the first EIP-1559 block, or it is the genesis block 42 // return the initial slice and initial base fee. 43 bigTimestamp := new(big.Int).SetUint64(parent.Time) 44 var ( 45 isApricotPhase3 = config.IsApricotPhase3(bigTimestamp) 46 isApricotPhase4 = config.IsApricotPhase4(bigTimestamp) 47 isApricotPhase5 = config.IsApricotPhase5(bigTimestamp) 48 ) 49 if !isApricotPhase3 || parent.Number.Cmp(common.Big0) == 0 { 50 initialSlice := make([]byte, params.ApricotPhase3ExtraDataSize) 51 initialBaseFee := big.NewInt(params.ApricotPhase3InitialBaseFee) 52 return initialSlice, initialBaseFee, nil 53 } 54 if uint64(len(parent.Extra)) != params.ApricotPhase3ExtraDataSize { 55 return nil, nil, fmt.Errorf("expected length of parent extra data to be %d, but found %d", params.ApricotPhase3ExtraDataSize, len(parent.Extra)) 56 } 57 58 if timestamp < parent.Time { 59 return nil, nil, fmt.Errorf("cannot calculate base fee for timestamp (%d) prior to parent timestamp (%d)", timestamp, parent.Time) 60 } 61 roll := timestamp - parent.Time 62 63 // roll the window over by the difference between the timestamps to generate 64 // the new rollup window. 65 newRollupWindow, err := rollLongWindow(parent.Extra, int(roll)) 66 if err != nil { 67 return nil, nil, err 68 } 69 70 // If AP5, use a less responsive [BaseFeeChangeDenominator] and a higher gas 71 // block limit 72 var ( 73 baseFee = new(big.Int).Set(parent.BaseFee) 74 baseFeeChangeDenominator = ApricotPhase4BaseFeeChangeDenominator 75 parentGasTarget = params.ApricotPhase3TargetGas 76 ) 77 if isApricotPhase5 { 78 baseFeeChangeDenominator = ApricotPhase5BaseFeeChangeDenominator 79 parentGasTarget = params.ApricotPhase5TargetGas 80 } 81 parentGasTargetBig := new(big.Int).SetUint64(parentGasTarget) 82 83 // Add in the gas used by the parent block in the correct place 84 // If the parent consumed gas within the rollup window, add the consumed 85 // gas in. 86 if roll < rollupWindow { 87 var blockGasCost, parentExtraStateGasUsed uint64 88 switch { 89 case isApricotPhase5: 90 // [blockGasCost] has been removed in AP5, so it is left as 0. 91 92 // At the start of a new network, the parent 93 // may not have a populated [ExtDataGasUsed]. 94 if parent.ExtDataGasUsed != nil { 95 parentExtraStateGasUsed = parent.ExtDataGasUsed.Uint64() 96 } 97 case isApricotPhase4: 98 // The [blockGasCost] is paid by the effective tips in the block using 99 // the block's value of [baseFee]. 100 blockGasCost = calcBlockGasCost( 101 ApricotPhase4TargetBlockRate, 102 ApricotPhase4MinBlockGasCost, 103 ApricotPhase4MaxBlockGasCost, 104 ApricotPhase4BlockGasCostStep, 105 parent.BlockGasCost, 106 parent.Time, timestamp, 107 ).Uint64() 108 109 // On the boundary of AP3 and AP4 or at the start of a new network, the parent 110 // may not have a populated [ExtDataGasUsed]. 111 if parent.ExtDataGasUsed != nil { 112 parentExtraStateGasUsed = parent.ExtDataGasUsed.Uint64() 113 } 114 default: 115 blockGasCost = ApricotPhase3BlockGasFee 116 } 117 118 // Compute the new state of the gas rolling window. 119 addedGas, overflow := math.SafeAdd(parent.GasUsed, parentExtraStateGasUsed) 120 if overflow { 121 addedGas = math.MaxUint64 122 } 123 124 // Only add the [blockGasCost] to the gas used if it isn't AP5 125 if !isApricotPhase5 { 126 addedGas, overflow = math.SafeAdd(addedGas, blockGasCost) 127 if overflow { 128 addedGas = math.MaxUint64 129 } 130 } 131 132 slot := rollupWindow - 1 - roll 133 start := slot * wrappers.LongLen 134 updateLongWindow(newRollupWindow, start, addedGas) 135 } 136 137 // Calculate the amount of gas consumed within the rollup window. 138 totalGas := sumLongWindow(newRollupWindow, int(rollupWindow)) 139 140 if totalGas == parentGasTarget { 141 return newRollupWindow, baseFee, nil 142 } 143 144 if totalGas > parentGasTarget { 145 // If the parent block used more gas than its target, the baseFee should increase. 146 gasUsedDelta := new(big.Int).SetUint64(totalGas - parentGasTarget) 147 x := new(big.Int).Mul(parent.BaseFee, gasUsedDelta) 148 y := x.Div(x, parentGasTargetBig) 149 baseFeeDelta := math.BigMax( 150 x.Div(y, baseFeeChangeDenominator), 151 common.Big1, 152 ) 153 154 baseFee.Add(baseFee, baseFeeDelta) 155 } else { 156 // Otherwise if the parent block used less gas than its target, the baseFee should decrease. 157 gasUsedDelta := new(big.Int).SetUint64(parentGasTarget - totalGas) 158 x := new(big.Int).Mul(parent.BaseFee, gasUsedDelta) 159 y := x.Div(x, parentGasTargetBig) 160 baseFeeDelta := math.BigMax( 161 x.Div(y, baseFeeChangeDenominator), 162 common.Big1, 163 ) 164 165 // If [roll] is greater than [rollupWindow], apply the state transition to the base fee to account 166 // for the interval during which no blocks were produced. 167 // We use roll/rollupWindow, so that the transition is applied for every [rollupWindow] seconds 168 // that has elapsed between the parent and this block. 169 if roll > rollupWindow { 170 // Note: roll/rollupWindow must be greater than 1 since we've checked that roll > rollupWindow 171 baseFeeDelta = baseFeeDelta.Mul(baseFeeDelta, new(big.Int).SetUint64(roll/rollupWindow)) 172 } 173 baseFee.Sub(baseFee, baseFeeDelta) 174 } 175 176 // Ensure that the base fee does not increase/decrease outside of the bounds 177 switch { 178 case isApricotPhase5: 179 baseFee = selectBigWithinBounds(ApricotPhase4MinBaseFee, baseFee, nil) 180 case isApricotPhase4: 181 baseFee = selectBigWithinBounds(ApricotPhase4MinBaseFee, baseFee, ApricotPhase4MaxBaseFee) 182 default: 183 baseFee = selectBigWithinBounds(ApricotPhase3MinBaseFee, baseFee, ApricotPhase3MaxBaseFee) 184 } 185 186 return newRollupWindow, baseFee, nil 187 } 188 189 // EstiamteNextBaseFee attempts to estimate the next base fee based on a block with [parent] being built at 190 // [timestamp]. 191 // If [timestamp] is less than the timestamp of [parent], then it uses the same timestamp as parent. 192 // Warning: This function should only be used in estimation and should not be used when calculating the canonical 193 // base fee for a subsequent block. 194 func EstimateNextBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uint64) ([]byte, *big.Int, error) { 195 if timestamp < parent.Time { 196 timestamp = parent.Time 197 } 198 return CalcBaseFee(config, parent, timestamp) 199 } 200 201 // selectBigWithinBounds returns [value] if it is within the bounds: 202 // lowerBound <= value <= upperBound or the bound at either end if [value] 203 // is outside of the defined boundaries. 204 func selectBigWithinBounds(lowerBound, value, upperBound *big.Int) *big.Int { 205 switch { 206 case lowerBound != nil && value.Cmp(lowerBound) < 0: 207 return new(big.Int).Set(lowerBound) 208 case upperBound != nil && value.Cmp(upperBound) > 0: 209 return new(big.Int).Set(upperBound) 210 default: 211 return value 212 } 213 } 214 215 // rollWindow rolls the longs within [consumptionWindow] over by [roll] places. 216 // For example, if there are 4 longs encoded in a 32 byte slice, rollWindow would 217 // have the following effect: 218 // Original: 219 // [1, 2, 3, 4] 220 // Roll = 0 221 // [1, 2, 3, 4] 222 // Roll = 1 223 // [2, 3, 4, 0] 224 // Roll = 2 225 // [3, 4, 0, 0] 226 // Roll = 3 227 // [4, 0, 0, 0] 228 // Roll >= 4 229 // [0, 0, 0, 0] 230 // Assumes that [roll] is greater than or equal to 0 231 func rollWindow(consumptionWindow []byte, size, roll int) ([]byte, error) { 232 if len(consumptionWindow)%size != 0 { 233 return nil, fmt.Errorf("expected consumption window length (%d) to be a multiple of size (%d)", len(consumptionWindow), size) 234 } 235 236 // Note: make allocates a zeroed array, so we are guaranteed 237 // that what we do not copy into, will be set to 0 238 res := make([]byte, len(consumptionWindow)) 239 bound := roll * size 240 if bound > len(consumptionWindow) { 241 return res, nil 242 } 243 copy(res[:], consumptionWindow[roll*size:]) 244 return res, nil 245 } 246 247 func rollLongWindow(consumptionWindow []byte, roll int) ([]byte, error) { 248 // Passes in [wrappers.LongLen] as the size of the individual value to be rolled over 249 // so that it can be used to roll an array of long values. 250 return rollWindow(consumptionWindow, wrappers.LongLen, roll) 251 } 252 253 // sumLongWindow sums [numLongs] encoded in [window]. Assumes that the length of [window] 254 // is sufficient to contain [numLongs] or else this function panics. 255 // If an overflow occurs, while summing the contents, the maximum uint64 value is returned. 256 func sumLongWindow(window []byte, numLongs int) uint64 { 257 var ( 258 sum uint64 = 0 259 overflow bool 260 ) 261 for i := 0; i < numLongs; i++ { 262 // If an overflow occurs while summing the elements of the window, return the maximum 263 // uint64 value immediately. 264 sum, overflow = math.SafeAdd(sum, binary.BigEndian.Uint64(window[wrappers.LongLen*i:])) 265 if overflow { 266 return math.MaxUint64 267 } 268 } 269 return sum 270 } 271 272 // updateLongWindow adds [gasConsumed] in at index within [window]. 273 // Assumes that [index] has already been validated. 274 // If an overflow occurs, the maximum uint64 value is used. 275 func updateLongWindow(window []byte, start uint64, gasConsumed uint64) { 276 prevGasConsumed := binary.BigEndian.Uint64(window[start:]) 277 278 totalGasConsumed, overflow := math.SafeAdd(prevGasConsumed, gasConsumed) 279 if overflow { 280 totalGasConsumed = math.MaxUint64 281 } 282 binary.BigEndian.PutUint64(window[start:], totalGasConsumed) 283 } 284 285 // calcBlockGasCost calculates the required block gas cost. If [parentTime] 286 // > [currentTime], the timeElapsed will be treated as 0. 287 func calcBlockGasCost( 288 targetBlockRate uint64, 289 minBlockGasCost *big.Int, 290 maxBlockGasCost *big.Int, 291 blockGasCostStep *big.Int, 292 parentBlockGasCost *big.Int, 293 parentTime, currentTime uint64, 294 ) *big.Int { 295 // Handle AP3/AP4 boundary by returning the minimum value as the boundary. 296 if parentBlockGasCost == nil { 297 return new(big.Int).Set(minBlockGasCost) 298 } 299 300 // Treat an invalid parent/current time combination as 0 elapsed time. 301 var timeElapsed uint64 302 if parentTime <= currentTime { 303 timeElapsed = currentTime - parentTime 304 } 305 306 var blockGasCost *big.Int 307 if timeElapsed < targetBlockRate { 308 blockGasCostDelta := new(big.Int).Mul(blockGasCostStep, new(big.Int).SetUint64(targetBlockRate-timeElapsed)) 309 blockGasCost = new(big.Int).Add(parentBlockGasCost, blockGasCostDelta) 310 } else { 311 blockGasCostDelta := new(big.Int).Mul(blockGasCostStep, new(big.Int).SetUint64(timeElapsed-targetBlockRate)) 312 blockGasCost = new(big.Int).Sub(parentBlockGasCost, blockGasCostDelta) 313 } 314 315 blockGasCost = selectBigWithinBounds(minBlockGasCost, blockGasCost, maxBlockGasCost) 316 if !blockGasCost.IsUint64() { 317 blockGasCost = new(big.Int).SetUint64(math.MaxUint64) 318 } 319 return blockGasCost 320 } 321 322 // MinRequiredTip is the estimated minimum tip a transaction would have 323 // needed to pay to be included in a given block (assuming it paid a tip 324 // proportional to its gas usage). In reality, there is no minimum tip that 325 // is enforced by the consensus engine and high tip paying transactions can 326 // subsidize the inclusion of low tip paying transactions. The only 327 // correctness check performed is that the sum of all tips is >= the 328 // required block fee. 329 // 330 // This function will return nil for all return values prior to Apricot Phase 4. 331 func MinRequiredTip(config *params.ChainConfig, header *types.Header) (*big.Int, error) { 332 if !config.IsApricotPhase4(new(big.Int).SetUint64(header.Time)) { 333 return nil, nil 334 } 335 if header.BaseFee == nil { 336 return nil, errBaseFeeNil 337 } 338 if header.BlockGasCost == nil { 339 return nil, errBlockGasCostNil 340 } 341 if header.ExtDataGasUsed == nil { 342 return nil, errExtDataGasUsedNil 343 } 344 345 // minTip = requiredBlockFee/blockGasUsage 346 requiredBlockFee := new(big.Int).Mul( 347 header.BlockGasCost, 348 header.BaseFee, 349 ) 350 blockGasUsage := new(big.Int).Add( 351 new(big.Int).SetUint64(header.GasUsed), 352 header.ExtDataGasUsed, 353 ) 354 return new(big.Int).Div(requiredBlockFee, blockGasUsage), nil 355 }