Skip to content

Commit

Permalink
Common: Cache Parameter Values + activated EIPs for current Hardfork …
Browse files Browse the repository at this point in the history
…/ SM copy() fix (#2994)

* VM: add total test time tracking to blockchain test runner

* Simplify on paramByHardfork(), paramByEIP() reads, replace with param() usage

* Common: make private members protected for some greater flexibility for users on sub-classing

* Common: new _buildParamsCache() methods + entrypoint calls, new _paramsCache member

* Common: add _buildParamsCache() implementation, replace param() code with direct _paramsCache() access

* Small fix

* VM: fix precompile activation API test

* StateManager: fix Common not properly copied within shallowCopy() method

* Common: add additional param() test for copied/original Common

* Common: fix cache initialization in params cache build method

* Common: add activated EIPs cache

* Apply review suggestions
  • Loading branch information
holgerd77 committed Aug 30, 2023
1 parent fcc910e commit d6d9391
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 52 deletions.
27 changes: 10 additions & 17 deletions packages/block/src/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,14 +404,13 @@ export class BlockHeader {
*/
protected _consensusFormatValidation() {
const { nonce, uncleHash, difficulty, extraData, number } = this
const hardfork = this.common.hardfork()

// Consensus type dependent checks
if (this.common.consensusAlgorithm() === ConsensusAlgorithm.Ethash) {
// PoW/Ethash
if (
number > BigInt(0) &&
this.extraData.length > this.common.paramByHardfork('vm', 'maxExtraDataSize', hardfork)
this.extraData.length > this.common.param('vm', 'maxExtraDataSize')
) {
// Check length of data on all post-genesis blocks
const msg = this._errorMsg('invalid amount of extra data')
Expand Down Expand Up @@ -506,10 +505,8 @@ export class BlockHeader {
parentGasLimit = parentGasLimit * elasticity
}
const gasLimit = this.gasLimit
const hardfork = this.common.hardfork()

const a =
parentGasLimit / this.common.paramByHardfork('gasConfig', 'gasLimitBoundDivisor', hardfork)
const a = parentGasLimit / this.common.param('gasConfig', 'gasLimitBoundDivisor')
const maxGasLimit = parentGasLimit + a
const minGasLimit = parentGasLimit - a

Expand All @@ -523,10 +520,8 @@ export class BlockHeader {
throw new Error(msg)
}

if (gasLimit < this.common.paramByHardfork('gasConfig', 'minGasLimit', hardfork)) {
const msg = this._errorMsg(
`gas limit decreased below minimum gas limit for hardfork=${hardfork}`
)
if (gasLimit < this.common.param('gasConfig', 'minGasLimit')) {
const msg = this._errorMsg(`gas limit decreased below minimum gas limit`)
throw new Error(msg)
}
}
Expand Down Expand Up @@ -704,18 +699,16 @@ export class BlockHeader {
)
throw new Error(msg)
}
const hardfork = this.common.hardfork()
const blockTs = this.timestamp
const { timestamp: parentTs, difficulty: parentDif } = parentBlockHeader
const minimumDifficulty = this.common.paramByHardfork('pow', 'minimumDifficulty', hardfork)
const offset =
parentDif / this.common.paramByHardfork('pow', 'difficultyBoundDivisor', hardfork)
const minimumDifficulty = this.common.param('pow', 'minimumDifficulty')
const offset = parentDif / this.common.param('pow', 'difficultyBoundDivisor')
let num = this.number

// We use a ! here as TS cannot follow this hardfork-dependent logic, but it always gets assigned
let dif!: bigint

if (this.common.hardforkGteHardfork(hardfork, Hardfork.Byzantium) === true) {
if (this.common.gteHardfork(Hardfork.Byzantium) === true) {
// max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99) (EIP100)
const uncleAddend = equalsBytes(parentBlockHeader.uncleHash, KECCAK256_RLP_ARRAY) ? 1 : 2
let a = BigInt(uncleAddend) - (blockTs - parentTs) / BigInt(9)
Expand All @@ -727,13 +720,13 @@ export class BlockHeader {
dif = parentDif + offset * a
}

if (this.common.hardforkGteHardfork(hardfork, Hardfork.Byzantium) === true) {
if (this.common.gteHardfork(Hardfork.Byzantium) === true) {
// Get delay as parameter from common
num = num - this.common.param('pow', 'difficultyBombDelay')
if (num < BigInt(0)) {
num = BigInt(0)
}
} else if (this.common.hardforkGteHardfork(hardfork, Hardfork.Homestead) === true) {
} else if (this.common.gteHardfork(Hardfork.Homestead) === true) {
// 1 - (block_timestamp - parent_timestamp) // 10
let a = BigInt(1) - (blockTs - parentTs) / BigInt(10)
const cutoff = BigInt(-99)
Expand All @@ -744,7 +737,7 @@ export class BlockHeader {
dif = parentDif + offset * a
} else {
// pre-homestead
if (parentTs + this.common.paramByHardfork('pow', 'durationLimit', hardfork) > blockTs) {
if (parentTs + this.common.param('pow', 'durationLimit') > blockTs) {
dif = offset + parentDif
} else {
dif = parentDif - offset
Expand Down
141 changes: 114 additions & 27 deletions packages/common/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,22 @@ import type {
CliqueConfig,
CommonOpts,
CustomCommonOpts,
EIPConfig,
EIPOrHFConfig,
EthashConfig,
GenesisBlockConfig,
GethConfigOpts,
HardforkByOpts,
HardforkConfig,
HardforkTransitionConfig,
} from './types.js'
import type { BigIntLike, PrefixedHexString } from '@ethereumjs/util'

type HardforkSpecKeys = string // keyof typeof HARDFORK_SPECS
type HardforkSpecValues = typeof HARDFORK_SPECS[HardforkSpecKeys]

type ParamsCacheConfig = Omit<EIPOrHFConfig, 'comment' | 'url' | 'status'>

/**
* Common class to access chain and hardfork parameters and to provide
* a unified and shared view on the network and hardfork state.
Expand All @@ -46,12 +52,15 @@ type HardforkSpecValues = typeof HARDFORK_SPECS[HardforkSpecKeys]
export class Common {
readonly DEFAULT_HARDFORK: string | Hardfork

private _chainParams: ChainConfig
private _hardfork: string | Hardfork
private _eips: number[] = []
private _customChains: ChainConfig[]
protected _chainParams: ChainConfig
protected _hardfork: string | Hardfork
protected _eips: number[] = []
protected _customChains: ChainConfig[]

private HARDFORK_CHANGES: [HardforkSpecKeys, HardforkSpecValues][]
protected _paramsCache: ParamsCacheConfig = {}
protected _activatedEIPsCache: number[] = []

protected HARDFORK_CHANGES: [HardforkSpecKeys, HardforkSpecValues][]

public events: EventEmitter

Expand Down Expand Up @@ -197,7 +206,7 @@ export class Common {
return Boolean((initializedChains['names'] as ChainName)[chainId.toString()])
}

private static _getChainParams(
protected static _getChainParams(
chain: string | number | Chain | bigint,
customChains?: ChainConfig[]
): ChainConfig {
Expand Down Expand Up @@ -238,6 +247,10 @@ export class Common {
if (opts.eips) {
this.setEIPs(opts.eips)
}
if (Object.keys(this._paramsCache).length === 0) {
this._buildParamsCache()
this._buildActivatedEIPsCache()
}
}

/**
Expand Down Expand Up @@ -283,6 +296,8 @@ export class Common {
if (hfChanges[0] === hardfork) {
if (this._hardfork !== hardfork) {
this._hardfork = hardfork
this._buildParamsCache()
this._buildActivatedEIPsCache()
this.events.emit('hardforkChanged', hardfork)
}
existing = true
Expand Down Expand Up @@ -435,7 +450,7 @@ export class Common {
* @param hardfork Hardfork name
* @returns Dictionary with hardfork params or null if hardfork not on chain
*/
private _getHardfork(hardfork: string | Hardfork): HardforkTransitionConfig | null {
protected _getHardfork(hardfork: string | Hardfork): HardforkTransitionConfig | null {
const hfs = this.hardforks()
for (const hf of hfs) {
if (hf['name'] === hardfork) return hf
Expand All @@ -458,6 +473,12 @@ export class Common {
`${eip} cannot be activated on hardfork ${this.hardfork()}, minimumHardfork: ${minHF}`
)
}
}
this._eips = eips
this._buildParamsCache()
this._buildActivatedEIPsCache()

for (const eip of eips) {
if ((EIPs as any)[eip].requiredEIPs !== undefined) {
for (const elem of (EIPs as any)[eip].requiredEIPs) {
if (!(eips.includes(elem) || this.isActivatedEIP(elem))) {
Expand All @@ -466,14 +487,85 @@ export class Common {
}
}
}
this._eips = eips
}

/**
* Internal helper for _buildParamsCache()
*/
protected _mergeWithParamsCache(params: HardforkConfig | EIPConfig) {
this._paramsCache['gasConfig'] = {
...this._paramsCache['gasConfig'],
...params['gasConfig'],
}
this._paramsCache['gasPrices'] = {
...this._paramsCache['gasPrices'],
...params['gasPrices'],
}
this._paramsCache['pow'] = {
...this._paramsCache['pow'],
...params['pow'],
}
this._paramsCache['sharding'] = {
...this._paramsCache['sharding'],
...params['sharding'],
}
this._paramsCache['vm'] = {
...this._paramsCache['vm'],
...params['vm'],
}
}

/**
* Build up a cache for all parameter values for the current HF and all activated EIPs
*/
protected _buildParamsCache() {
this._paramsCache = {}
// Iterate through all hardforks up to hardfork set
const hardfork = this.hardfork()
for (const hfChanges of this.HARDFORK_CHANGES) {
// EIP-referencing HF config (e.g. for berlin)
if ('eips' in hfChanges[1]) {
const hfEIPs = hfChanges[1]['eips']
for (const eip of hfEIPs!) {
if (!(eip in EIPs)) {
throw new Error(`${eip} not supported`)
}

this._mergeWithParamsCache(EIPs[eip])
}
// Parameter-inlining HF config (e.g. for istanbul)
} else {
this._mergeWithParamsCache(hfChanges[1])
}
if (hfChanges[0] === hardfork) break
}
// Iterate through all additionally activated EIPs
for (const eip of this._eips) {
if (!(eip in EIPs)) {
throw new Error(`${eip} not supported`)
}

this._mergeWithParamsCache(EIPs[eip])
}
}

protected _buildActivatedEIPsCache() {
this._activatedEIPsCache = []

for (const hfChanges of this.HARDFORK_CHANGES) {
const hf = hfChanges[1]
if (this.gteHardfork(hf['name']) && 'eips' in hf) {
this._activatedEIPsCache = this._activatedEIPsCache.concat(hf['eips'] as number[])
}
}
this._activatedEIPsCache = this._activatedEIPsCache.concat(this._eips)
}

/**
* Returns a parameter for the current chain setup
*
* If the parameter is present in an EIP, the EIP always takes precedence.
* Otherwise the parameter if taken from the latest applied HF with
* Otherwise the parameter is taken from the latest applied HF with
* a change on the respective parameter.
*
* @param topic Parameter topic ('gasConfig', 'gasPrices', 'vm', 'pow')
Expand All @@ -483,12 +575,14 @@ export class Common {
param(topic: string, name: string): bigint {
// TODO: consider the case that different active EIPs
// can change the same parameter
let value
for (const eip of this._eips) {
value = this.paramByEIP(topic, name, eip)
if (value !== undefined) return value
let value = null
if (
(this._paramsCache as any)[topic] !== undefined &&
(this._paramsCache as any)[topic][name] !== undefined
) {
value = (this._paramsCache as any)[topic][name].v
}
return this.paramByHardfork(topic, name, this._hardfork)
return BigInt(value ?? 0)
}

/**
Expand All @@ -501,14 +595,14 @@ export class Common {
paramByHardfork(topic: string, name: string, hardfork: string | Hardfork): bigint {
let value = null
for (const hfChanges of this.HARDFORK_CHANGES) {
// EIP-referencing HF file (e.g. berlin.json)
// EIP-referencing HF config (e.g. for berlin)
if ('eips' in hfChanges[1]) {
const hfEIPs = hfChanges[1]['eips']
for (const eip of hfEIPs!) {
const valueEIP = this.paramByEIP(topic, name, eip)
value = typeof valueEIP === 'bigint' ? valueEIP : value
}
// Parameter-inlining HF file (e.g. istanbul.json)
// Parameter-inlining HF config (e.g. for istanbul)
} else {
if (
(hfChanges[1] as any)[topic] !== undefined &&
Expand Down Expand Up @@ -575,17 +669,9 @@ export class Common {
* @param eip
*/
isActivatedEIP(eip: number): boolean {
if (this.eips().includes(eip)) {
if (this._activatedEIPsCache.includes(eip)) {
return true
}
for (const hfChanges of this.HARDFORK_CHANGES) {
const hf = hfChanges[1]
if (this.gteHardfork(hf['name']) && 'eips' in hf) {
if ((hf['eips'] as number[]).includes(eip)) {
return true
}
}
}
return false
}

Expand Down Expand Up @@ -755,7 +841,7 @@ export class Common {
* @param genesisHash Genesis block hash of the chain
* @returns Fork hash as hex string
*/
private _calcForkHash(hardfork: string | Hardfork, genesisHash: Uint8Array): PrefixedHexString {
protected _calcForkHash(hardfork: string | Hardfork, genesisHash: Uint8Array): PrefixedHexString {
let hfBytes = new Uint8Array(0)
let prevBlockOrTime = 0
for (const hf of this.hardforks()) {
Expand Down Expand Up @@ -905,7 +991,8 @@ export class Common {
}

/**
* Returns the active EIPs
* Returns the additionally activated EIPs
* (by using the `eips` constructor option)
* @returns List of EIPs
*/
eips(): number[] {
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ type ParamDict = {
d: string
}

type EIPOrHFConfig = {
export type EIPOrHFConfig = {
comment: string
url: string
status: string
Expand Down
16 changes: 16 additions & 0 deletions packages/common/test/params.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ describe('[Common]: Parameter access for param(), paramByHardfork()', () => {
)
})

it('Access on copied Common instances', () => {
const c = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Shanghai })
let msg = 'Should correctly access param with param() on original Common'
assert.equal(c.param('pow', 'minerReward'), BigInt(2000000000000000000), msg)

const cCopy = c.copy()
cCopy.setHardfork(Hardfork.Chainstart)

msg = 'Should correctly access param with param() on copied Common with hardfork changed'
assert.equal(cCopy.param('pow', 'minerReward'), BigInt(5000000000000000000), msg)

msg =
'Should correctly access param with param() on original Common after copy and HF change on copied Common'
assert.equal(c.param('pow', 'minerReward'), BigInt(2000000000000000000), msg)
})

it('EIP param access, paramByEIP()', () => {
const c = new Common({ chain: Chain.Mainnet })

Expand Down
4 changes: 2 additions & 2 deletions packages/evm/src/precompiles/0a-kzg-point-evaluation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export async function precompile0a(opts: PrecompileInput): Promise<ExecResult> {
return EvmErrorResult(new EvmError(ERROR.INVALID_INPUT_LENGTH), opts.gasLimit)
}

const version = Number(opts.common.paramByEIP('sharding', 'blobCommitmentVersionKzg', 4844))
const fieldElementsPerBlob = opts.common.paramByEIP('sharding', 'fieldElementsPerBlob', 4844)!
const version = Number(opts.common.param('sharding', 'blobCommitmentVersionKzg'))
const fieldElementsPerBlob = opts.common.param('sharding', 'fieldElementsPerBlob')
const versionedHash = opts.data.subarray(0, 32)
const z = opts.data.subarray(32, 64)
const y = opts.data.subarray(64, 96)
Expand Down
Loading

0 comments on commit d6d9391

Please sign in to comment.