UNPKG

@ethereumjs/block

Version:
840 lines 39.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BlockHeader = void 0; const common_1 = require("@ethereumjs/common"); const rlp_1 = require("@ethereumjs/rlp"); const util_1 = require("@ethereumjs/util"); const keccak_js_1 = require("ethereum-cryptography/keccak.js"); const clique_js_1 = require("./clique.js"); const helpers_js_1 = require("./helpers.js"); const DEFAULT_GAS_LIMIT = BigInt('0xffffffffffffff'); /** * An object that represents the block header. */ class BlockHeader { /** * This constructor takes the values, validates them, assigns them and freezes the object. * * @deprecated Use the public static factory methods to assist in creating a Header object from * varying data types. For a default empty header, use {@link BlockHeader.fromHeaderData}. * */ constructor(headerData, opts = {}) { this.cache = { hash: undefined, }; if (opts.common) { this.common = opts.common.copy(); } else { this.common = new common_1.Common({ chain: common_1.Chain.Mainnet, // default }); } this.keccakFunction = this.common.customCrypto.keccak256 ?? keccak_js_1.keccak256; const skipValidateConsensusFormat = opts.skipConsensusFormatValidation ?? false; const defaults = { parentHash: (0, util_1.zeros)(32), uncleHash: util_1.KECCAK256_RLP_ARRAY, coinbase: util_1.Address.zero(), stateRoot: (0, util_1.zeros)(32), transactionsTrie: util_1.KECCAK256_RLP, receiptTrie: util_1.KECCAK256_RLP, logsBloom: (0, util_1.zeros)(256), difficulty: util_1.BIGINT_0, number: util_1.BIGINT_0, gasLimit: DEFAULT_GAS_LIMIT, gasUsed: util_1.BIGINT_0, timestamp: util_1.BIGINT_0, extraData: new Uint8Array(0), mixHash: (0, util_1.zeros)(32), nonce: (0, util_1.zeros)(8), }; const parentHash = (0, util_1.toType)(headerData.parentHash, util_1.TypeOutput.Uint8Array) ?? defaults.parentHash; const uncleHash = (0, util_1.toType)(headerData.uncleHash, util_1.TypeOutput.Uint8Array) ?? defaults.uncleHash; const coinbase = new util_1.Address((0, util_1.toType)(headerData.coinbase ?? defaults.coinbase, util_1.TypeOutput.Uint8Array)); const stateRoot = (0, util_1.toType)(headerData.stateRoot, util_1.TypeOutput.Uint8Array) ?? defaults.stateRoot; const transactionsTrie = (0, util_1.toType)(headerData.transactionsTrie, util_1.TypeOutput.Uint8Array) ?? defaults.transactionsTrie; const receiptTrie = (0, util_1.toType)(headerData.receiptTrie, util_1.TypeOutput.Uint8Array) ?? defaults.receiptTrie; const logsBloom = (0, util_1.toType)(headerData.logsBloom, util_1.TypeOutput.Uint8Array) ?? defaults.logsBloom; const difficulty = (0, util_1.toType)(headerData.difficulty, util_1.TypeOutput.BigInt) ?? defaults.difficulty; const number = (0, util_1.toType)(headerData.number, util_1.TypeOutput.BigInt) ?? defaults.number; const gasLimit = (0, util_1.toType)(headerData.gasLimit, util_1.TypeOutput.BigInt) ?? defaults.gasLimit; const gasUsed = (0, util_1.toType)(headerData.gasUsed, util_1.TypeOutput.BigInt) ?? defaults.gasUsed; const timestamp = (0, util_1.toType)(headerData.timestamp, util_1.TypeOutput.BigInt) ?? defaults.timestamp; const extraData = (0, util_1.toType)(headerData.extraData, util_1.TypeOutput.Uint8Array) ?? defaults.extraData; const mixHash = (0, util_1.toType)(headerData.mixHash, util_1.TypeOutput.Uint8Array) ?? defaults.mixHash; const nonce = (0, util_1.toType)(headerData.nonce, util_1.TypeOutput.Uint8Array) ?? defaults.nonce; const setHardfork = opts.setHardfork ?? false; if (setHardfork === true) { this.common.setHardforkBy({ blockNumber: number, timestamp, }); } else if (typeof setHardfork !== 'boolean') { this.common.setHardforkBy({ blockNumber: number, td: setHardfork, timestamp, }); } // Hardfork defaults which couldn't be paired with earlier defaults const hardforkDefaults = { baseFeePerGas: this.common.isActivatedEIP(1559) ? number === this.common.hardforkBlock(common_1.Hardfork.London) ? this.common.param('gasConfig', 'initialBaseFee') : util_1.BIGINT_7 : undefined, withdrawalsRoot: this.common.isActivatedEIP(4895) ? util_1.KECCAK256_RLP : undefined, blobGasUsed: this.common.isActivatedEIP(4844) ? util_1.BIGINT_0 : undefined, excessBlobGas: this.common.isActivatedEIP(4844) ? util_1.BIGINT_0 : undefined, parentBeaconBlockRoot: this.common.isActivatedEIP(4788) ? (0, util_1.zeros)(32) : undefined, requestsRoot: this.common.isActivatedEIP(7685) ? util_1.KECCAK256_RLP : undefined, }; const baseFeePerGas = (0, util_1.toType)(headerData.baseFeePerGas, util_1.TypeOutput.BigInt) ?? hardforkDefaults.baseFeePerGas; const withdrawalsRoot = (0, util_1.toType)(headerData.withdrawalsRoot, util_1.TypeOutput.Uint8Array) ?? hardforkDefaults.withdrawalsRoot; const blobGasUsed = (0, util_1.toType)(headerData.blobGasUsed, util_1.TypeOutput.BigInt) ?? hardforkDefaults.blobGasUsed; const excessBlobGas = (0, util_1.toType)(headerData.excessBlobGas, util_1.TypeOutput.BigInt) ?? hardforkDefaults.excessBlobGas; const parentBeaconBlockRoot = (0, util_1.toType)(headerData.parentBeaconBlockRoot, util_1.TypeOutput.Uint8Array) ?? hardforkDefaults.parentBeaconBlockRoot; const requestsRoot = (0, util_1.toType)(headerData.requestsRoot, util_1.TypeOutput.Uint8Array) ?? hardforkDefaults.requestsRoot; if (!this.common.isActivatedEIP(1559) && baseFeePerGas !== undefined) { throw new Error('A base fee for a block can only be set with EIP1559 being activated'); } if (!this.common.isActivatedEIP(4895) && withdrawalsRoot !== undefined) { throw new Error('A withdrawalsRoot for a header can only be provided with EIP4895 being activated'); } if (!this.common.isActivatedEIP(4844)) { if (blobGasUsed !== undefined) { throw new Error('blob gas used can only be provided with EIP4844 activated'); } if (excessBlobGas !== undefined) { throw new Error('excess blob gas can only be provided with EIP4844 activated'); } } if (!this.common.isActivatedEIP(4788) && parentBeaconBlockRoot !== undefined) { throw new Error('A parentBeaconBlockRoot for a header can only be provided with EIP4788 being activated'); } if (!this.common.isActivatedEIP(7685) && requestsRoot !== undefined) { throw new Error('requestsRoot can only be provided with EIP 7685 activated'); } this.parentHash = parentHash; this.uncleHash = uncleHash; this.coinbase = coinbase; this.stateRoot = stateRoot; this.transactionsTrie = transactionsTrie; this.receiptTrie = receiptTrie; this.logsBloom = logsBloom; this.difficulty = difficulty; this.number = number; this.gasLimit = gasLimit; this.gasUsed = gasUsed; this.timestamp = timestamp; this.extraData = extraData; this.mixHash = mixHash; this.nonce = nonce; this.baseFeePerGas = baseFeePerGas; this.withdrawalsRoot = withdrawalsRoot; this.blobGasUsed = blobGasUsed; this.excessBlobGas = excessBlobGas; this.parentBeaconBlockRoot = parentBeaconBlockRoot; this.requestsRoot = requestsRoot; this._genericFormatValidation(); this._validateDAOExtraData(); // Now we have set all the values of this Header, we possibly have set a dummy // `difficulty` value (defaults to 0). If we have a `calcDifficultyFromHeader` // block option parameter, we instead set difficulty to this value. if (opts.calcDifficultyFromHeader && this.common.consensusAlgorithm() === common_1.ConsensusAlgorithm.Ethash) { this.difficulty = this.ethashCanonicalDifficulty(opts.calcDifficultyFromHeader); } // If cliqueSigner is provided, seal block with provided privateKey. if (opts.cliqueSigner) { // Ensure extraData is at least length CLIQUE_EXTRA_VANITY + CLIQUE_EXTRA_SEAL const minExtraDataLength = clique_js_1.CLIQUE_EXTRA_VANITY + clique_js_1.CLIQUE_EXTRA_SEAL; if (this.extraData.length < minExtraDataLength) { const remainingLength = minExtraDataLength - this.extraData.length; this.extraData = (0, util_1.concatBytes)(this.extraData, new Uint8Array(remainingLength)); } this.extraData = this.cliqueSealBlock(opts.cliqueSigner); } // Validate consensus format after block is sealed (if applicable) so extraData checks will pass if (skipValidateConsensusFormat === false) this._consensusFormatValidation(); const freeze = opts?.freeze ?? true; if (freeze) { Object.freeze(this); } } /** * EIP-4399: After merge to PoS, `mixHash` supplanted as `prevRandao` */ get prevRandao() { if (!this.common.isActivatedEIP(4399)) { const msg = this._errorMsg('The prevRandao parameter can only be accessed when EIP-4399 is activated'); throw new Error(msg); } return this.mixHash; } /** * Static constructor to create a block header from a header data dictionary * * @param headerData * @param opts */ static fromHeaderData(headerData = {}, opts = {}) { return new BlockHeader(headerData, opts); } /** * Static constructor to create a block header from a RLP-serialized header * * @param serializedHeaderData * @param opts */ static fromRLPSerializedHeader(serializedHeaderData, opts = {}) { const values = rlp_1.RLP.decode(serializedHeaderData); if (!Array.isArray(values)) { throw new Error('Invalid serialized header input. Must be array'); } return BlockHeader.fromValuesArray(values, opts); } /** * Static constructor to create a block header from an array of Bytes values * * @param values * @param opts */ static fromValuesArray(values, opts = {}) { const headerData = (0, helpers_js_1.valuesArrayToHeaderData)(values); const { number, baseFeePerGas, excessBlobGas, blobGasUsed, parentBeaconBlockRoot, requestsRoot, } = headerData; const header = BlockHeader.fromHeaderData(headerData, opts); if (header.common.isActivatedEIP(1559) && baseFeePerGas === undefined) { const eip1559ActivationBlock = (0, util_1.bigIntToBytes)(header.common.eipBlock(1559)); if (eip1559ActivationBlock !== undefined && (0, util_1.equalsBytes)(eip1559ActivationBlock, number)) { throw new Error('invalid header. baseFeePerGas should be provided'); } } if (header.common.isActivatedEIP(4844)) { if (excessBlobGas === undefined) { throw new Error('invalid header. excessBlobGas should be provided'); } else if (blobGasUsed === undefined) { throw new Error('invalid header. blobGasUsed should be provided'); } } if (header.common.isActivatedEIP(4788) && parentBeaconBlockRoot === undefined) { throw new Error('invalid header. parentBeaconBlockRoot should be provided'); } if (header.common.isActivatedEIP(7685) && requestsRoot === undefined) { throw new Error('invalid header. requestsRoot should be provided'); } return header; } /** * Validates correct buffer lengths, throws if invalid. */ _genericFormatValidation() { const { parentHash, stateRoot, transactionsTrie, receiptTrie, mixHash, nonce } = this; if (parentHash.length !== 32) { const msg = this._errorMsg(`parentHash must be 32 bytes, received ${parentHash.length} bytes`); throw new Error(msg); } if (stateRoot.length !== 32) { const msg = this._errorMsg(`stateRoot must be 32 bytes, received ${stateRoot.length} bytes`); throw new Error(msg); } if (transactionsTrie.length !== 32) { const msg = this._errorMsg(`transactionsTrie must be 32 bytes, received ${transactionsTrie.length} bytes`); throw new Error(msg); } if (receiptTrie.length !== 32) { const msg = this._errorMsg(`receiptTrie must be 32 bytes, received ${receiptTrie.length} bytes`); throw new Error(msg); } if (mixHash.length !== 32) { const msg = this._errorMsg(`mixHash must be 32 bytes, received ${mixHash.length} bytes`); throw new Error(msg); } if (nonce.length !== 8) { const msg = this._errorMsg(`nonce must be 8 bytes, received ${nonce.length} bytes`); throw new Error(msg); } // check if the block used too much gas if (this.gasUsed > this.gasLimit) { const msg = this._errorMsg(`Invalid block: too much gas used. Used: ${this.gasUsed}, gas limit: ${this.gasLimit}`); throw new Error(msg); } // Validation for EIP-1559 blocks if (this.common.isActivatedEIP(1559)) { if (typeof this.baseFeePerGas !== 'bigint') { const msg = this._errorMsg('EIP1559 block has no base fee field'); throw new Error(msg); } const londonHfBlock = this.common.hardforkBlock(common_1.Hardfork.London); if (typeof londonHfBlock === 'bigint' && londonHfBlock !== util_1.BIGINT_0 && this.number === londonHfBlock) { const initialBaseFee = this.common.param('gasConfig', 'initialBaseFee'); if (this.baseFeePerGas !== initialBaseFee) { const msg = this._errorMsg('Initial EIP1559 block does not have initial base fee'); throw new Error(msg); } } } if (this.common.isActivatedEIP(4895)) { if (this.withdrawalsRoot === undefined) { const msg = this._errorMsg('EIP4895 block has no withdrawalsRoot field'); throw new Error(msg); } if (this.withdrawalsRoot?.length !== 32) { const msg = this._errorMsg(`withdrawalsRoot must be 32 bytes, received ${this.withdrawalsRoot.length} bytes`); throw new Error(msg); } } if (this.common.isActivatedEIP(4788)) { if (this.parentBeaconBlockRoot === undefined) { const msg = this._errorMsg('EIP4788 block has no parentBeaconBlockRoot field'); throw new Error(msg); } if (this.parentBeaconBlockRoot?.length !== 32) { const msg = this._errorMsg(`parentBeaconBlockRoot must be 32 bytes, received ${this.parentBeaconBlockRoot.length} bytes`); throw new Error(msg); } } if (this.common.isActivatedEIP(7685)) { if (this.requestsRoot === undefined) { const msg = this._errorMsg('EIP7685 block has no requestsRoot field'); throw new Error(msg); } } } /** * Checks static parameters related to consensus algorithm * @throws if any check fails */ _consensusFormatValidation() { const { nonce, uncleHash, difficulty, extraData, number } = this; // Consensus type dependent checks if (this.common.consensusAlgorithm() === common_1.ConsensusAlgorithm.Ethash) { // PoW/Ethash if (number > util_1.BIGINT_0 && 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'); throw new Error(msg); } } if (this.common.consensusAlgorithm() === common_1.ConsensusAlgorithm.Clique) { // PoA/Clique const minLength = clique_js_1.CLIQUE_EXTRA_VANITY + clique_js_1.CLIQUE_EXTRA_SEAL; if (!this.cliqueIsEpochTransition()) { // ExtraData length on epoch transition if (this.extraData.length !== minLength) { const msg = this._errorMsg(`extraData must be ${minLength} bytes on non-epoch transition blocks, received ${this.extraData.length} bytes`); throw new Error(msg); } } else { const signerLength = this.extraData.length - minLength; if (signerLength % 20 !== 0) { const msg = this._errorMsg(`invalid signer list length in extraData, received signer length of ${signerLength} (not divisible by 20)`); throw new Error(msg); } // coinbase (beneficiary) on epoch transition if (!this.coinbase.isZero()) { const msg = this._errorMsg(`coinbase must be filled with zeros on epoch transition blocks, received ${this.coinbase}`); throw new Error(msg); } } // MixHash format if (!(0, util_1.equalsBytes)(this.mixHash, new Uint8Array(32))) { const msg = this._errorMsg(`mixHash must be filled with zeros, received ${this.mixHash}`); throw new Error(msg); } } // Validation for PoS blocks (EIP-3675) if (this.common.consensusType() === common_1.ConsensusType.ProofOfStake) { let error = false; let errorMsg = ''; if (!(0, util_1.equalsBytes)(uncleHash, util_1.KECCAK256_RLP_ARRAY)) { errorMsg += `, uncleHash: ${(0, util_1.bytesToHex)(uncleHash)} (expected: ${(0, util_1.bytesToHex)(util_1.KECCAK256_RLP_ARRAY)})`; error = true; } if (number !== util_1.BIGINT_0) { // Skip difficulty, nonce, and extraData check for PoS genesis block as genesis block may have non-zero difficulty (if TD is > 0) if (difficulty !== util_1.BIGINT_0) { errorMsg += `, difficulty: ${difficulty} (expected: 0)`; error = true; } if (extraData.length > 32) { errorMsg += `, extraData: ${(0, util_1.bytesToHex)(extraData)} (cannot exceed 32 bytes length, received ${extraData.length} bytes)`; error = true; } if (!(0, util_1.equalsBytes)(nonce, (0, util_1.zeros)(8))) { errorMsg += `, nonce: ${(0, util_1.bytesToHex)(nonce)} (expected: ${(0, util_1.bytesToHex)((0, util_1.zeros)(8))})`; error = true; } } if (error) { const msg = this._errorMsg(`Invalid PoS block: ${errorMsg}`); throw new Error(msg); } } } /** * Validates if the block gasLimit remains in the boundaries set by the protocol. * Throws if out of bounds. * * @param parentBlockHeader - the header from the parent `Block` of this header */ validateGasLimit(parentBlockHeader) { let parentGasLimit = parentBlockHeader.gasLimit; // EIP-1559: assume double the parent gas limit on fork block // to adopt to the new gas target centered logic const londonHardforkBlock = this.common.hardforkBlock(common_1.Hardfork.London); if (typeof londonHardforkBlock === 'bigint' && londonHardforkBlock !== util_1.BIGINT_0 && this.number === londonHardforkBlock) { const elasticity = this.common.param('gasConfig', 'elasticityMultiplier'); parentGasLimit = parentGasLimit * elasticity; } const gasLimit = this.gasLimit; const a = parentGasLimit / this.common.param('gasConfig', 'gasLimitBoundDivisor'); const maxGasLimit = parentGasLimit + a; const minGasLimit = parentGasLimit - a; if (gasLimit >= maxGasLimit) { const msg = this._errorMsg(`gas limit increased too much. Gas limit: ${gasLimit}, max gas limit: ${maxGasLimit}`); throw new Error(msg); } if (gasLimit <= minGasLimit) { const msg = this._errorMsg(`gas limit decreased too much. Gas limit: ${gasLimit}, min gas limit: ${minGasLimit}`); throw new Error(msg); } if (gasLimit < this.common.param('gasConfig', 'minGasLimit')) { const msg = this._errorMsg(`gas limit decreased below minimum gas limit. Gas limit: ${gasLimit}, minimum gas limit: ${this.common.param('gasConfig', 'minGasLimit')}`); throw new Error(msg); } } /** * Calculates the base fee for a potential next block */ calcNextBaseFee() { if (!this.common.isActivatedEIP(1559)) { const msg = this._errorMsg('calcNextBaseFee() can only be called with EIP1559 being activated'); throw new Error(msg); } let nextBaseFee; const elasticity = this.common.param('gasConfig', 'elasticityMultiplier'); const parentGasTarget = this.gasLimit / elasticity; if (parentGasTarget === this.gasUsed) { nextBaseFee = this.baseFeePerGas; } else if (this.gasUsed > parentGasTarget) { const gasUsedDelta = this.gasUsed - parentGasTarget; const baseFeeMaxChangeDenominator = this.common.param('gasConfig', 'baseFeeMaxChangeDenominator'); const calculatedDelta = (this.baseFeePerGas * gasUsedDelta) / parentGasTarget / baseFeeMaxChangeDenominator; nextBaseFee = (calculatedDelta > util_1.BIGINT_1 ? calculatedDelta : util_1.BIGINT_1) + this.baseFeePerGas; } else { const gasUsedDelta = parentGasTarget - this.gasUsed; const baseFeeMaxChangeDenominator = this.common.param('gasConfig', 'baseFeeMaxChangeDenominator'); const calculatedDelta = (this.baseFeePerGas * gasUsedDelta) / parentGasTarget / baseFeeMaxChangeDenominator; nextBaseFee = this.baseFeePerGas - calculatedDelta > util_1.BIGINT_0 ? this.baseFeePerGas - calculatedDelta : util_1.BIGINT_0; } return nextBaseFee; } /** * Returns the price per unit of blob gas for a blob transaction in the current/pending block * @returns the price in gwei per unit of blob gas spent */ getBlobGasPrice() { if (this.excessBlobGas === undefined) { throw new Error('header must have excessBlobGas field populated'); } return this._getBlobGasPrice(this.excessBlobGas); } /** * Returns the blob gas price depending upon the `excessBlobGas` value * @param excessBlobGas */ _getBlobGasPrice(excessBlobGas) { return (0, helpers_js_1.fakeExponential)(this.common.param('gasPrices', 'minBlobGasPrice'), excessBlobGas, this.common.param('gasConfig', 'blobGasPriceUpdateFraction')); } /** * Returns the total fee for blob gas spent for including blobs in block. * * @param numBlobs number of blobs in the transaction/block * @returns the total blob gas fee for numBlobs blobs */ calcDataFee(numBlobs) { const blobGasPerBlob = this.common.param('gasConfig', 'blobGasPerBlob'); const blobGasUsed = blobGasPerBlob * BigInt(numBlobs); const blobGasPrice = this.getBlobGasPrice(); return blobGasUsed * blobGasPrice; } /** * Calculates the excess blob gas for next (hopefully) post EIP 4844 block. */ calcNextExcessBlobGas() { // The validation of the fields and 4844 activation is already taken care in BlockHeader constructor const targetGasConsumed = (this.excessBlobGas ?? util_1.BIGINT_0) + (this.blobGasUsed ?? util_1.BIGINT_0); const targetBlobGasPerBlock = this.common.param('gasConfig', 'targetBlobGasPerBlock'); if (targetGasConsumed <= targetBlobGasPerBlock) { return util_1.BIGINT_0; } else { return targetGasConsumed - targetBlobGasPerBlock; } } /** * Calculate the blob gas price of the block built on top of this one * @returns The blob gas price */ calcNextBlobGasPrice() { return this._getBlobGasPrice(this.calcNextExcessBlobGas()); } /** * Returns a Uint8Array Array of the raw Bytes in this header, in order. */ raw() { const rawItems = [ this.parentHash, this.uncleHash, this.coinbase.bytes, this.stateRoot, this.transactionsTrie, this.receiptTrie, this.logsBloom, (0, util_1.bigIntToUnpaddedBytes)(this.difficulty), (0, util_1.bigIntToUnpaddedBytes)(this.number), (0, util_1.bigIntToUnpaddedBytes)(this.gasLimit), (0, util_1.bigIntToUnpaddedBytes)(this.gasUsed), (0, util_1.bigIntToUnpaddedBytes)(this.timestamp ?? util_1.BIGINT_0), this.extraData, this.mixHash, this.nonce, ]; if (this.common.isActivatedEIP(1559)) { rawItems.push((0, util_1.bigIntToUnpaddedBytes)(this.baseFeePerGas)); } if (this.common.isActivatedEIP(4895)) { rawItems.push(this.withdrawalsRoot); } // in kaunstinen 2 verkle is scheduled after withdrawals, will eventually be post deneb hopefully if (this.common.isActivatedEIP(6800)) { // execution witness is not mandatory part of the the block so nothing to push here // but keep this comment segment for clarity regarding the same and move it according as per the // HF sequence eventually planned } if (this.common.isActivatedEIP(4844)) { rawItems.push((0, util_1.bigIntToUnpaddedBytes)(this.blobGasUsed)); rawItems.push((0, util_1.bigIntToUnpaddedBytes)(this.excessBlobGas)); } if (this.common.isActivatedEIP(4788)) { rawItems.push(this.parentBeaconBlockRoot); } if (this.common.isActivatedEIP(7685)) { rawItems.push(this.requestsRoot); } return rawItems; } /** * Returns the hash of the block header. */ hash() { if (Object.isFrozen(this)) { if (!this.cache.hash) { this.cache.hash = this.keccakFunction(rlp_1.RLP.encode(this.raw())); } return this.cache.hash; } return this.keccakFunction(rlp_1.RLP.encode(this.raw())); } /** * Checks if the block header is a genesis header. */ isGenesis() { return this.number === util_1.BIGINT_0; } _requireClique(name) { if (this.common.consensusAlgorithm() !== common_1.ConsensusAlgorithm.Clique) { const msg = this._errorMsg(`BlockHeader.${name}() call only supported for clique PoA networks`); throw new Error(msg); } } /** * Returns the canonical difficulty for this block. * * @param parentBlockHeader - the header from the parent `Block` of this header */ ethashCanonicalDifficulty(parentBlockHeader) { if (this.common.consensusType() !== common_1.ConsensusType.ProofOfWork) { const msg = this._errorMsg('difficulty calculation is only supported on PoW chains'); throw new Error(msg); } if (this.common.consensusAlgorithm() !== common_1.ConsensusAlgorithm.Ethash) { const msg = this._errorMsg('difficulty calculation currently only supports the ethash algorithm'); throw new Error(msg); } const blockTs = this.timestamp; const { timestamp: parentTs, difficulty: parentDif } = parentBlockHeader; 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; if (this.common.gteHardfork(common_1.Hardfork.Byzantium)) { // max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99) (EIP100) const uncleAddend = (0, util_1.equalsBytes)(parentBlockHeader.uncleHash, util_1.KECCAK256_RLP_ARRAY) ? 1 : 2; let a = BigInt(uncleAddend) - (blockTs - parentTs) / BigInt(9); const cutoff = BigInt(-99); // MAX(cutoff, a) if (cutoff > a) { a = cutoff; } dif = parentDif + offset * a; } if (this.common.gteHardfork(common_1.Hardfork.Byzantium)) { // Get delay as parameter from common num = num - this.common.param('pow', 'difficultyBombDelay'); if (num < util_1.BIGINT_0) { num = util_1.BIGINT_0; } } else if (this.common.gteHardfork(common_1.Hardfork.Homestead)) { // 1 - (block_timestamp - parent_timestamp) // 10 let a = util_1.BIGINT_1 - (blockTs - parentTs) / BigInt(10); const cutoff = BigInt(-99); // MAX(cutoff, a) if (cutoff > a) { a = cutoff; } dif = parentDif + offset * a; } else { // pre-homestead if (parentTs + this.common.param('pow', 'durationLimit') > blockTs) { dif = offset + parentDif; } else { dif = parentDif - offset; } } const exp = num / BigInt(100000) - util_1.BIGINT_2; if (exp >= 0) { dif = dif + util_1.BIGINT_2 ** exp; } if (dif < minimumDifficulty) { dif = minimumDifficulty; } return dif; } /** * PoA clique signature hash without the seal. */ cliqueSigHash() { this._requireClique('cliqueSigHash'); const raw = this.raw(); raw[12] = this.extraData.subarray(0, this.extraData.length - clique_js_1.CLIQUE_EXTRA_SEAL); return this.keccakFunction(rlp_1.RLP.encode(raw)); } /** * Checks if the block header is an epoch transition * header (only clique PoA, throws otherwise) */ cliqueIsEpochTransition() { this._requireClique('cliqueIsEpochTransition'); const epoch = BigInt(this.common.consensusConfig().epoch); // Epoch transition block if the block number has no // remainder on the division by the epoch length return this.number % epoch === util_1.BIGINT_0; } /** * Returns extra vanity data * (only clique PoA, throws otherwise) */ cliqueExtraVanity() { this._requireClique('cliqueExtraVanity'); return this.extraData.subarray(0, clique_js_1.CLIQUE_EXTRA_VANITY); } /** * Returns extra seal data * (only clique PoA, throws otherwise) */ cliqueExtraSeal() { this._requireClique('cliqueExtraSeal'); return this.extraData.subarray(-clique_js_1.CLIQUE_EXTRA_SEAL); } /** * Seal block with the provided signer. * Returns the final extraData field to be assigned to `this.extraData`. * @hidden */ cliqueSealBlock(privateKey) { this._requireClique('cliqueSealBlock'); const ecSignFunction = this.common.customCrypto?.ecsign ?? util_1.ecsign; const signature = ecSignFunction(this.cliqueSigHash(), privateKey); const signatureB = (0, util_1.concatBytes)(signature.r, signature.s, (0, util_1.bigIntToBytes)(signature.v - util_1.BIGINT_27)); const extraDataWithoutSeal = this.extraData.subarray(0, this.extraData.length - clique_js_1.CLIQUE_EXTRA_SEAL); const extraData = (0, util_1.concatBytes)(extraDataWithoutSeal, signatureB); return extraData; } /** * Returns a list of signers * (only clique PoA, throws otherwise) * * This function throws if not called on an epoch * transition block and should therefore be used * in conjunction with {@link BlockHeader.cliqueIsEpochTransition} */ cliqueEpochTransitionSigners() { this._requireClique('cliqueEpochTransitionSigners'); if (!this.cliqueIsEpochTransition()) { const msg = this._errorMsg('Signers are only included in epoch transition blocks (clique)'); throw new Error(msg); } const start = clique_js_1.CLIQUE_EXTRA_VANITY; const end = this.extraData.length - clique_js_1.CLIQUE_EXTRA_SEAL; const signerBytes = this.extraData.subarray(start, end); const signerList = []; const signerLength = 20; for (let start = 0; start <= signerBytes.length - signerLength; start += signerLength) { signerList.push(signerBytes.subarray(start, start + signerLength)); } return signerList.map((buf) => new util_1.Address(buf)); } /** * Verifies the signature of the block (last 65 bytes of extraData field) * (only clique PoA, throws otherwise) * * Method throws if signature is invalid */ cliqueVerifySignature(signerList) { this._requireClique('cliqueVerifySignature'); const signerAddress = this.cliqueSigner(); const signerFound = signerList.find((signer) => { return signer.equals(signerAddress); }); return !!signerFound; } /** * Returns the signer address */ cliqueSigner() { this._requireClique('cliqueSigner'); const extraSeal = this.cliqueExtraSeal(); // Reasonable default for default blocks if (extraSeal.length === 0 || (0, util_1.equalsBytes)(extraSeal, new Uint8Array(65))) { return util_1.Address.zero(); } const r = extraSeal.subarray(0, 32); const s = extraSeal.subarray(32, 64); const v = (0, util_1.bytesToBigInt)(extraSeal.subarray(64, 65)) + util_1.BIGINT_27; const pubKey = (0, util_1.ecrecover)(this.cliqueSigHash(), v, r, s); return util_1.Address.fromPublicKey(pubKey); } /** * Returns the rlp encoding of the block header. */ serialize() { return rlp_1.RLP.encode(this.raw()); } /** * Returns the block header in JSON format. */ toJSON() { const withdrawalAttr = this.withdrawalsRoot ? { withdrawalsRoot: (0, util_1.bytesToHex)(this.withdrawalsRoot) } : {}; const jsonDict = { parentHash: (0, util_1.bytesToHex)(this.parentHash), uncleHash: (0, util_1.bytesToHex)(this.uncleHash), coinbase: this.coinbase.toString(), stateRoot: (0, util_1.bytesToHex)(this.stateRoot), transactionsTrie: (0, util_1.bytesToHex)(this.transactionsTrie), ...withdrawalAttr, receiptTrie: (0, util_1.bytesToHex)(this.receiptTrie), logsBloom: (0, util_1.bytesToHex)(this.logsBloom), difficulty: (0, util_1.bigIntToHex)(this.difficulty), number: (0, util_1.bigIntToHex)(this.number), gasLimit: (0, util_1.bigIntToHex)(this.gasLimit), gasUsed: (0, util_1.bigIntToHex)(this.gasUsed), timestamp: (0, util_1.bigIntToHex)(this.timestamp), extraData: (0, util_1.bytesToHex)(this.extraData), mixHash: (0, util_1.bytesToHex)(this.mixHash), nonce: (0, util_1.bytesToHex)(this.nonce), }; if (this.common.isActivatedEIP(1559)) { jsonDict.baseFeePerGas = (0, util_1.bigIntToHex)(this.baseFeePerGas); } if (this.common.isActivatedEIP(4844)) { jsonDict.blobGasUsed = (0, util_1.bigIntToHex)(this.blobGasUsed); jsonDict.excessBlobGas = (0, util_1.bigIntToHex)(this.excessBlobGas); } if (this.common.isActivatedEIP(4788)) { jsonDict.parentBeaconBlockRoot = (0, util_1.bytesToHex)(this.parentBeaconBlockRoot); } if (this.common.isActivatedEIP(7685)) { jsonDict.requestsRoot = (0, util_1.bytesToHex)(this.requestsRoot); } return jsonDict; } /** * Validates extra data is DAO_ExtraData for DAO_ForceExtraDataRange blocks after DAO * activation block (see: https://blog.slock.it/hard-fork-specification-24b889e70703) */ _validateDAOExtraData() { if (!this.common.hardforkIsActiveOnBlock(common_1.Hardfork.Dao, this.number)) { return; } const DAOActivationBlock = this.common.hardforkBlock(common_1.Hardfork.Dao); if (DAOActivationBlock === null || this.number < DAOActivationBlock) { return; } const DAO_ExtraData = (0, util_1.hexToBytes)('0x64616f2d686172642d666f726b'); const DAO_ForceExtraDataRange = BigInt(9); const drift = this.number - DAOActivationBlock; if (drift <= DAO_ForceExtraDataRange && !(0, util_1.equalsBytes)(this.extraData, DAO_ExtraData)) { const msg = this._errorMsg(`extraData should be 'dao-hard-fork', got ${(0, util_1.bytesToUtf8)(this.extraData)} (hex: ${(0, util_1.bytesToHex)(this.extraData)})`); throw new Error(msg); } } /** * Return a compact error string representation of the object */ errorStr() { let hash = ''; try { hash = (0, util_1.bytesToHex)(this.hash()); } catch (e) { hash = 'error'; } let hf = ''; try { hf = this.common.hardfork(); } catch (e) { hf = 'error'; } let errorStr = `block header number=${this.number} hash=${hash} `; errorStr += `hf=${hf} baseFeePerGas=${this.baseFeePerGas ?? 'none'}`; return errorStr; } /** * Helper function to create an annotated error message * * @param msg Base error message * @hidden */ _errorMsg(msg) { return `${msg} (${this.errorStr()})`; } } exports.BlockHeader = BlockHeader; //# sourceMappingURL=header.js.map