@ethereumjs/block
Version:
Provides Block serialization and help functions
840 lines • 39.2 kB
JavaScript
"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