@ethereumjs/block
Version:
Provides Block serialization and help functions
311 lines • 13.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createBlockFromJSONRPCProvider = void 0;
exports.createBlock = createBlock;
exports.createEmptyBlock = createEmptyBlock;
exports.createBlockFromBytesArray = createBlockFromBytesArray;
exports.createBlockFromRLP = createBlockFromRLP;
exports.createBlockFromRPC = createBlockFromRPC;
exports.createBlockFromExecutionPayload = createBlockFromExecutionPayload;
exports.createBlockFromBeaconPayloadJSON = createBlockFromBeaconPayloadJSON;
exports.createSealedCliqueBlock = createSealedCliqueBlock;
const mpt_1 = require("@ethereumjs/mpt");
const rlp_1 = require("@ethereumjs/rlp");
const tx_1 = require("@ethereumjs/tx");
const util_1 = require("@ethereumjs/util");
const clique_ts_1 = require("../consensus/clique.js");
const helpers_ts_1 = require("../helpers.js");
const index_ts_1 = require("../index.js");
/**
* Static constructor to create a block from a block data dictionary
*
* @param blockData
* @param opts
*/
function createBlock(blockData = {}, opts) {
const { header: headerData, transactions: txsData, uncleHeaders: uhsData, withdrawals: withdrawalsData, executionWitness: executionWitnessData, } = blockData;
const header = (0, index_ts_1.createBlockHeader)(headerData, opts);
// parse transactions
const transactions = [];
for (const txData of txsData ?? []) {
const tx = (0, tx_1.createTx)(txData, {
...opts,
// Use header common in case of setHardfork being activated
common: header.common,
});
transactions.push(tx);
}
// parse uncle headers
const uncleHeaders = [];
const uncleOpts = {
...opts,
// Use header common in case of setHardfork being activated
common: header.common,
// Disable this option here (all other options carried over), since this overwrites the provided Difficulty to an incorrect value
calcDifficultyFromHeader: undefined,
};
// Uncles are obsolete post-merge, any hardfork by option implies setHardfork
if (opts?.setHardfork !== undefined) {
uncleOpts.setHardfork = true;
}
for (const uhData of uhsData ?? []) {
const uh = (0, index_ts_1.createBlockHeader)(uhData, uncleOpts);
uncleHeaders.push(uh);
}
const withdrawals = withdrawalsData?.map(util_1.createWithdrawal);
// The witness data is planned to come in rlp serialized bytes so leave this
// stub till that time
const executionWitness = executionWitnessData;
return new index_ts_1.Block(header, transactions, uncleHeaders, withdrawals, opts, executionWitness);
}
/**
* Simple static constructor if only an empty block is needed
* (tree shaking advantages since it does not draw all the tx constructors in)
*
* @param headerData
* @param opts
*/
function createEmptyBlock(headerData, opts) {
const header = (0, index_ts_1.createBlockHeader)(headerData, opts);
return new index_ts_1.Block(header);
}
/**
* Static constructor to create a block from an array of Bytes values
*
* @param values
* @param opts
*/
function createBlockFromBytesArray(values, opts) {
if (values.length > 5) {
throw (0, util_1.EthereumJSErrorWithoutCode)(`invalid More values=${values.length} than expected were received (at most 5)`);
}
// First try to load header so that we can use its common (in case of setHardfork being activated)
// to correctly make checks on the hardforks
const [headerData, txsData, uhsData, ...valuesTail] = values;
const header = (0, index_ts_1.createBlockHeaderFromBytesArray)(headerData, opts);
// conditional assignment of rest of values and splicing them out from the valuesTail
const withdrawalBytes = header.common.isActivatedEIP(4895)
? valuesTail.splice(0, 1)[0]
: undefined;
// if witness bytes are not present that we should assume that witness has not been provided
// in that scenario pass null as undefined is used for default witness assignment
const executionWitnessBytes = header.common.isActivatedEIP(6800)
? valuesTail.splice(0, 1)[0]
: null;
if (header.common.isActivatedEIP(4895) &&
(withdrawalBytes === undefined || !Array.isArray(withdrawalBytes))) {
throw (0, util_1.EthereumJSErrorWithoutCode)('Invalid serialized block input: EIP-4895 is active, and no withdrawals were provided as array');
}
if (header.common.isActivatedEIP(6800) && executionWitnessBytes === undefined) {
throw (0, util_1.EthereumJSErrorWithoutCode)('Invalid serialized block input: EIP-6800 is active, and execution witness is undefined');
}
// parse transactions
const transactions = [];
for (const txData of txsData ?? []) {
transactions.push((0, tx_1.createTxFromBlockBodyData)(txData, {
...opts,
// Use header common in case of setHardfork being activated
common: header.common,
}));
}
// parse uncle headers
const uncleHeaders = [];
const uncleOpts = {
...opts,
// Use header common in case of setHardfork being activated
common: header.common,
// Disable this option here (all other options carried over), since this overwrites the provided Difficulty to an incorrect value
calcDifficultyFromHeader: undefined,
};
// Uncles are obsolete post-merge, any hardfork by option implies setHardfork
if (opts?.setHardfork !== undefined) {
uncleOpts.setHardfork = true;
}
for (const uncleHeaderData of uhsData ?? []) {
uncleHeaders.push((0, index_ts_1.createBlockHeaderFromBytesArray)(uncleHeaderData, uncleOpts));
}
const withdrawals = withdrawalBytes
?.map(([index, validatorIndex, address, amount]) => ({
index,
validatorIndex,
address,
amount,
}))
?.map(util_1.createWithdrawal);
// executionWitness are not part of the EL fetched blocks via eth_ bodies method
// they are currently only available via the engine api constructed blocks
let executionWitness;
if (header.common.isActivatedEIP(6800)) {
if (executionWitnessBytes !== undefined) {
executionWitness = JSON.parse((0, util_1.bytesToUtf8)(rlp_1.RLP.decode(executionWitnessBytes)));
}
else if (opts?.executionWitness !== undefined) {
executionWitness = opts.executionWitness;
}
else {
// don't assign default witness if eip 6800 is implemented as it leads to incorrect
// assumptions while executing the if not present in input implies its unavailable
executionWitness = null;
}
}
return new index_ts_1.Block(header, transactions, uncleHeaders, withdrawals, opts, executionWitness);
}
/**
* Static constructor to create a block from a RLP-serialized block
*
* @param serialized
* @param opts
*/
function createBlockFromRLP(serialized, opts) {
const values = rlp_1.RLP.decode(Uint8Array.from(serialized));
if (!Array.isArray(values)) {
throw (0, util_1.EthereumJSErrorWithoutCode)('Invalid serialized block input. Must be array');
}
return createBlockFromBytesArray(values, opts);
}
/**
* Creates a new block object from Ethereum JSON RPC.
*
* @param blockParams - Ethereum JSON RPC of block (eth_getBlockByNumber)
* @param uncles - Optional list of Ethereum JSON RPC of uncles (eth_getUncleByBlockHashAndIndex)
* @param opts - An object describing the blockchain
*/
function createBlockFromRPC(blockParams, uncles = [], options) {
const header = (0, index_ts_1.createBlockHeaderFromRPC)(blockParams, options);
const transactions = [];
const opts = { common: header.common };
for (const _txParams of blockParams.transactions ?? []) {
const txParams = (0, tx_1.normalizeTxParams)(_txParams);
const tx = (0, tx_1.createTx)(txParams, opts);
transactions.push(tx);
}
const uncleHeaders = uncles.map((uh) => (0, index_ts_1.createBlockHeaderFromRPC)(uh, options));
return createBlock({ header, transactions, uncleHeaders, withdrawals: blockParams.withdrawals }, options);
}
/**
* Method to retrieve a block from a JSON-RPC provider and format as a {@link Block}
* @param provider either a url for a remote provider or an Ethers JSONRPCProvider object
* @param blockTag block hash or block number to be run
* @param opts {@link BlockOptions}
* @returns the block specified by `blockTag`
*/
const createBlockFromJSONRPCProvider = async (provider, blockTag, opts) => {
let blockData;
const providerUrl = (0, util_1.getProvider)(provider);
if (typeof blockTag === 'string' && blockTag.length === 66) {
blockData = await (0, util_1.fetchFromProvider)(providerUrl, {
method: 'eth_getBlockByHash',
params: [blockTag, true],
});
}
else if (typeof blockTag === 'bigint') {
blockData = await (0, util_1.fetchFromProvider)(providerUrl, {
method: 'eth_getBlockByNumber',
params: [(0, util_1.bigIntToHex)(blockTag), true],
});
}
else if ((0, util_1.isHexString)(blockTag) ||
blockTag === 'latest' ||
blockTag === 'earliest' ||
blockTag === 'pending' ||
blockTag === 'finalized' ||
blockTag === 'safe') {
blockData = await (0, util_1.fetchFromProvider)(providerUrl, {
method: 'eth_getBlockByNumber',
params: [blockTag, true],
});
}
else {
throw (0, util_1.EthereumJSErrorWithoutCode)(`expected blockTag to be block hash, bigint, hex prefixed string, or earliest/latest/pending; got ${blockTag}`);
}
if (blockData === null) {
throw (0, util_1.EthereumJSErrorWithoutCode)('No block data returned from provider');
}
const uncleHeaders = [];
if (blockData.uncles.length > 0) {
for (let x = 0; x < blockData.uncles.length; x++) {
const headerData = await (0, util_1.fetchFromProvider)(providerUrl, {
method: 'eth_getUncleByBlockHashAndIndex',
params: [blockData.hash, (0, util_1.intToHex)(x)],
});
uncleHeaders.push(headerData);
}
}
return createBlockFromRPC(blockData, uncleHeaders, opts);
};
exports.createBlockFromJSONRPCProvider = createBlockFromJSONRPCProvider;
/**
* Method to retrieve a block from an execution payload
* @param execution payload constructed from beacon payload
* @param opts {@link BlockOptions}
* @returns the block constructed block
*/
async function createBlockFromExecutionPayload(payload, opts) {
const { blockNumber: number, receiptsRoot: receiptTrie, prevRandao: mixHash, feeRecipient: coinbase, transactions, withdrawals: withdrawalsData, executionWitness, } = payload;
const txs = [];
for (const [index, serializedTx] of transactions.entries()) {
try {
const tx = (0, tx_1.createTxFromRLP)((0, util_1.hexToBytes)(serializedTx), {
common: opts?.common,
});
txs.push(tx);
}
catch (error) {
const validationError = `Invalid tx at index ${index}: ${error}`;
throw validationError;
}
}
const transactionsTrie = await (0, helpers_ts_1.genTransactionsTrieRoot)(txs, new mpt_1.MerklePatriciaTrie({ common: opts?.common }));
const withdrawals = withdrawalsData?.map((wData) => (0, util_1.createWithdrawal)(wData));
const withdrawalsRoot = withdrawals
? await (0, helpers_ts_1.genWithdrawalsTrieRoot)(withdrawals, new mpt_1.MerklePatriciaTrie({ common: opts?.common }))
: undefined;
const header = {
...payload,
number,
receiptTrie,
transactionsTrie,
withdrawalsRoot,
mixHash,
coinbase,
};
// we are not setting setHardfork as common is already set to the correct hf
const block = createBlock({ header, transactions: txs, withdrawals, executionWitness }, opts);
if (block.common.isActivatedEIP(6800) &&
(executionWitness === undefined || executionWitness === null)) {
throw Error('Missing executionWitness for EIP-6800 activated executionPayload');
}
// Verify blockHash matches payload
if (!(0, util_1.equalsBytes)(block.hash(), (0, util_1.hexToBytes)(payload.blockHash))) {
const validationError = `Invalid blockHash, expected: ${payload.blockHash}, received: ${(0, util_1.bytesToHex)(block.hash())}`;
throw Error(validationError);
}
return block;
}
/**
* Method to retrieve a block from a beacon payload JSON
* @param payload JSON of a beacon beacon fetched from beacon apis
* @param opts {@link BlockOptions}
* @returns the block constructed block
*/
async function createBlockFromBeaconPayloadJSON(payload, opts) {
const executionPayload = (0, index_ts_1.executionPayloadFromBeaconPayload)(payload);
return createBlockFromExecutionPayload(executionPayload, opts);
}
function createSealedCliqueBlock(blockData = {}, cliqueSigner, opts = {}) {
const sealedCliqueBlock = createBlock(blockData, {
...opts,
...{ freeze: false, skipConsensusFormatValidation: true },
});
sealedCliqueBlock.header.extraData = (0, clique_ts_1.generateCliqueBlockExtraData)(sealedCliqueBlock.header, cliqueSigner);
if (opts?.freeze === true) {
// We have to freeze here since we can't freeze the block when constructing it since we are overwriting `extraData`
Object.freeze(sealedCliqueBlock);
}
if (opts?.skipConsensusFormatValidation === false) {
// We need to validate the consensus format here since we skipped it when constructing the block
sealedCliqueBlock.header['_consensusFormatValidation']();
}
return sealedCliqueBlock;
}
//# sourceMappingURL=constructors.js.map