/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see .
*/
import pkg from 'crc-32';
import { EventEmitter, bytesToHex, hexToBytes, uint8ArrayConcat } from 'web3-utils';
import type { Numbers } from 'web3-types';
import { TypeOutput } from './types.js';
import { intToUint8Array, toType, parseGethGenesis } from './utils.js';
import goerli from './chains/goerli.js';
import mainnet from './chains/mainnet.js';
import sepolia from './chains/sepolia.js';
import { EIPs } from './eips/index.js';
import type { ConsensusAlgorithm, ConsensusType } from './enums.js';
import { Chain, CustomChain, Hardfork } from './enums.js';
import { hardforks as HARDFORK_SPECS } from './hardforks/index.js';
import type {
BootstrapNodeConfig,
CasperConfig,
ChainConfig,
ChainName,
ChainsConfig,
CliqueConfig,
CommonOpts,
CustomCommonOpts,
EthashConfig,
GenesisBlockConfig,
GethConfigOpts,
HardforkConfig,
} from './types.js';
const { buf: crc32Uint8Array } = pkg;
type HardforkSpecKeys = keyof typeof HARDFORK_SPECS;
type HardforkSpecValues = typeof HARDFORK_SPECS[HardforkSpecKeys];
/**
* Common class to access chain and hardfork parameters and to provide
* a unified and shared view on the network and hardfork state.
*
* Use the {@link Common.custom} static constructor for creating simple
* custom chain {@link Common} objects (more complete custom chain setups
* can be created via the main constructor and the {@link CommonOpts.customChains} parameter).
*/
export class Common extends EventEmitter {
public readonly DEFAULT_HARDFORK: string | Hardfork;
private _chainParams: ChainConfig;
private _hardfork: string | Hardfork;
private _eips: number[] = [];
private readonly _customChains: ChainConfig[];
private readonly HARDFORK_CHANGES: [HardforkSpecKeys, HardforkSpecValues][];
/**
* Creates a {@link Common} object for a custom chain, based on a standard one.
*
* It uses all the {@link Chain} parameters from the {@link baseChain} option except the ones overridden
* in a provided {@link chainParamsOrName} dictionary. Some usage example:
*
* ```javascript
* Common.custom({chainId: 123})
* ```
*
* There are also selected supported custom chains which can be initialized by using one of the
* {@link CustomChains} for {@link chainParamsOrName}, e.g.:
*
* ```javascript
* Common.custom(CustomChains.MaticMumbai)
* ```
*
* Note that these supported custom chains only provide some base parameters (usually the chain and
* network ID and a name) and can only be used for selected use cases (e.g. sending a tx with
* the `web3-utils/tx` library to a Layer-2 chain).
*
* @param chainParamsOrName Custom parameter dict (`name` will default to `custom-chain`) or string with name of a supported custom chain
* @param opts Custom chain options to set the {@link CustomCommonOpts.baseChain}, selected {@link CustomCommonOpts.hardfork} and others
*/
public static custom(
chainParamsOrName: Partial | CustomChain,
opts: CustomCommonOpts = {},
): Common {
const baseChain = opts.baseChain ?? 'mainnet';
const standardChainParams = { ...Common._getChainParams(baseChain) };
standardChainParams.name = 'custom-chain';
if (typeof chainParamsOrName !== 'string') {
return new Common({
chain: {
...standardChainParams,
...chainParamsOrName,
},
...opts,
});
}
if (chainParamsOrName === CustomChain.PolygonMainnet) {
return Common.custom(
{
name: CustomChain.PolygonMainnet,
chainId: 137,
networkId: 137,
},
opts,
);
}
if (chainParamsOrName === CustomChain.PolygonMumbai) {
return Common.custom(
{
name: CustomChain.PolygonMumbai,
chainId: 80001,
networkId: 80001,
},
opts,
);
}
if (chainParamsOrName === CustomChain.ArbitrumRinkebyTestnet) {
return Common.custom(
{
name: CustomChain.ArbitrumRinkebyTestnet,
chainId: 421611,
networkId: 421611,
},
opts,
);
}
if (chainParamsOrName === CustomChain.ArbitrumOne) {
return Common.custom(
{
name: CustomChain.ArbitrumOne,
chainId: 42161,
networkId: 42161,
},
opts,
);
}
if (chainParamsOrName === CustomChain.xDaiChain) {
return Common.custom(
{
name: CustomChain.xDaiChain,
chainId: 100,
networkId: 100,
},
opts,
);
}
if (chainParamsOrName === CustomChain.OptimisticKovan) {
return Common.custom(
{
name: CustomChain.OptimisticKovan,
chainId: 69,
networkId: 69,
},
// Optimism has not implemented the London hardfork yet (targeting Q1.22)
{ hardfork: Hardfork.Berlin, ...opts },
);
}
if (chainParamsOrName === CustomChain.OptimisticEthereum) {
return Common.custom(
{
name: CustomChain.OptimisticEthereum,
chainId: 10,
networkId: 10,
},
// Optimism has not implemented the London hardfork yet (targeting Q1.22)
{ hardfork: Hardfork.Berlin, ...opts },
);
}
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Custom chain ${chainParamsOrName} not supported`);
}
/**
* Static method to load and set common from a geth genesis json
* @param genesisJson json of geth configuration
* @param { chain, eips, genesisHash, hardfork, mergeForkIdPostMerge } to further configure the common instance
* @returns Common
*/
public static fromGethGenesis(
genesisJson: any,
{ chain, eips, genesisHash, hardfork, mergeForkIdPostMerge }: GethConfigOpts,
): Common {
const genesisParams = parseGethGenesis(genesisJson, chain, mergeForkIdPostMerge);
const common = new Common({
chain: genesisParams.name ?? 'custom',
customChains: [genesisParams],
eips,
hardfork: hardfork ?? genesisParams.hardfork,
});
if (genesisHash !== undefined) {
common.setForkHashes(genesisHash);
}
return common;
}
/**
* Static method to determine if a {@link chainId} is supported as a standard chain
* @param chainId bigint id (`1`) of a standard chain
* @returns boolean
*/
public static isSupportedChainId(chainId: bigint): boolean {
const initializedChains = this._getInitializedChains();
return Boolean((initializedChains.names as ChainName)[chainId.toString()]);
}
private static _getChainParams(
_chain: string | number | Chain | bigint,
customChains?: ChainConfig[],
): ChainConfig {
let chain = _chain;
const initializedChains = this._getInitializedChains(customChains);
if (typeof chain === 'number' || typeof chain === 'bigint') {
chain = chain.toString();
if ((initializedChains.names as ChainName)[chain]) {
const name: string = (initializedChains.names as ChainName)[chain];
return initializedChains[name] as ChainConfig;
}
throw new Error(`Chain with ID ${chain} not supported`);
}
if (initializedChains[chain] !== undefined) {
return initializedChains[chain] as ChainConfig;
}
throw new Error(`Chain with name ${chain} not supported`);
}
public constructor(opts: CommonOpts) {
super();
this._customChains = opts.customChains ?? [];
this._chainParams = this.setChain(opts.chain);
this.DEFAULT_HARDFORK = this._chainParams.defaultHardfork ?? Hardfork.Merge;
// Assign hardfork changes in the sequence of the applied hardforks
this.HARDFORK_CHANGES = this.hardforks().map(hf => [
hf.name as HardforkSpecKeys,
HARDFORK_SPECS[hf.name as HardforkSpecKeys],
]);
this._hardfork = this.DEFAULT_HARDFORK;
if (opts.hardfork !== undefined) {
this.setHardfork(opts.hardfork);
}
if (opts.eips) {
this.setEIPs(opts.eips);
}
}
/**
* Sets the chain
* @param chain String ('mainnet') or Number (1) chain representation.
* Or, a Dictionary of chain parameters for a private network.
* @returns The dictionary with parameters set as chain
*/
public setChain(chain: string | number | Chain | bigint | object): ChainConfig {
if (typeof chain === 'number' || typeof chain === 'bigint' || typeof chain === 'string') {
this._chainParams = Common._getChainParams(chain, this._customChains);
} else if (typeof chain === 'object') {
if (this._customChains.length > 0) {
throw new Error(
'Chain must be a string, number, or bigint when initialized with customChains passed in',
);
}
const required = ['networkId', 'genesis', 'hardforks', 'bootstrapNodes'];
for (const param of required) {
if (!(param in chain)) {
throw new Error(`Missing required chain parameter: ${param}`);
}
}
this._chainParams = chain as ChainConfig;
} else {
throw new Error('Wrong input format');
}
for (const hf of this.hardforks()) {
if (hf.block === undefined) {
throw new Error(`Hardfork cannot have undefined block number`);
}
}
return this._chainParams;
}
/**
* Sets the hardfork to get params for
* @param hardfork String identifier (e.g. 'byzantium') or {@link Hardfork} enum
*/
public setHardfork(hardfork: string | Hardfork): void {
let existing = false;
for (const hfChanges of this.HARDFORK_CHANGES) {
if (hfChanges[0] === hardfork) {
if (this._hardfork !== hardfork) {
this._hardfork = hardfork;
this.emit('hardforkChanged', hardfork);
}
existing = true;
}
}
if (!existing) {
throw new Error(`Hardfork with name ${hardfork} not supported`);
}
}
/**
* Returns the hardfork based on the block number or an optional
* total difficulty (Merge HF) provided.
*
* An optional TD takes precedence in case the corresponding HF block
* is set to `null` or otherwise needs to match (if not an error
* will be thrown).
*
* @param blockNumber
* @param td : total difficulty of the parent block (for block hf) OR of the chain latest (for chain hf)
* @param timestamp: timestamp in seconds at which block was/is to be minted
* @returns The name of the HF
*/
public getHardforkByBlockNumber(
_blockNumber: Numbers,
_td?: Numbers,
_timestamp?: Numbers,
): string {
const blockNumber = toType(_blockNumber, TypeOutput.BigInt);
const td = toType(_td, TypeOutput.BigInt);
const timestamp = toType(_timestamp, TypeOutput.Number);
// Filter out hardforks with no block number, no ttd or no timestamp (i.e. unapplied hardforks)
const hfs = this.hardforks().filter(
hf =>
// eslint-disable-next-line no-null/no-null
hf.block !== null ||
// eslint-disable-next-line no-null/no-null
(hf.ttd !== null && hf.ttd !== undefined) ||
hf.timestamp !== undefined,
);
// eslint-disable-next-line no-null/no-null
const mergeIndex = hfs.findIndex(hf => hf.ttd !== null && hf.ttd !== undefined);
const doubleTTDHF = hfs
.slice(mergeIndex + 1)
// eslint-disable-next-line no-null/no-null
.findIndex(hf => hf.ttd !== null && hf.ttd !== undefined);
if (doubleTTDHF >= 0) {
throw Error(`More than one merge hardforks found with ttd specified`);
}
// Find the first hardfork that has a block number greater than `blockNumber`
// (skips the merge hardfork since it cannot have a block number specified).
// If timestamp is not provided, it also skips timestamps hardforks to continue
// discovering/checking number hardforks.
let hfIndex = hfs.findIndex(
hf =>
// eslint-disable-next-line no-null/no-null
(hf.block !== null && hf.block > blockNumber) ||
(timestamp !== undefined && Number(hf.timestamp) > timestamp),
);
if (hfIndex === -1) {
// all hardforks apply, set hfIndex to the last one as that's the candidate
hfIndex = hfs.length;
} else if (hfIndex === 0) {
// cannot have a case where a block number is before all applied hardforks
// since the chain has to start with a hardfork
throw Error('Must have at least one hardfork at block 0');
}
// If timestamp is not provided, we need to rollback to the last hf with block or ttd
if (timestamp === undefined) {
const stepBack = hfs
.slice(0, hfIndex)
.reverse()
// eslint-disable-next-line no-null/no-null
.findIndex(hf => hf.block !== null || hf.ttd !== undefined);
hfIndex -= stepBack;
}
// Move hfIndex one back to arrive at candidate hardfork
hfIndex -= 1;
// If the timestamp was not provided, we could have skipped timestamp hardforks to look for number
// hardforks. so it will now be needed to rollback
// eslint-disable-next-line no-null/no-null
if (hfs[hfIndex].block === null && hfs[hfIndex].timestamp === undefined) {
// We're on the merge hardfork. Let's check the TTD
// eslint-disable-next-line no-null/no-null
if (td === undefined || td === null || BigInt(hfs[hfIndex].ttd!) > td) {
// Merge ttd greater than current td so we're on hardfork before merge
hfIndex -= 1;
}
// eslint-disable-next-line no-null/no-null
} else if (mergeIndex >= 0 && td !== undefined && td !== null) {
if (hfIndex >= mergeIndex && BigInt(hfs[mergeIndex].ttd!) > td) {
throw Error(
'Maximum HF determined by total difficulty is lower than the block number HF',
);
} else if (hfIndex < mergeIndex && BigInt(hfs[mergeIndex].ttd!) <= td) {
throw Error(
'HF determined by block number is lower than the minimum total difficulty HF',
);
}
}
const hfStartIndex = hfIndex;
// Move the hfIndex to the end of the hardforks that might be scheduled on the same block/timestamp
// This won't anyway be the case with Merge hfs
for (; hfIndex < hfs.length - 1; hfIndex += 1) {
// break out if hfIndex + 1 is not scheduled at hfIndex
if (
hfs[hfIndex].block !== hfs[hfIndex + 1].block ||
hfs[hfIndex].timestamp !== hfs[hfIndex + 1].timestamp
) {
break;
}
}
if (timestamp) {
const minTimeStamp = hfs
.slice(0, hfStartIndex)
.reduce(
(acc: number, hf: HardforkConfig) => Math.max(Number(hf.timestamp ?? '0'), acc),
0,
);
if (minTimeStamp > timestamp) {
throw Error(
`Maximum HF determined by timestamp is lower than the block number/ttd HF`,
);
}
const maxTimeStamp = hfs
.slice(hfIndex + 1)
.reduce(
(acc: number, hf: HardforkConfig) =>
Math.min(Number(hf.timestamp ?? timestamp), acc),
timestamp,
);
if (maxTimeStamp < timestamp) {
throw Error(`Maximum HF determined by block number/ttd is lower than timestamp HF`);
}
}
const hardfork = hfs[hfIndex];
return hardfork.name;
}
/**
* Sets a new hardfork based on the block number or an optional
* total difficulty (Merge HF) provided.
*
* An optional TD takes precedence in case the corresponding HF block
* is set to `null` or otherwise needs to match (if not an error
* will be thrown).
*
* @param blockNumber
* @param td
* @param timestamp
* @returns The name of the HF set
*/
public setHardforkByBlockNumber(
blockNumber: Numbers,
td?: Numbers,
timestamp?: Numbers,
): string {
const hardfork = this.getHardforkByBlockNumber(blockNumber, td, timestamp);
this.setHardfork(hardfork);
return hardfork;
}
/**
* Internal helper function, returns the params for the given hardfork for the chain set
* @param hardfork Hardfork name
* @returns Dictionary with hardfork params or null if hardfork not on chain
*/
// eslint-disable-next-line @typescript-eslint/ban-types
public _getHardfork(hardfork: string | Hardfork): HardforkConfig | null {
const hfs = this.hardforks();
for (const hf of hfs) {
if (hf.name === hardfork) return hf;
}
// eslint-disable-next-line no-null/no-null
return null;
}
/**
* Sets the active EIPs
* @param eips
*/
public setEIPs(eips: number[] = []) {
for (const eip of eips) {
if (!(eip in EIPs)) {
throw new Error(`${eip} not supported`);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
const minHF = this.gteHardfork(EIPs[eip].minimumHardfork);
if (!minHF) {
throw new Error(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`${eip} cannot be activated on hardfork ${this.hardfork()}, minimumHardfork: ${minHF}`,
);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (EIPs[eip].requiredEIPs !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
for (const elem of EIPs[eip].requiredEIPs) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
if (!(eips.includes(elem) || this.isActivatedEIP(elem))) {
throw new Error(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`${eip} requires EIP ${elem}, but is not included in the EIP list`,
);
}
}
}
}
this._eips = 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
* a change on the respective parameter.
*
* @param topic Parameter topic ('gasConfig', 'gasPrices', 'vm', 'pow')
* @param name Parameter name (e.g. 'minGasLimit' for 'gasConfig' topic)
* @returns The value requested or `BigInt(0)` if not found
*/
public 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;
}
return this.paramByHardfork(topic, name, this._hardfork);
}
/**
* Returns the parameter corresponding to a hardfork
* @param topic Parameter topic ('gasConfig', 'gasPrices', 'vm', 'pow')
* @param name Parameter name (e.g. 'minGasLimit' for 'gasConfig' topic)
* @param hardfork Hardfork name
* @returns The value requested or `BigInt(0)` if not found
*/
public paramByHardfork(topic: string, name: string, hardfork: string | Hardfork): bigint {
// eslint-disable-next-line no-null/no-null
let value = null;
for (const hfChanges of this.HARDFORK_CHANGES) {
// EIP-referencing HF file (e.g. berlin.json)
if ('eips' in hfChanges[1]) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
const hfEIPs = hfChanges[1].eips;
for (const eip of hfEIPs) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const valueEIP = this.paramByEIP(topic, name, eip);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
value = typeof valueEIP === 'bigint' ? valueEIP : value;
}
// Parameter-inlining HF file (e.g. istanbul.json)
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (hfChanges[1][topic] === undefined) {
throw new Error(`Topic ${topic} not defined`);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (hfChanges[1][topic][name] !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
value = hfChanges[1][topic][name].v;
}
}
if (hfChanges[0] === hardfork) break;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return BigInt(value ?? 0);
}
/**
* Returns a parameter corresponding to an EIP
* @param topic Parameter topic ('gasConfig', 'gasPrices', 'vm', 'pow')
* @param name Parameter name (e.g. 'minGasLimit' for 'gasConfig' topic)
* @param eip Number of the EIP
* @returns The value requested or `undefined` if not found
*/
// eslint-disable-next-line class-methods-use-this
public paramByEIP(topic: string, name: string, eip: number): bigint | undefined {
if (!(eip in EIPs)) {
throw new Error(`${eip} not supported`);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const eipParams = EIPs[eip];
if (!(topic in eipParams)) {
throw new Error(`Topic ${topic} not defined`);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (eipParams[topic][name] === undefined) {
return undefined;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
const value = eipParams[topic][name].v;
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return BigInt(value);
}
/**
* Returns a parameter for the hardfork active on block number or
* optional provided total difficulty (Merge HF)
* @param topic Parameter topic
* @param name Parameter name
* @param blockNumber Block number
* @param td Total difficulty
* * @returns The value requested or `BigInt(0)` if not found
*/
public paramByBlock(
topic: string,
name: string,
blockNumber: Numbers,
td?: Numbers,
timestamp?: Numbers,
): bigint {
const hardfork = this.getHardforkByBlockNumber(blockNumber, td, timestamp);
return this.paramByHardfork(topic, name, hardfork);
}
/**
* Checks if an EIP is activated by either being included in the EIPs
* manually passed in with the {@link CommonOpts.eips} or in a
* hardfork currently being active
*
* Note: this method only works for EIPs being supported
* by the {@link CommonOpts.eips} constructor option
* @param eip
*/
public isActivatedEIP(eip: number): boolean {
if (this.eips().includes(eip)) {
return true;
}
for (const hfChanges of this.HARDFORK_CHANGES) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const hf = hfChanges[1];
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
if (this.gteHardfork(hf.name) && 'eips' in hf) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if ((hf.eips as number[]).includes(eip)) {
return true;
}
}
}
return false;
}
/**
* Checks if set or provided hardfork is active on block number
* @param hardfork Hardfork name or null (for HF set)
* @param blockNumber
* @returns True if HF is active on block number
*/
public hardforkIsActiveOnBlock(
// eslint-disable-next-line @typescript-eslint/ban-types
_hardfork: string | Hardfork | null,
_blockNumber: Numbers,
): boolean {
const blockNumber = toType(_blockNumber, TypeOutput.BigInt);
const hardfork = _hardfork ?? this._hardfork;
const hfBlock = this.hardforkBlock(hardfork);
if (typeof hfBlock === 'bigint' && hfBlock !== BigInt(0) && blockNumber >= hfBlock) {
return true;
}
return false;
}
/**
* Alias to hardforkIsActiveOnBlock when hardfork is set
* @param blockNumber
* @returns True if HF is active on block number
*/
public activeOnBlock(blockNumber: Numbers): boolean {
// eslint-disable-next-line no-null/no-null
return this.hardforkIsActiveOnBlock(null, blockNumber);
}
/**
* Sequence based check if given or set HF1 is greater than or equal HF2
* @param hardfork1 Hardfork name or null (if set)
* @param hardfork2 Hardfork name
* @param opts Hardfork options
* @returns True if HF1 gte HF2
*/
public hardforkGteHardfork(
// eslint-disable-next-line @typescript-eslint/ban-types
_hardfork1: string | Hardfork | null,
hardfork2: string | Hardfork,
): boolean {
const hardfork1 = _hardfork1 ?? this._hardfork;
const hardforks = this.hardforks();
let posHf1 = -1;
let posHf2 = -1;
let index = 0;
for (const hf of hardforks) {
if (hf.name === hardfork1) posHf1 = index;
if (hf.name === hardfork2) posHf2 = index;
index += 1;
}
return posHf1 >= posHf2 && posHf2 !== -1;
}
/**
* Alias to hardforkGteHardfork when hardfork is set
* @param hardfork Hardfork name
* @returns True if hardfork set is greater than hardfork provided
*/
public gteHardfork(hardfork: string | Hardfork): boolean {
// eslint-disable-next-line no-null/no-null
return this.hardforkGteHardfork(null, hardfork);
}
/**
* Returns the hardfork change block for hardfork provided or set
* @param hardfork Hardfork name, optional if HF set
* @returns Block number or null if unscheduled
*/
// eslint-disable-next-line @typescript-eslint/ban-types
public hardforkBlock(_hardfork?: string | Hardfork): bigint | null {
const hardfork = _hardfork ?? this._hardfork;
const block = this._getHardfork(hardfork)?.block;
// eslint-disable-next-line no-null/no-null
if (block === undefined || block === null) {
// eslint-disable-next-line no-null/no-null
return null;
}
return BigInt(block);
}
// eslint-disable-next-line @typescript-eslint/ban-types
public hardforkTimestamp(_hardfork?: string | Hardfork): bigint | null {
const hardfork = _hardfork ?? this._hardfork;
const timestamp = this._getHardfork(hardfork)?.timestamp;
// eslint-disable-next-line no-null/no-null
if (timestamp === undefined || timestamp === null) {
// eslint-disable-next-line no-null/no-null
return null;
}
return BigInt(timestamp);
}
/**
* Returns the hardfork change block for eip
* @param eip EIP number
* @returns Block number or null if unscheduled
*/
// eslint-disable-next-line @typescript-eslint/ban-types
public eipBlock(eip: number): bigint | null {
for (const hfChanges of this.HARDFORK_CHANGES) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const hf = hfChanges[1];
if ('eips' in hf) {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
if (hf.eips.includes(eip)) {
return this.hardforkBlock(
typeof hfChanges[0] === 'number' ? String(hfChanges[0]) : hfChanges[0],
);
}
}
}
// eslint-disable-next-line no-null/no-null
return null;
}
/**
* Returns the hardfork change total difficulty (Merge HF) for hardfork provided or set
* @param hardfork Hardfork name, optional if HF set
* @returns Total difficulty or null if no set
*/
// eslint-disable-next-line @typescript-eslint/ban-types
public hardforkTTD(_hardfork?: string | Hardfork): bigint | null {
const hardfork = _hardfork ?? this._hardfork;
const ttd = this._getHardfork(hardfork)?.ttd;
// eslint-disable-next-line no-null/no-null
if (ttd === undefined || ttd === null) {
// eslint-disable-next-line no-null/no-null
return null;
}
return BigInt(ttd);
}
/**
* True if block number provided is the hardfork (given or set) change block
* @param blockNumber Number of the block to check
* @param hardfork Hardfork name, optional if HF set
* @returns True if blockNumber is HF block
* @deprecated
*/
public isHardforkBlock(_blockNumber: Numbers, _hardfork?: string | Hardfork): boolean {
const blockNumber = toType(_blockNumber, TypeOutput.BigInt);
const hardfork = _hardfork ?? this._hardfork;
const block = this.hardforkBlock(hardfork);
return typeof block === 'bigint' && block !== BigInt(0) ? block === blockNumber : false;
}
/**
* Returns the change block for the next hardfork after the hardfork provided or set
* @param hardfork Hardfork name, optional if HF set
* @returns Block timestamp, number or null if not available
*/
// eslint-disable-next-line @typescript-eslint/ban-types
public nextHardforkBlockOrTimestamp(_hardfork?: string | Hardfork): bigint | null {
const hardfork = _hardfork ?? this._hardfork;
const hfs = this.hardforks();
let hfIndex = hfs.findIndex(hf => hf.name === hardfork);
// If the current hardfork is merge, go one behind as merge hf is not part of these
// calcs even if the merge hf block is set
if (hardfork === Hardfork.Merge) {
hfIndex -= 1;
}
// Hardfork not found
if (hfIndex < 0) {
// eslint-disable-next-line no-null/no-null
return null;
}
let currHfTimeOrBlock = hfs[hfIndex].timestamp ?? hfs[hfIndex].block;
currHfTimeOrBlock =
// eslint-disable-next-line no-null/no-null
currHfTimeOrBlock !== null && currHfTimeOrBlock !== undefined
? Number(currHfTimeOrBlock)
: // eslint-disable-next-line no-null/no-null
null;
const nextHf = hfs.slice(hfIndex + 1).find(hf => {
let hfTimeOrBlock = hf.timestamp ?? hf.block;
hfTimeOrBlock =
// eslint-disable-next-line no-null/no-null
hfTimeOrBlock !== null && hfTimeOrBlock !== undefined
? Number(hfTimeOrBlock)
: // eslint-disable-next-line no-null/no-null
null;
return (
hf.name !== Hardfork.Merge &&
// eslint-disable-next-line no-null/no-null
hfTimeOrBlock !== null &&
hfTimeOrBlock !== undefined &&
hfTimeOrBlock !== currHfTimeOrBlock
);
});
// If no next hf found with valid block or timestamp return null
if (nextHf === undefined) {
// eslint-disable-next-line no-null/no-null
return null;
}
const nextHfBlock = nextHf.timestamp ?? nextHf.block;
// eslint-disable-next-line no-null/no-null
if (nextHfBlock === null || nextHfBlock === undefined) {
// eslint-disable-next-line no-null/no-null
return null;
}
return BigInt(nextHfBlock);
}
/**
* Returns the change block for the next hardfork after the hardfork provided or set
* @param hardfork Hardfork name, optional if HF set
* @returns Block number or null if not available
* @deprecated
*/
// eslint-disable-next-line @typescript-eslint/ban-types
public nextHardforkBlock(_hardfork?: string | Hardfork): bigint | null {
const hardfork = _hardfork ?? this._hardfork;
let hfBlock = this.hardforkBlock(hardfork);
// If this is a merge hardfork with block not set, then we fallback to previous hardfork
// to find the nextHardforkBlock
// eslint-disable-next-line no-null/no-null
if (hfBlock === null && hardfork === Hardfork.Merge) {
const hfs = this.hardforks();
// eslint-disable-next-line no-null/no-null
const mergeIndex = hfs.findIndex(hf => hf.ttd !== null && hf.ttd !== undefined);
if (mergeIndex < 0) {
throw Error(`Merge hardfork should have been found`);
}
hfBlock = this.hardforkBlock(hfs[mergeIndex - 1].name);
}
// eslint-disable-next-line no-null/no-null
if (hfBlock === null) {
// eslint-disable-next-line no-null/no-null
return null;
}
// Next fork block number or null if none available
// Logic: if accumulator is still null and on the first occurrence of
// a block greater than the current hfBlock set the accumulator,
// pass on the accumulator as the final result from this time on
// eslint-disable-next-line no-null/no-null, @typescript-eslint/ban-types
const nextHfBlock = this.hardforks().reduce((acc: bigint | null, hf: HardforkConfig) => {
// We need to ignore the merge block in our next hardfork calc
const block = BigInt(
// eslint-disable-next-line no-null/no-null
hf.block === null || (hf.ttd !== undefined && hf.ttd !== null) ? 0 : hf.block,
);
// Typescript can't seem to follow that the hfBlock is not null at this point
// eslint-disable-next-line no-null/no-null
return block > hfBlock! && acc === null ? block : acc;
// eslint-disable-next-line no-null/no-null
}, null);
return nextHfBlock;
}
/**
* True if block number provided is the hardfork change block following the hardfork given or set
* @param blockNumber Number of the block to check
* @param hardfork Hardfork name, optional if HF set
* @returns True if blockNumber is HF block
* @deprecated
*/
public isNextHardforkBlock(_blockNumber: Numbers, _hardfork?: string | Hardfork): boolean {
const blockNumber = toType(_blockNumber, TypeOutput.BigInt);
const hardfork = _hardfork ?? this._hardfork;
// eslint-disable-next-line deprecation/deprecation
const nextHardforkBlock = this.nextHardforkBlock(hardfork);
// eslint-disable-next-line no-null/no-null
return nextHardforkBlock === null ? false : nextHardforkBlock === blockNumber;
}
/**
* Internal helper function to calculate a fork hash
* @param hardfork Hardfork name
* @param genesisHash Genesis block hash of the chain
* @returns Fork hash as hex string
*/
public _calcForkHash(hardfork: string | Hardfork, genesisHash: Uint8Array) {
let hfUint8Array = new Uint8Array();
let prevBlockOrTime = 0;
for (const hf of this.hardforks()) {
const { block, timestamp, name } = hf;
// Timestamp to be used for timestamp based hfs even if we may bundle
// block number with them retrospectively
let blockOrTime = timestamp ?? block;
// eslint-disable-next-line no-null/no-null
blockOrTime = blockOrTime !== null ? Number(blockOrTime) : null;
// Skip for chainstart (0), not applied HFs (null) and
// when already applied on same blockOrTime HFs
// and on the merge since forkhash doesn't change on merge hf
if (
typeof blockOrTime === 'number' &&
blockOrTime !== 0 &&
blockOrTime !== prevBlockOrTime &&
name !== Hardfork.Merge
) {
const hfBlockUint8Array = hexToBytes(blockOrTime.toString(16).padStart(16, '0'));
hfUint8Array = uint8ArrayConcat(hfUint8Array, hfBlockUint8Array);
prevBlockOrTime = blockOrTime;
}
if (hf.name === hardfork) break;
}
const inputUint8Array = uint8ArrayConcat(genesisHash, hfUint8Array);
// CRC32 delivers result as signed (negative) 32-bit integer,
// convert to hex string
// eslint-disable-next-line no-bitwise
const forkhash = bytesToHex(intToUint8Array(crc32Uint8Array(inputUint8Array) >>> 0));
return forkhash;
}
/**
* Returns an eth/64 compliant fork hash (EIP-2124)
* @param hardfork Hardfork name, optional if HF set
* @param genesisHash Genesis block hash of the chain, optional if already defined and not needed to be calculated
*/
public forkHash(_hardfork?: string | Hardfork, genesisHash?: Uint8Array): string {
const hardfork = _hardfork ?? this._hardfork;
const data = this._getHardfork(hardfork);
if (
// eslint-disable-next-line no-null/no-null
data === null ||
// eslint-disable-next-line no-null/no-null
(data?.block === null && data?.timestamp === undefined && data?.ttd === undefined)
) {
const msg = 'No fork hash calculation possible for future hardfork';
throw new Error(msg);
}
// eslint-disable-next-line no-null/no-null
if (data?.forkHash !== null && data?.forkHash !== undefined) {
return data.forkHash;
}
if (!genesisHash) throw new Error('genesisHash required for forkHash calculation');
return this._calcForkHash(hardfork, genesisHash);
}
/**
*
* @param forkHash Fork hash as a hex string
* @returns Array with hardfork data (name, block, forkHash)
*/
// eslint-disable-next-line @typescript-eslint/ban-types
public hardforkForForkHash(forkHash: string): HardforkConfig | null {
const resArray = this.hardforks().filter((hf: HardforkConfig) => hf.forkHash === forkHash);
// eslint-disable-next-line no-null/no-null
return resArray.length >= 1 ? resArray[resArray.length - 1] : null;
}
/**
* Sets any missing forkHashes on the passed-in {@link Common} instance
* @param common The {@link Common} to set the forkHashes for
* @param genesisHash The genesis block hash
*/
public setForkHashes(genesisHash: Uint8Array) {
for (const hf of this.hardforks()) {
const blockOrTime = hf.timestamp ?? hf.block;
if (
// eslint-disable-next-line no-null/no-null
(hf.forkHash === null || hf.forkHash === undefined) &&
// eslint-disable-next-line no-null/no-null
((blockOrTime !== null && blockOrTime !== undefined) ||
typeof hf.ttd !== 'undefined')
) {
hf.forkHash = this.forkHash(hf.name, genesisHash);
}
}
}
/**
* Returns the Genesis parameters of the current chain
* @returns Genesis dictionary
*/
public genesis(): GenesisBlockConfig {
return this._chainParams.genesis;
}
/**
* Returns the hardforks for current chain
* @returns {Array} Array with arrays of hardforks
*/
public hardforks(): HardforkConfig[] {
return this._chainParams.hardforks;
}
/**
* Returns bootstrap nodes for the current chain
* @returns {Dictionary} Dict with bootstrap nodes
*/
public bootstrapNodes(): BootstrapNodeConfig[] | undefined {
return this._chainParams.bootstrapNodes;
}
/**
* Returns DNS networks for the current chain
* @returns {String[]} Array of DNS ENR urls
*/
public dnsNetworks(): string[] {
return this._chainParams.dnsNetworks!;
}
/**
* Returns the hardfork set
* @returns Hardfork name
*/
public hardfork(): string | Hardfork {
return this._hardfork;
}
/**
* Returns the Id of current chain
* @returns chain Id
*/
public chainId(): bigint {
return BigInt(this._chainParams.chainId);
}
/**
* Returns the name of current chain
* @returns chain name (lower case)
*/
public chainName(): string {
return this._chainParams.name;
}
/**
* Returns the Id of current network
* @returns network Id
*/
public networkId(): bigint {
return BigInt(this._chainParams.networkId);
}
/**
* Returns the active EIPs
* @returns List of EIPs
*/
public eips(): number[] {
return this._eips;
}
/**
* Returns the consensus type of the network
* Possible values: "pow"|"poa"|"pos"
*
* Note: This value can update along a Hardfork.
*/
public consensusType(): string | ConsensusType {
const hardfork = this.hardfork();
let value;
for (const hfChanges of this.HARDFORK_CHANGES) {
if ('consensus' in hfChanges[1]) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
value = hfChanges[1].consensus.type;
}
if (hfChanges[0] === hardfork) break;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return value ?? this._chainParams.consensus.type;
}
/**
* Returns the concrete consensus implementation
* algorithm or protocol for the network
* e.g. "ethash" for "pow" consensus type,
* "clique" for "poa" consensus type or
* "casper" for "pos" consensus type.
*
* Note: This value can update along a Hardfork.
*/
public consensusAlgorithm(): string | ConsensusAlgorithm {
const hardfork = this.hardfork();
let value;
for (const hfChanges of this.HARDFORK_CHANGES) {
if ('consensus' in hfChanges[1]) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
value = hfChanges[1].consensus.algorithm;
}
if (hfChanges[0] === hardfork) break;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return value ?? (this._chainParams.consensus.algorithm as ConsensusAlgorithm);
}
/**
* Returns a dictionary with consensus configuration
* parameters based on the consensus algorithm
*
* Expected returns (parameters must be present in
* the respective chain json files):
*
* ethash: empty object
* clique: period, epoch
* casper: empty object
*
* Note: This value can update along a Hardfork.
*/
public consensusConfig(): { [key: string]: CliqueConfig | EthashConfig | CasperConfig } {
const hardfork = this.hardfork();
let value;
for (const hfChanges of this.HARDFORK_CHANGES) {
if ('consensus' in hfChanges[1]) {
// The config parameter is named after the respective consensus algorithm
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
value = hfChanges[1].consensus[hfChanges[1].consensus.algorithm];
}
if (hfChanges[0] === hardfork) break;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return (
value ??
this._chainParams.consensus[this.consensusAlgorithm() as ConsensusAlgorithm] ??
{}
);
}
/**
* Returns a deep copy of this {@link Common} instance.
*/
public copy(): Common {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-assignment
const copy = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
copy.removeAllListeners();
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return copy;
}
public static _getInitializedChains(customChains?: ChainConfig[]): ChainsConfig {
const names: ChainName = {};
for (const [name, id] of Object.entries(Chain)) {
names[id] = name.toLowerCase();
}
const chains = { mainnet, goerli, sepolia } as ChainsConfig;
if (customChains) {
for (const chain of customChains) {
const { name } = chain;
names[chain.chainId.toString()] = name;
chains[name] = chain;
}
}
chains.names = names;
return chains;
}
}