From 7600046a11288a9e3f249f0c5d052aff096b4c08 Mon Sep 17 00:00:00 2001 From: jangko Date: Tue, 29 Jun 2021 14:41:01 +0700 Subject: [PATCH] EIP-1559: unify PoA and PoW gasLimit and baseFee validation Turn out both EthHash and Clique are using the same gasLimit validation. They also share the same EIP-1559 baseFee validation. --- nimbus/p2p/clique.nim | 18 +--- nimbus/p2p/clique/clique_defs.nim | 21 +--- nimbus/p2p/clique/clique_utils.nim | 102 ------------------- nimbus/p2p/gaslimit.nim | 153 +++++++++++++++++++++++++++++ nimbus/p2p/validate.nim | 37 +------ nimbus/utils/header.nim | 10 -- 6 files changed, 162 insertions(+), 179 deletions(-) create mode 100644 nimbus/p2p/gaslimit.nim diff --git a/nimbus/p2p/clique.nim b/nimbus/p2p/clique.nim index 80dfa98d0..20387496e 100644 --- a/nimbus/p2p/clique.nim +++ b/nimbus/p2p/clique.nim @@ -24,6 +24,7 @@ import ../db/[db_chain, state_db], ../utils, ./clique/[clique_cfg, clique_defs, clique_utils, ec_recover, recent_snaps], + ./gaslimit, chronicles, chronos, eth/[common, keys, rlp], @@ -190,20 +191,9 @@ proc verifyCascadingFields(c: var Clique; header: BlockHeader; &"invalid gasUsed: have {header.gasUsed}, " & &"gasLimit {header.gasLimit}")) - if not c.cfg.dbChain.config.isLondonOrLater(header.blockNumber): - # Verify BaseFee not present before EIP-1559 fork. - if not header.baseFee.isZero: - return err((errCliqueUnsupportedBaseFee, - "invalid baseFee before London fork: have " & - &"{header.baseFee}, want <0>")) - let rc = c.cfg.dbChain.validateGasLimit(header) - if rc.isErr: - return err(rc.error) - else: - let rc = c.cfg.dbChain.config.verify1559Header(parent = parent, - header = header) - if rc.isErr: - return err(rc.error) + let rc = c.cfg.dbChain.validateGasLimitOrBaseFee(header, parent) + if rc.isErr: + return err((errCliqueGasLimitOrBaseFee, rc.error)) # Retrieve the snapshot needed to verify this header and cache it var snap = c.snapshot(header.blockNumber-1, header.parentHash, parents) diff --git a/nimbus/p2p/clique/clique_defs.nim b/nimbus/p2p/clique/clique_defs.nim index b2a576c0f..abe29c062 100644 --- a/nimbus/p2p/clique/clique_defs.nim +++ b/nimbus/p2p/clique/clique_defs.nim @@ -97,21 +97,6 @@ const ## the cutoff threshold and by clique as the snapshot trust limit. 90000 - # params/protocol_params.go(121): BaseFeeChangeDenominator = 8 [..] - EIP1559_BASE_FEE_CHANGE_DENOMINATOR* = ##\ - ## Bounds the amount the base fee can change between blocks. - 8 - - # params/protocol_params.go(122): ElasticityMultiplier = 2 [..] - EIP1559_ELASTICITY_MULTIPLIER* = ##\ - ## Bounds the maximum gas limit an EIP-1559 block may have. - 2 - - # params/protocol_params.go(123): InitialBaseFee = 1000000000 [..] - EIP1559_INITIAL_BASE_FEE* = ##\ - ## Initial base fee for Eip1559 blocks. - 1000000000.u256 - # ------------------------------------------------------------------------------ # Error tokens # ------------------------------------------------------------------------------ @@ -244,16 +229,12 @@ type errSnapshotStore ## .. errSnapshotClone + errCliqueGasLimitOrBaseFee errCliqueExceedsGasLimit - errCliqueUnsupportedBaseFee - errCliqueBaseFeeError errCliqueGasRepriceFork errCliqueSealSigFn errCliqueStopped = "process was interrupted" - errCliqueExpectedBaseFee = "header is missing baseFee" - errCliqueGasLimitTooLow = "gas limit is too low" - errCliqueGasLimitTooHigh = "gas limit is too high" errCliqueUnclesNotAllowed = "uncles not allowed" # not really an error diff --git a/nimbus/p2p/clique/clique_utils.nim b/nimbus/p2p/clique/clique_utils.nim index d895226e3..7eb562c0c 100644 --- a/nimbus/p2p/clique/clique_utils.nim +++ b/nimbus/p2p/clique/clique_utils.nim @@ -45,24 +45,6 @@ type # Private helpers # ------------------------------------------------------------------------------ -proc gasLimitBounds(limit: GasInt): (GasInt, GasInt) = - ## See also utils.header.gasLimitBounds() - let - bndRange = limit div GAS_LIMIT_ADJUSTMENT_FACTOR - upperBound = if GAS_LIMIT_MAXIMUM - bndRange < limit: GAS_LIMIT_MAXIMUM - else: limit + bndRange - lowerBound = max(GAS_LIMIT_MINIMUM, limit - bndRange) - - return (lowerBound, upperBound) - -proc validateGasLimit(header: BlockHeader; limit: GasInt): CliqueResult = - let (lowBound, highBound) = gasLimitBounds(limit) - if header.gasLimit < lowBound: - return err((errCliqueGasLimitTooLow,"")) - if highBound < header.gasLimit: - return err((errCliqueGasLimitTooHigh,"")) - return ok() - func zeroItem[T](t: typedesc[T]): T {.inline.} = discard @@ -139,90 +121,6 @@ proc verifyForkHashes*(c: var ChainConfig; header: BlockHeader): CliqueResult {. return err((errCliqueGasRepriceFork, &"Homestead gas reprice fork: have {c.eip150Hash}, want {hash}")) -proc validateGasLimit*(c: var BaseChainDB; header: BlockHeader): CliqueResult {. - gcsafe, raises: [Defect,RlpError,BlockNotFound].} = - ## See also private function p2p.validate.validateGasLimit() - let parent = c.getBlockHeader(header.parentHash) - header.validateGasLimit(parent.gasLimit) - -# ------------------------------------------------------------------------------ -# Eip 1559 support -# ------------------------------------------------------------------------------ - -# params/config.go(450): func (c *ChainConfig) IsLondon(num [..] -proc isLondonOrLater*(c: var ChainConfig; number: BlockNumber): bool = - c.toFork(number) >= FkLondon - -# consensus/misc/eip1559.go(55): func CalcBaseFee(config [..] -proc calc1599BaseFee*(c: var ChainConfig; parent: BlockHeader): UInt256 = - ## calculates the basefee of the header. - - # If the current block is the first EIP-1559 block, return the - # initial base fee. - if not c.isLondonOrLater(parent.blockNumber): - return EIP1559_INITIAL_BASE_FEE - - let parentGasTarget = parent.gasLimit div EIP1559_ELASTICITY_MULTIPLIER - - # If the parent gasUsed is the same as the target, the baseFee remains - # unchanged. - if parent.gasUsed == parentGasTarget: - return parent.baseFee - - let parentGasDenom = parentGasTarget.u256 * - EIP1559_BASE_FEE_CHANGE_DENOMINATOR.u256 - - # baseFee is an Option[T] - let parentBaseFee = parent.baseFee - - if parentGasTarget < parent.gasUsed: - # If the parent block used more gas than its target, the baseFee should - # increase. - let - gasUsedDelta = (parent.gasUsed - parentGasTarget).u256 - baseFeeDelta = (parentBaseFee * gasUsedDelta) div parentGasDenom - - return parentBaseFee + max(baseFeeDelta, 1.u256) - - else: - # Otherwise if the parent block used less gas than its target, the - # baseFee should decrease. - let - gasUsedDelta = (parentGasTarget - parent.gasUsed).u256 - baseFeeDelta = (parentBaseFee * gasUsedDelta) div parentGasDenom - - return max(parentBaseFee - baseFeeDelta, 0.u256) - - -# consensus/misc/eip1559.go(32): func VerifyEip1559Header(config [..] -proc verify1559Header*(c: var ChainConfig; - parent, header: BlockHeader): CliqueResult {. - gcsafe, raises: [Defect,ValueError].} = - ## Verify that the gas limit remains within allowed bounds - let limit = if c.isLondonOrLater(parent.blockNumber): - parent.gasLimit - else: - parent.gasLimit * EIP1559_ELASTICITY_MULTIPLIER - let rc = header.validateGasLimit(limit) - if rc.isErr: - return err(rc.error) - - let headerBaseFee = header.baseFee - # Verify the header is not malformed - if headerBaseFee.isZero: - return err((errCliqueExpectedBaseFee,"")) - - # Verify the baseFee is correct based on the parent header. - var expectedBaseFee = c.calc1599BaseFee(parent) - if headerBaseFee != expectedBaseFee: - return err((errCliqueBaseFeeError, - &"invalid baseFee: have {expectedBaseFee}, "& - &"want {header.baseFee}, " & - &"parent.baseFee {parent.baseFee}, "& - &"parent.gasUsed {parent.gasUsed}")) - - return ok() - # ------------------------------------------------------------------------------ # Seal hash support # ------------------------------------------------------------------------------ diff --git a/nimbus/p2p/gaslimit.nim b/nimbus/p2p/gaslimit.nim new file mode 100644 index 000000000..e8994f348 --- /dev/null +++ b/nimbus/p2p/gaslimit.nim @@ -0,0 +1,153 @@ +# Nimbus +# Copyright (c) 2018 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or +# http://www.apache.org/licenses/LICENSE-2.0) +# * MIT license ([LICENSE-MIT](LICENSE-MIT) or +# http://opensource.org/licenses/MIT) +# at your option. This file may not be copied, modified, or distributed except +# according to those terms. + +import + std/strformat, + stew/results, + eth/common, + ../db/db_chain, + ../constants, + ../errors, + ../chain_config, + ../forks, + ../config + +const + EIP1559_BASE_FEE_CHANGE_DENOMINATOR* = ##\ + ## Bounds the amount the base fee can change between blocks. + 8 + + EIP1559_ELASTICITY_MULTIPLIER* = ##\ + ## Bounds the maximum gas limit an EIP-1559 block may have. + 2 + + EIP1559_INITIAL_BASE_FEE* = ##\ + ## Initial base fee for Eip1559 blocks. + 1000000000.u256 + +# ------------------------------------------------------------------------------ +# Pre Eip 1559 gas limit validation +# ------------------------------------------------------------------------------ + +proc validateGasLimit(header: BlockHeader; limit: GasInt): Result[void, string] = + let diff = if limit > header.gasLimit: + limit - header.gasLimit + else: + header.gasLimit - limit + + let upperLimit = limit div GAS_LIMIT_ADJUSTMENT_FACTOR + + if diff >= upperLimit: + return err("invalid gas limit: have {header.gasLimit}, want {limit} +-= {upperLimit-1}") + if header.gasLimit < GAS_LIMIT_MINIMUM: + return err("invalid gas limit below 5000") + ok() + +proc validateGasLimit(c: BaseChainDB; header: BlockHeader): Result[void, string] {. + gcsafe, raises: [Defect,RlpError,BlockNotFound].} = + let parent = c.getBlockHeader(header.parentHash) + header.validateGasLimit(parent.gasLimit) + +# ------------------------------------------------------------------------------ +# Eip 1559 support +# ------------------------------------------------------------------------------ + +# params/config.go(450): func (c *ChainConfig) IsLondon(num [..] +proc isLondonOrLater*(c: ChainConfig; number: BlockNumber): bool = + c.toFork(number) >= FkLondon + +# consensus/misc/eip1559.go(55): func CalcBaseFee(config [..] +proc calcEip1599BaseFee(c: ChainConfig; parent: BlockHeader): UInt256 = + ## calculates the basefee of the header. + + # If the current block is the first EIP-1559 block, return the + # initial base fee. + if not c.isLondonOrLater(parent.blockNumber): + return EIP1559_INITIAL_BASE_FEE + + let parentGasTarget = parent.gasLimit div EIP1559_ELASTICITY_MULTIPLIER + + # If the parent gasUsed is the same as the target, the baseFee remains + # unchanged. + if parent.gasUsed == parentGasTarget: + return parent.baseFee + + let parentGasDenom = parentGasTarget.u256 * + EIP1559_BASE_FEE_CHANGE_DENOMINATOR.u256 + + # baseFee is an Option[T] + let parentBaseFee = parent.baseFee + + if parentGasTarget < parent.gasUsed: + # If the parent block used more gas than its target, the baseFee should + # increase. + let + gasUsedDelta = (parent.gasUsed - parentGasTarget).u256 + baseFeeDelta = (parentBaseFee * gasUsedDelta) div parentGasDenom + + return parentBaseFee + max(baseFeeDelta, 1.u256) + + else: + # Otherwise if the parent block used less gas than its target, the + # baseFee should decrease. + let + gasUsedDelta = (parentGasTarget - parent.gasUsed).u256 + baseFeeDelta = (parentBaseFee * gasUsedDelta) div parentGasDenom + + return max(parentBaseFee - baseFeeDelta, 0.u256) + + +# consensus/misc/eip1559.go(32): func VerifyEip1559Header(config [..] +proc verifyEip1559Header(c: ChainConfig; + parent, header: BlockHeader): Result[void, string] {. + gcsafe, raises: [Defect,ValueError].} = + ## Verify that the gas limit remains within allowed bounds + let limit = if c.isLondonOrLater(parent.blockNumber): + parent.gasLimit + else: + parent.gasLimit * EIP1559_ELASTICITY_MULTIPLIER + let rc = header.validateGasLimit(limit) + if rc.isErr: + return rc + + let headerBaseFee = header.baseFee + # Verify the header is not malformed + if headerBaseFee.isZero: + return err("Post EIP-1559 header expected to have base fee") + + # Verify the baseFee is correct based on the parent header. + var expectedBaseFee = c.calcEip1599BaseFee(parent) + if headerBaseFee != expectedBaseFee: + return err(&"invalid baseFee: have {expectedBaseFee}, "& + &"want {header.baseFee}, " & + &"parent.baseFee {parent.baseFee}, "& + &"parent.gasUsed {parent.gasUsed}") + + return ok() + +proc validateGasLimitOrBaseFee*(c: BaseChainDB; + header, parent: BlockHeader): Result[void, string] {. + gcsafe, raises: [Defect,ValueError,CatchableError].} = + + if not c.config.isLondonOrLater(header.blockNumber): + # Verify BaseFee not present before EIP-1559 fork. + if not header.baseFee.isZero: + return err("invalid baseFee before London fork: have " & + &"{header.baseFee}, want <0>") + let rc = c.validateGasLimit(header) + if rc.isErr: + return rc + else: + let rc = c.config.verifyEip1559Header(parent = parent, + header = header) + if rc.isErr: + return rc + + return ok() diff --git a/nimbus/p2p/validate.nim b/nimbus/p2p/validate.nim index 0d14b7718..e1b0e4a6a 100644 --- a/nimbus/p2p/validate.nim +++ b/nimbus/p2p/validate.nim @@ -20,6 +20,7 @@ import ../forks, ./dao, ./validate/epoch_hash_cache, + ./gaslimit, chronicles, eth/[common, rlp, trie/trie_defs], ethash, @@ -141,42 +142,12 @@ proc validateSeal(hashCache: var EpochHashCache; checkPOW(header.blockNumber, miningHash, header.mixDigest, header.nonce, header.difficulty, hashCache) - -proc validateGasLimit(chainDB: BaseChainDB; - header: BlockHeader): Result[void,string] = - let parentHeader = chainDB.getBlockHeader(header.parentHash) - let (lowBound, highBound) = gasLimitBounds(parentHeader) - - if header.gasLimit < lowBound: - return err("The gas limit is too low") - if header.gasLimit > highBound: - return err("The gas limit is too high") - - result = ok() - - -func validateGasLimit(gasLimit, parentGasLimit: GasInt): Result[void,string] = - if gasLimit < GAS_LIMIT_MINIMUM: - return err("Gas limit is below minimum") - if gasLimit > GAS_LIMIT_MAXIMUM: - return err("Gas limit is above maximum") - - let diff = gasLimit - parentGasLimit - if diff > (parentGasLimit div GAS_LIMIT_ADJUSTMENT_FACTOR): - return err("Gas limit difference to parent is too big") - - result = ok() - proc validateHeader(db: BaseChainDB; header, parentHeader: BlockHeader; numTransactions: int; checkSealOK: bool; hashCache: var EpochHashCache): Result[void,string] = if header.extraData.len > 32: return err("BlockHeader.extraData larger than 32 bytes") - result = validateGasLimit(header.gasLimit, parentHeader.gasLimit) - if result.isErr: - return - if header.gasUsed == 0 and 0 < numTransactions: return err("zero gasUsed but tranactions present"); @@ -346,9 +317,9 @@ proc validateHeaderAndKinship*(chainDB: BaseChainDB; header: BlockHeader; return err("BlockHeader.extraData larger than 32 bytes") return ok() - let parentHeader = chainDB.getBlockHeader(header.parentHash) + let parent = chainDB.getBlockHeader(header.parentHash) result = chainDB.validateHeader( - header, parentHeader,numTransactions, checkSealOK, hashCache) + header, parent, numTransactions, checkSealOK, hashCache) if result.isErr: return @@ -360,7 +331,7 @@ proc validateHeaderAndKinship*(chainDB: BaseChainDB; header: BlockHeader; result = chainDB.validateUncles(header, uncles, checkSealOK, hashCache) if result.isOk: - result = chainDB.validateGaslimit(header) + result = chainDB.validateGasLimitOrBaseFee(header, parent) proc validateHeaderAndKinship*(chainDB: BaseChainDB; diff --git a/nimbus/utils/header.nim b/nimbus/utils/header.nim index b4095ae1f..c2773eee2 100644 --- a/nimbus/utils/header.nim +++ b/nimbus/utils/header.nim @@ -19,16 +19,6 @@ proc hasUncles*(header: BlockHeader): bool = header.ommersHash != EMPTY_UNCLE_HA proc `$`*(header: BlockHeader): string = result = &"BlockHeader(timestamp: {header.timestamp} difficulty: {header.difficulty} blockNumber: {header.blockNumber} gasLimit: {header.gasLimit})" -proc gasLimitBounds*(parent: BlockHeader): (GasInt, GasInt) = - ## Compute the boundaries for the block gas limit based on the parent block. - let - boundaryRange = (parent.gasLimit div GAS_LIMIT_ADJUSTMENT_FACTOR) - upperBound = if GAS_LIMIT_MAXIMUM - boundaryRange < parent.gasLimit: - GAS_LIMIT_MAXIMUM else: parent.gasLimit + boundaryRange - lowerBound = max(GAS_LIMIT_MINIMUM, parent.gasLimit - boundaryRange) - - return (lowerBound, upperBound) - proc computeGasLimit*(parent: BlockHeader, gasLimitFloor: GasInt): GasInt = #[ For each block: