diff --git a/packages/block/src/block.ts b/packages/block/src/block.ts index fef9fa8ece9..ed0bb2d98ed 100644 --- a/packages/block/src/block.ts +++ b/packages/block/src/block.ts @@ -20,7 +20,6 @@ import { keccak256 } from 'ethereum-cryptography/keccak' import { executionPayloadFromBeaconPayload } from './from-beacon-payload' import { blockFromRpc } from './from-rpc' import { BlockHeader } from './header' -import { calcExcessDataGas, getDataGasPrice } from './helpers' import type { BeaconPayloadJson } from './from-beacon-payload' import type { @@ -480,7 +479,7 @@ export class Block { validateTransactions(stringError: true): string[] validateTransactions(stringError = false) { const errors: string[] = [] - let blockDataGas = BigInt(0) + let dataGasUsed = BigInt(0) const dataGasLimit = this._common.param('gasConfig', 'maxDataGasPerBlock') const dataGasPerBlob = this._common.param('gasConfig', 'dataGasPerBlob') @@ -502,10 +501,10 @@ export class Block { } if (this._common.isActivatedEIP(4844) === true) { if (tx instanceof BlobEIP4844Transaction) { - blockDataGas += BigInt(tx.numBlobs()) * dataGasPerBlob - if (blockDataGas > dataGasLimit) { + dataGasUsed += BigInt(tx.numBlobs()) * dataGasPerBlob + if (dataGasUsed > dataGasLimit) { errs.push( - `tx causes total data gas of ${blockDataGas} to exceed maximum data gas per block of ${dataGasLimit}` + `tx causes total data gas of ${dataGasUsed} to exceed maximum data gas per block of ${dataGasLimit}` ) } } @@ -515,6 +514,12 @@ export class Block { } } + if (this._common.isActivatedEIP(4844) === true) { + if (dataGasUsed !== this.header.dataGasUsed) { + errors.push(`invalid dataGasUsed expected=${this.header.dataGasUsed} actual=${dataGasUsed}`) + } + } + return stringError ? errors : errors.length === 0 } @@ -563,10 +568,13 @@ export class Block { */ validateBlobTransactions(parentHeader: BlockHeader) { if (this._common.isActivatedEIP(4844)) { - let numBlobs = 0 + const dataGasLimit = this._common.param('gasConfig', 'maxDataGasPerBlock') + const dataGasPerBlob = this._common.param('gasConfig', 'dataGasPerBlob') + let dataGasUsed = BigInt(0) + for (const tx of this.transactions) { if (tx instanceof BlobEIP4844Transaction) { - const dataGasPrice = getDataGasPrice(parentHeader) + const dataGasPrice = this.header.getDataGasPrice() if (tx.maxFeePerDataGas < dataGasPrice) { throw new Error( `blob transaction maxFeePerDataGas ${ @@ -574,10 +582,24 @@ export class Block { } < than block data gas price ${dataGasPrice} - ${this.errorStr()}` ) } - numBlobs += tx.versionedHashes.length + + dataGasUsed += BigInt(tx.versionedHashes.length) * dataGasPerBlob + + if (dataGasUsed > dataGasLimit) { + throw new Error( + `tx causes total data gas of ${dataGasUsed} to exceed maximum data gas per block of ${dataGasLimit}` + ) + } } } - const expectedExcessDataGas = calcExcessDataGas(parentHeader, numBlobs) + + if (this.header.dataGasUsed !== dataGasUsed) { + throw new Error( + `block dataGasUsed mismatch: have ${this.header.dataGasUsed}, want ${dataGasUsed}` + ) + } + + const expectedExcessDataGas = parentHeader.calcNextExcessDataGas() if (this.header.excessDataGas !== expectedExcessDataGas) { throw new Error( `block excessDataGas mismatch: have ${this.header.excessDataGas}, want ${expectedExcessDataGas}` diff --git a/packages/block/src/from-beacon-payload.ts b/packages/block/src/from-beacon-payload.ts index da67f8e7555..6577c1d2ea8 100644 --- a/packages/block/src/from-beacon-payload.ts +++ b/packages/block/src/from-beacon-payload.ts @@ -27,6 +27,7 @@ export type BeaconPayloadJson = { block_hash: string transactions: string[] withdrawals?: BeaconWithdrawal[] + data_gas_used?: string excess_data_gas?: string } @@ -61,6 +62,9 @@ export function executionPayloadFromBeaconPayload(payload: BeaconPayloadJson): E })) } + if (payload.data_gas_used !== undefined && payload.data_gas_used !== null) { + executionPayload.dataGasUsed = bigIntToHex(BigInt(payload.data_gas_used)) + } if (payload.excess_data_gas !== undefined && payload.excess_data_gas !== null) { executionPayload.excessDataGas = bigIntToHex(BigInt(payload.excess_data_gas)) } diff --git a/packages/block/src/header-from-rpc.ts b/packages/block/src/header-from-rpc.ts index 0bd804ad8c1..1afb853b7b9 100644 --- a/packages/block/src/header-from-rpc.ts +++ b/packages/block/src/header-from-rpc.ts @@ -28,6 +28,8 @@ export function blockHeaderFromRpc(blockParams: JsonRpcBlock, options?: BlockOpt nonce, baseFeePerGas, withdrawalsRoot, + dataGasUsed, + excessDataGas, } = blockParams const blockHeader = BlockHeader.fromHeaderData( @@ -49,6 +51,8 @@ export function blockHeaderFromRpc(blockParams: JsonRpcBlock, options?: BlockOpt nonce, baseFeePerGas, withdrawalsRoot, + dataGasUsed, + excessDataGas, }, options ) diff --git a/packages/block/src/header.ts b/packages/block/src/header.ts index 35905e0960e..8e93e33d159 100644 --- a/packages/block/src/header.ts +++ b/packages/block/src/header.ts @@ -23,7 +23,7 @@ import { keccak256 } from 'ethereum-cryptography/keccak' import { hexToBytes } from 'ethereum-cryptography/utils' import { CLIQUE_EXTRA_SEAL, CLIQUE_EXTRA_VANITY } from './clique' -import { valuesArrayToHeaderData } from './helpers' +import { fakeExponential, valuesArrayToHeaderData } from './helpers' import type { BlockHeaderBytes, BlockOptions, HeaderData, JsonHeader } from './types' import type { CliqueConfig } from '@ethereumjs/common' @@ -55,6 +55,7 @@ export class BlockHeader { public readonly nonce: Uint8Array public readonly baseFeePerGas?: bigint public readonly withdrawalsRoot?: Uint8Array + public readonly dataGasUsed?: bigint public readonly excessDataGas?: bigint public readonly _common: Common @@ -194,6 +195,7 @@ export class BlockHeader { : BigInt(7) : undefined, withdrawalsRoot: this._common.isActivatedEIP(4895) ? KECCAK256_RLP : undefined, + dataGasUsed: this._common.isActivatedEIP(4844) ? BigInt(0) : undefined, excessDataGas: this._common.isActivatedEIP(4844) ? BigInt(0) : undefined, } @@ -201,6 +203,8 @@ export class BlockHeader { toType(headerData.baseFeePerGas, TypeOutput.BigInt) ?? hardforkDefaults.baseFeePerGas const withdrawalsRoot = toType(headerData.withdrawalsRoot, TypeOutput.Uint8Array) ?? hardforkDefaults.withdrawalsRoot + const dataGasUsed = + toType(headerData.dataGasUsed, TypeOutput.BigInt) ?? hardforkDefaults.dataGasUsed const excessDataGas = toType(headerData.excessDataGas, TypeOutput.BigInt) ?? hardforkDefaults.excessDataGas @@ -214,8 +218,14 @@ export class BlockHeader { ) } - if (!this._common.isActivatedEIP(4844) && headerData.excessDataGas !== undefined) { - throw new Error('excess data gas can only be provided with EIP4844 activated') + if (!this._common.isActivatedEIP(4844)) { + if (headerData.dataGasUsed !== undefined) { + throw new Error('data gas used can only be provided with EIP4844 activated') + } + + if (headerData.excessDataGas !== undefined) { + throw new Error('excess data gas can only be provided with EIP4844 activated') + } } this.parentHash = parentHash @@ -235,6 +245,7 @@ export class BlockHeader { this.nonce = nonce this.baseFeePerGas = baseFeePerGas this.withdrawalsRoot = withdrawalsRoot + this.dataGasUsed = dataGasUsed this.excessDataGas = excessDataGas this._genericFormatValidation() this._validateDAOExtraData() @@ -523,6 +534,50 @@ export class BlockHeader { return nextBaseFee } + /** + * Returns the price per unit of data gas for a blob transaction in the current/pending block + * @returns the price in gwei per unit of data gas spent + */ + getDataGasPrice(): bigint { + if (this.excessDataGas === undefined) { + throw new Error('header must have excessDataGas field populated') + } + return fakeExponential( + this._common.param('gasPrices', 'minDataGasPrice'), + this.excessDataGas, + this._common.param('gasConfig', 'dataGasPriceUpdateFraction') + ) + } + + /** + * Returns the total fee for data gas spent for including blobs in block. + * + * @param numBlobs number of blobs in the transaction/block + * @returns the total data gas fee for numBlobs blobs + */ + calcDataFee(numBlobs: number): bigint { + const dataGasPerBlob = this._common.param('gasConfig', 'dataGasPerBlob') + const dataGasUsed = dataGasPerBlob * BigInt(numBlobs) + + const dataGasPrice = this.getDataGasPrice() + return dataGasUsed * dataGasPrice + } + + /** + * Calculates the excess data gas for next (hopefully) post EIP 4844 block. + */ + public calcNextExcessDataGas(): bigint { + // The validation of the fields and 4844 activation is already taken care in BlockHeader constructor + const targetGasConsumed = (this.excessDataGas ?? BigInt(0)) + (this.dataGasUsed ?? BigInt(0)) + const targetDataGasPerBlock = this._common.param('gasConfig', 'targetDataGasPerBlock') + + if (targetGasConsumed <= targetDataGasPerBlock) { + return BigInt(0) + } else { + return targetGasConsumed - targetDataGasPerBlock + } + } + /** * Returns a Uint8Array Array of the raw Bytes in this header, in order. */ @@ -553,6 +608,7 @@ export class BlockHeader { rawItems.push(this.withdrawalsRoot!) } if (this._common.isActivatedEIP(4844) === true) { + rawItems.push(bigIntToUnpaddedBytes(this.dataGasUsed!)) rawItems.push(bigIntToUnpaddedBytes(this.excessDataGas!)) } @@ -822,6 +878,7 @@ export class BlockHeader { jsonDict.baseFeePerGas = bigIntToHex(this.baseFeePerGas!) } if (this._common.isActivatedEIP(4844) === true) { + jsonDict.dataGasUsed = bigIntToHex(this.dataGasUsed!) jsonDict.excessDataGas = bigIntToHex(this.excessDataGas!) } return jsonDict diff --git a/packages/block/src/helpers.ts b/packages/block/src/helpers.ts index 52a16a9aaae..c6540738e15 100644 --- a/packages/block/src/helpers.ts +++ b/packages/block/src/helpers.ts @@ -1,7 +1,6 @@ import { BlobEIP4844Transaction } from '@ethereumjs/tx' import { TypeOutput, isHexString, toType } from '@ethereumjs/util' -import type { BlockHeader } from './header' import type { BlockHeaderBytes, HeaderData } from './types' import type { TypedTransaction } from '@ethereumjs/tx' @@ -41,10 +40,11 @@ export function valuesArrayToHeaderData(values: BlockHeaderBytes): HeaderData { nonce, baseFeePerGas, withdrawalsRoot, + dataGasUsed, excessDataGas, ] = values - if (values.length > 18) { + if (values.length > 19) { throw new Error('invalid header. More values than expected were received') } if (values.length < 15) { @@ -69,6 +69,7 @@ export function valuesArrayToHeaderData(values: BlockHeaderBytes): HeaderData { nonce, baseFeePerGas, withdrawalsRoot, + dataGasUsed, excessDataGas, } } @@ -81,34 +82,6 @@ export function getDifficulty(headerData: HeaderData): bigint | null { return null } -/** - * Calculates the excess data gas for a post EIP 4844 block given the parent block header. - * @param parent header for the parent block - * @param newBlobs number of blobs contained in block - * @returns the excess data gas for the prospective next block - * - * Note: This function expects that it is only being called on a valid block as it does not have - * access to the "current" block's common instance to verify if 4844 is active or not. - */ -export const calcExcessDataGas = (parent: BlockHeader, newBlobs: number) => { - if (!parent._common.isActivatedEIP(4844)) { - // If 4844 isn't active on header, assume this is the first post-fork block so excess data gas is 0 - return BigInt(0) - } - if (parent.excessDataGas === undefined) { - // Given 4844 is active on parent block, we expect it to have an excessDataGas field - throw new Error('parent header does not contain excessDataGas field') - } - - const consumedDataGas = BigInt(newBlobs) * parent._common.param('gasConfig', 'dataGasPerBlob') - const targetDataGasPerBlock = parent._common.param('gasConfig', 'targetDataGasPerBlock') - - if (parent.excessDataGas + consumedDataGas < targetDataGasPerBlock) return BigInt(0) - else { - return parent.excessDataGas + consumedDataGas - targetDataGasPerBlock - } -} - export const getNumBlobs = (transactions: TypedTransaction[]) => { let numBlobs = 0 for (const tx of transactions) { @@ -134,34 +107,3 @@ export const fakeExponential = (factor: bigint, numerator: bigint, denominator: return output / denominator } - -/** - * Returns the price per unit of data gas for a blob transaction in the current/pending block - * @param header the parent header for the current block (or current head of the chain) - * @returns the price in gwei per unit of data gas spent - */ -export const getDataGasPrice = (header: BlockHeader) => { - if (header.excessDataGas === undefined) { - throw new Error('parent header must have excessDataGas field populated') - } - return fakeExponential( - header._common.param('gasPrices', 'minDataGasPrice'), - header.excessDataGas, - header._common.param('gasConfig', 'dataGasPriceUpdateFraction') - ) -} - -/** - * Returns the total fee for data gas spent on `numBlobs` in the current/pending block - * @param numBlobs - * @param parent parent header of the current/pending block - * @returns the total data gas fee for a transaction assuming it contains `numBlobs` - */ -export const calcDataFee = (numBlobs: number, parent: BlockHeader) => { - if (parent.excessDataGas === undefined) { - throw new Error('parent header must have excessDataGas field populated') - } - const totalDataGas = parent._common.param('gasConfig', 'dataGasPerBlob') * BigInt(numBlobs) - const dataGasPrice = getDataGasPrice(parent) - return totalDataGas * dataGasPrice -} diff --git a/packages/block/src/index.ts b/packages/block/src/index.ts index 37b5cd9d17f..d5d8884435d 100644 --- a/packages/block/src/index.ts +++ b/packages/block/src/index.ts @@ -1,10 +1,4 @@ export { Block } from './block' export { BlockHeader } from './header' -export { - calcDataFee, - calcExcessDataGas, - getDataGasPrice, - getDifficulty, - valuesArrayToHeaderData, -} from './helpers' +export { getDifficulty, valuesArrayToHeaderData } from './helpers' export * from './types' diff --git a/packages/block/src/types.ts b/packages/block/src/types.ts index 8f176d5e0c3..ff881b05cae 100644 --- a/packages/block/src/types.ts +++ b/packages/block/src/types.ts @@ -104,6 +104,7 @@ export interface HeaderData { nonce?: BytesLike baseFeePerGas?: BigIntLike withdrawalsRoot?: BytesLike + dataGasUsed?: BigIntLike excessDataGas?: BigIntLike } @@ -167,6 +168,7 @@ export interface JsonHeader { nonce?: string baseFeePerGas?: string withdrawalsRoot?: string + dataGasUsed?: string excessDataGas?: string } @@ -197,6 +199,7 @@ export interface JsonRpcBlock { baseFeePerGas?: string // If EIP-1559 is enabled for this block, returns the base fee per gas withdrawals?: Array // If EIP-4895 is enabled for this block, array of withdrawals withdrawalsRoot?: string // If EIP-4895 is enabled for this block, the root of the withdrawal trie of the block. + dataGasUsed?: string // If EIP-4844 is enabled for this block, returns the data gas used for the block excessDataGas?: string // If EIP-4844 is enabled for this block, returns the excess data gas for the block } @@ -223,5 +226,6 @@ export type ExecutionPayload = { blockHash: string // DATA, 32 Bytes transactions: string[] // Array of DATA - Array of transaction rlp strings, withdrawals?: WithdrawalV1[] // Array of withdrawal objects - excessDataGas?: string // QUANTITY, 256 Bits + dataGasUsed?: string // QUANTITY, 64 Bits + excessDataGas?: string // QUANTITY, 64 Bits } diff --git a/packages/block/test/eip4844block.spec.ts b/packages/block/test/eip4844block.spec.ts index 2ff9b51884c..343d6f7dbdd 100644 --- a/packages/block/test/eip4844block.spec.ts +++ b/packages/block/test/eip4844block.spec.ts @@ -10,27 +10,34 @@ import { import * as kzg from 'c-kzg' import * as tape from 'tape' -import { Block, calcExcessDataGas, getDataGasPrice } from '../src' +import { Block } from '../src' import { BlockHeader } from '../src/header' -import { calcDataFee, fakeExponential, getNumBlobs } from '../src/helpers' +import { fakeExponential, getNumBlobs } from '../src/helpers' import type { TypedTransaction } from '@ethereumjs/tx' // Hack to detect if running in browser or not const isBrowser = new Function('try {return this===window;}catch(e){ return false;}') -if (isBrowser() === false) initKZG(kzg, __dirname + '/../../client/src/trustedSetups/devnet4.txt') +if (isBrowser() === false) { + try { + initKZG(kzg, __dirname + '/../../client/src/trustedSetups/devnet4.txt') + // eslint-disable-next-line + } catch {} +} const gethGenesis = require('./testdata/4844-hardfork.json') const common = Common.fromGethGenesis(gethGenesis, { chain: 'customChain', hardfork: Hardfork.Cancun, }) +const dataGasPerBlob = common.param('gasConfig', 'dataGasPerBlob') tape('EIP4844 header tests', function (t) { if (isBrowser() === true) { t.end() } else { const earlyCommon = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Istanbul }) + t.throws( () => { BlockHeader.fromHeaderData( @@ -49,6 +56,26 @@ tape('EIP4844 header tests', function (t) { }, 'should throw when setting excessDataGas with EIP4844 not being activated' ) + + t.throws( + () => { + BlockHeader.fromHeaderData( + { + dataGasUsed: 1n, + }, + { + common: earlyCommon, + } + ) + }, + (err: any) => { + return ( + err.message.toString() === 'data gas used can only be provided with EIP4844 activated' + ) + }, + 'should throw when setting dataGasUsed with EIP4844 not being activated' + ) + const excessDataGas = BlockHeader.fromHeaderData( {}, { common, skipConsensusFormatValidation: true } @@ -87,26 +114,12 @@ tape('data gas tests', async (t) => { } else { const preShardingHeader = BlockHeader.fromHeaderData({}) - let excessDataGas = calcExcessDataGas(preShardingHeader, 2) - t.equals( - excessDataGas, - 0n, - 'excess data gas where 4844 is not active on parent header should be 0' - ) - - t.throws( - () => getDataGasPrice(preShardingHeader), - (err: any) => err.message.includes('parent header must have excessDataGas field'), - 'getDataGasPrice throws when header has no excessDataGas field' - ) + let excessDataGas = preShardingHeader.calcNextExcessDataGas() + t.equals(excessDataGas, 0n, 'excess data gas where 4844 is not active on header should be 0') t.throws( - () => - calcDataFee( - BlobEIP4844Transaction.fromTxData({}, { common }).numBlobs(), - preShardingHeader - ), - (err: any) => err.message.includes('parent header must have excessDataGas field'), + () => preShardingHeader.calcDataFee(1), + (err: any) => err.message.includes('header must have excessDataGas field'), 'calcDataFee throws when header has no excessDataGas field' ) @@ -115,41 +128,21 @@ tape('data gas tests', async (t) => { { common, skipConsensusFormatValidation: true } ) - excessDataGas = calcExcessDataGas(lowGasHeader, 1) - let dataGasPrice = getDataGasPrice(lowGasHeader) + excessDataGas = lowGasHeader.calcNextExcessDataGas() + let dataGasPrice = lowGasHeader.getDataGasPrice() t.equal(excessDataGas, 0n, 'excess data gas should be 0 for small parent header data gas') t.equal(dataGasPrice, 1n, 'data gas price should be 1n when low or no excess data gas') const highGasHeader = BlockHeader.fromHeaderData( - { number: 1, excessDataGas: 4194304 }, + { number: 1, excessDataGas: 4194304, dataGasUsed: BigInt(4) * dataGasPerBlob }, { common, skipConsensusFormatValidation: true } ) - excessDataGas = calcExcessDataGas(highGasHeader, 4) - dataGasPrice = getDataGasPrice(highGasHeader) + excessDataGas = highGasHeader.calcNextExcessDataGas() + dataGasPrice = highGasHeader.getDataGasPrice() t.equal(excessDataGas, 4456448n) t.equal(dataGasPrice, 6n, 'computed correct data gas price') - const blobs = getBlobs('hello world') - const commitments = blobsToCommitments(blobs) - const versionedHashes = commitmentsToVersionedHashes(commitments) - - const unsignedTx = BlobEIP4844Transaction.fromTxData( - { - versionedHashes, - blobs, - kzgCommitments: commitments, - maxFeePerDataGas: 100000000n, - gasLimit: 0xffffffn, - to: randomBytes(20), - }, - { common } - ) - - t.equal(calcDataFee(unsignedTx.numBlobs(), lowGasHeader), 131072n, 'compute data fee correctly') - t.equal( - calcDataFee(unsignedTx.numBlobs(), highGasHeader), - 786432n, - 'compute data fee correctly' - ) + t.equal(lowGasHeader.calcDataFee(1), 131072n, 'compute data fee correctly') + t.equal(highGasHeader.calcDataFee(4), 3145728n, 'compute data fee correctly') t.end() } }) @@ -186,22 +179,29 @@ tape('transaction validation tests', async (t) => { ).sign(randomBytes(32)) const parentHeader = BlockHeader.fromHeaderData( - { number: 1n, excessDataGas: 4194304 }, + { number: 1n, excessDataGas: 4194304, dataGasUsed: 0 }, { common, skipConsensusFormatValidation: true } ) + const excessDataGas = parentHeader.calcNextExcessDataGas() // eslint-disable-next-line no-inner-declarations function getBlock(transactions: TypedTransaction[]) { const blobs = getNumBlobs(transactions) - const excessDataGas = calcExcessDataGas(parentHeader, blobs) + const blockHeader = BlockHeader.fromHeaderData( - { number: 2n, parentHash: parentHeader.hash(), excessDataGas }, + { + number: 2n, + parentHash: parentHeader.hash(), + excessDataGas, + dataGasUsed: BigInt(blobs) * dataGasPerBlob, + }, { common, skipConsensusFormatValidation: true } ) - return Block.fromBlockData( + const block = Block.fromBlockData( { header: blockHeader, transactions }, { common, skipConsensusFormatValidation: true } ) + return block } const blockWithValidTx = getBlock([tx1]) @@ -214,11 +214,30 @@ tape('transaction validation tests', async (t) => { () => blockWithValidTx.validateBlobTransactions(parentHeader), 'does not throw when all tx maxFeePerDataGas are >= to block data gas fee' ) + const blockJson = blockWithValidTx.toJSON() + blockJson.header!.dataGasUsed = '0x0' + const blockWithInvalidHeader = Block.fromBlockData(blockJson, { common }) + t.throws( + () => blockWithInvalidHeader.validateBlobTransactions(parentHeader), + (err: any) => err.message.includes('block dataGasUsed mismatch'), + 'throws with correct error message when tx maxFeePerDataGas less than block data gas fee' + ) + + t.throws( + () => blockWithInvalidTx.validateBlobTransactions(parentHeader), + (err: any) => err.message.includes('than block data gas price'), + 'throws with correct error message when tx maxFeePerDataGas less than block data gas fee' + ) t.throws( () => blockWithInvalidTx.validateBlobTransactions(parentHeader), (err: any) => err.message.includes('than block data gas price'), 'throws with correct error message when tx maxFeePerDataGas less than block data gas fee' ) + t.throws( + () => blockWithTooManyBlobs.validateBlobTransactions(parentHeader), + (err: any) => err.message.includes('exceed maximum data gas per block'), + 'throws with correct error message when tx maxFeePerDataGas less than block data gas fee' + ) t.ok( blockWithTooManyBlobs diff --git a/packages/block/test/from-beacon-payload.spec.ts b/packages/block/test/from-beacon-payload.spec.ts index ce7dad7f65d..397916aa423 100644 --- a/packages/block/test/from-beacon-payload.spec.ts +++ b/packages/block/test/from-beacon-payload.spec.ts @@ -1,7 +1,7 @@ import { Common, Hardfork } from '@ethereumjs/common' import * as tape from 'tape' -import { Block } from '../src/index' +import { Block, BlockHeader } from '../src/index' import * as payload87335 from './testdata/payload-slot-87335.json' import * as payload87475 from './testdata/payload-slot-87475.json' @@ -20,7 +20,11 @@ tape('[fromExecutionPayloadJson]: 4844 devnet 5', async function (t) { for (const payload of [payload87335, payload87475]) { try { const block = await Block.fromBeaconPayloadJson(payload, { common }) - block.validateBlobTransactions({ excessDataGas: BigInt(0), _common: common } as any) + const parentHeader = BlockHeader.fromHeaderData( + { excessDataGas: BigInt(0), dataGasUsed: block.header.excessDataGas! + BigInt(262144) }, + { common } + ) + block.validateBlobTransactions(parentHeader) st.pass(`successfully constructed block=${block.header.number}`) } catch (e) { st.fail(`failed to construct block, error: ${e}`) @@ -49,12 +53,12 @@ tape('[fromExecutionPayloadJson]: 4844 devnet 5', async function (t) { const block = await Block.fromBeaconPayloadJson( { ...payload87475, - excess_data_gas: payload87335.excess_data_gas, - block_hash: '0x506ff15910ef7c5a713b21f225b3ffb4bfb4aeea4ea4891e3a71c0ad7bf6a8e0', + block_hash: '0x5be157c3b687537d20a252ad072e8d6a458108eb1b95944368f5a8f8f3325b07', }, { common } ) - block.validateBlobTransactions({ excessDataGas: BigInt(0), _common: common } as any) + const parentHeader = BlockHeader.fromHeaderData({ excessDataGas: BigInt(0) }, { common }) + block.validateBlobTransactions(parentHeader) st.fail(`should have failed constructing the block`) } catch (e) { st.pass(`correctly failed constructing block, error: ${e}`) diff --git a/packages/block/test/header.spec.ts b/packages/block/test/header.spec.ts index c15e653f694..9492f87e623 100644 --- a/packages/block/test/header.spec.ts +++ b/packages/block/test/header.spec.ts @@ -153,7 +153,7 @@ tape('[Block]: Header functions', function (t) { }) t.test('Initialization -> fromValuesArray() -> error cases', function (st) { - const headerArray = Array(19).fill(new Uint8Array(0)) + const headerArray = Array(20).fill(new Uint8Array(0)) // mock header data (if set to zeros(0) header throws) headerArray[0] = zeros(32) //parentHash diff --git a/packages/block/test/testdata/payload-slot-87335.json b/packages/block/test/testdata/payload-slot-87335.json index e2147f3d6e3..1ec85a19269 100644 --- a/packages/block/test/testdata/payload-slot-87335.json +++ b/packages/block/test/testdata/payload-slot-87335.json @@ -11,7 +11,7 @@ "timestamp": "1683551220", "extra_data": "0x4e65746865726d696e64", "base_fee_per_gas": "7", - "block_hash": "0x56351338f4a1531e2b591693bdd1d028deee00f7850396677560eafaa6017e30", + "block_hash": "0xc8f941ddbc909b59693e98a786eadc2c695fc49de778ea1150ac092945956b74", "transactions": [ "0x03f89d850120b996ed3685012a1a646085012a1a64608303345094ffb38a7a99e3e2335be83fc74b7faa19d55312418308a80280c085012a1a6460e1a00153a6a1e053cf4c5a09e84088ed8ad7cb53d76c8168f1b82f7cfebfcd06da1a01a007785223eec68459d72265f10bdb30ec3415252a63100605a03142fa211ebbe9a07dbbf9e081fa7b9a01202e4d9ee0e0e513f80efbbab6c784635429905389ce86", "0x03f889850120b996ed81f0847735940084b2d05e158307a1208001855f495f4955c084b2d05e15e1a001d343d3cd62abd9c5754cbe5128c25ea90786a8ae75fb79c8cf95f4dcdd08ec80a014103732b5a9789bbf5ea859ed904155398abbef343f8fd63007efb70795d382a07272e847382789a092eadf08e2b9002e727376f8466fff0e4d4639fd60a528f2", @@ -19,5 +19,6 @@ "0x03f897850120b996ed80840bebc200843b9aca078303345094c8d369b164361a8961286cfbab3bc10f962185a88080c08411e1a300e1a0011df88a2971c8a7ac494a7ba37ec1acaa1fc1edeeb38c839b5d1693d47b69b080a032f122f06e5802224db4c8a58fd22c75173a713f63f89936f811c144b9e40129a043a2a872cbfa5727007adf6a48febe5f190d2e4cd5ed6122823fb6ff47ecda32" ], "withdrawals": [], - "excess_data_gas": "262144" + "excess_data_gas": "262144", + "data_gas_used": "524288" } diff --git a/packages/block/test/testdata/payload-slot-87475.json b/packages/block/test/testdata/payload-slot-87475.json index 51f24c3b5cc..116f121c663 100644 --- a/packages/block/test/testdata/payload-slot-87475.json +++ b/packages/block/test/testdata/payload-slot-87475.json @@ -11,7 +11,7 @@ "timestamp": "1683552900", "extra_data": "0x4e65746865726d696e64", "base_fee_per_gas": "9", - "block_hash": "0xd1ede53dab0cf183787351c90d4077a6e8a9b759781b2e7500a4fb607342892f", + "block_hash": "0x5be157c3b687537d20a252ad072e8d6a458108eb1b95944368f5a8f8f3325b07", "transactions": [ "0x02f8f3850120b996ed1a85012a1645a985012a1645b0830186a0948e4fc0f136ba56a151fd1530d4d6695df6a0fb4a80b880d08cc67f792879a8e1d0d5569a51af02f456f410ad93b1de0c038b667b7c5577898a95d960f0527fb7298b05f6a2c83ed6f508eee540edb1248b9235bb26fa566927967d32652b88610110527fe29d11468a1e028eedc6143170491b87a32609c236cb7068ebb1799c616c393061013052604a61015053600d6101515360b661c001a0f49f3f2b301cc72120fd1dcb1fe218012623035f5824d17343e802a4741c4e60a02a40bf4d7a5d52c8f554b04c635a898650584c5452be62a5304e50ca59e4d55f", "0x03f8dc850120b996ed04840bebc200843b9aca0783033450948a185c5a7941b20253a2923690dd54a9e7bfd0a980b844a8d55bda000000000000000000000000573d9cd570267bb9d1547192e51e5c8d017d70340000000000000000000000000000000000000000000000000000000000000000c08411e1a300e1a001a974d0ba16f7a378867bb0dd359d78fdf831a2b73823a8a1eea34e6615cb9980a02860847930ba2d79763f1c9196ad364dc7bc28f633d16cb1715a12b27a6db735a02d0434073f7d217a035e484dd7e6513bfe64035b16b06478113a07c58c79ffa9", @@ -19,5 +19,6 @@ "0x03f8dc850120b996ed06841dcd6500843b9aca0783033450948a185c5a7941b20253a2923690dd54a9e7bfd0a980b844a8d55bda000000000000000000000000573d9cd570267bb9d1547192e51e5c8d017d70340000000000000000000000000000000000000000000000000000000000000000c08411e1a300e1a0011df88a2971c8a7ac494a7ba37ec1acaa1fc1edeeb38c839b5d1693d47b69b001a0f6e51ba86726dad6fe6e07f97b90c94ab79251ba2b1f900cb794dcc513d65952a02babf887931c21d86caf54a044803109023041bb9db1e1d5884b06ae52881793" ], "withdrawals": [], - "excess_data_gas": "131072" + "excess_data_gas": "131072", + "data_gas_used": "393216" } diff --git a/packages/client/src/rpc/modules/engine.ts b/packages/client/src/rpc/modules/engine.ts index e43b504ddc3..517eddc6314 100644 --- a/packages/client/src/rpc/modules/engine.ts +++ b/packages/client/src/rpc/modules/engine.ts @@ -48,7 +48,7 @@ type Uint256 = string type WithdrawalV1 = Exclude[number] export type ExecutionPayloadV1 = Omit export type ExecutionPayloadV2 = ExecutionPayload & { withdrawals: WithdrawalV1[] } -export type ExecutionPayloadV3 = ExecutionPayload & { excessDataGas: Uint256 } +export type ExecutionPayloadV3 = ExecutionPayload & { excessDataGas: Uint64; dataGasUsed: Uint64 } export type ForkchoiceStateV1 = { headBlockHash: Bytes32 @@ -122,7 +122,8 @@ const executionPayloadV2FieldValidators = { } const executionPayloadV3FieldValidators = { ...executionPayloadV2FieldValidators, - excessDataGas: validators.uint256, + dataGasUsed: validators.uint64, + excessDataGas: validators.uint64, } const forkchoiceFieldValidators = { @@ -169,6 +170,7 @@ export const blockToExecutionPayload = (block: Block, value: bigint, bundle?: Bl timestamp: header.timestamp!, extraData: header.extraData!, baseFeePerGas: header.baseFeePerGas!, + dataGasUsed: header.dataGasUsed, excessDataGas: header.excessDataGas, blockHash: bytesToPrefixedHexString(block.hash()), prevRandao: header.mixHash!, diff --git a/packages/client/src/rpc/modules/eth.ts b/packages/client/src/rpc/modules/eth.ts index 5909c07f620..0ced993d62e 100644 --- a/packages/client/src/rpc/modules/eth.ts +++ b/packages/client/src/rpc/modules/eth.ts @@ -124,6 +124,7 @@ const jsonRpcBlock = async ( uncles: block.uncleHeaders.map((uh) => bytesToPrefixedHexString(uh.hash())), baseFeePerGas: header.baseFeePerGas, ...withdrawalsAttr, + dataGasUsed: header.dataGasUsed, excessDataGas: header.excessDataGas, } } diff --git a/packages/client/src/rpc/util/CLConnectionManager.ts b/packages/client/src/rpc/util/CLConnectionManager.ts index f15964ffa3b..599f0c49670 100644 --- a/packages/client/src/rpc/util/CLConnectionManager.ts +++ b/packages/client/src/rpc/util/CLConnectionManager.ts @@ -158,7 +158,9 @@ export class CLConnectionManager { msg += ` withdrawals=${(payload.payload as ExecutionPayloadV2).withdrawals.length}` } if ('excessDataGas' in payload.payload && payload.payload.excessDataGas !== null) { - msg += ` excessDataGas=${(payload.payload as ExecutionPayloadV3).excessDataGas}` + msg += `dataGasUsed=${(payload.payload as ExecutionPayloadV3).dataGasUsed} excessDataGas=${ + (payload.payload as ExecutionPayloadV3).excessDataGas + }` } return msg } diff --git a/packages/client/test/rpc/engine/getPayloadV3.spec.ts b/packages/client/test/rpc/engine/getPayloadV3.spec.ts index b3c24e265bb..7f6afbddbbb 100644 --- a/packages/client/test/rpc/engine/getPayloadV3.spec.ts +++ b/packages/client/test/rpc/engine/getPayloadV3.spec.ts @@ -23,9 +23,9 @@ import { checkError } from '../util' // Since the genesis is copy of withdrawals with just sharding hardfork also started // at 0, we can re-use the same payload args const validForkChoiceState = { - headBlockHash: '0x860e60008cf149dcdb3dbd42f54bd23a5a5024a94b0cc85df1adbe0f528389f6', - safeBlockHash: '0x860e60008cf149dcdb3dbd42f54bd23a5a5024a94b0cc85df1adbe0f528389f6', - finalizedBlockHash: '0x860e60008cf149dcdb3dbd42f54bd23a5a5024a94b0cc85df1adbe0f528389f6', + headBlockHash: '0x771d2330f20db47cef611364dc643ab75e1e99159fe37dc86942ab03c6a6344b', + safeBlockHash: '0x771d2330f20db47cef611364dc643ab75e1e99159fe37dc86942ab03c6a6344b', + finalizedBlockHash: '0x771d2330f20db47cef611364dc643ab75e1e99159fe37dc86942ab03c6a6344b', } const validPayloadAttributes = { timestamp: '0x2f', @@ -35,7 +35,10 @@ const validPayloadAttributes = { const validPayload = [validForkChoiceState, { ...validPayloadAttributes, withdrawals: [] }] -initKZG(kzg, __dirname + '/../../../src/trustedSetups/devnet4.txt') +try { + initKZG(kzg, __dirname + '/../../../src/trustedSetups/devnet4.txt') + // eslint-disable-next-line + } catch {} const method = 'engine_getPayloadV3' tape(`${method}: call with invalid payloadId`, async (t) => { @@ -82,6 +85,7 @@ tape(`${method}: call with known payload`, async (t) => { let payloadId let expectRes = (res: any) => { payloadId = res.body.result.payloadId + t.ok(payloadId !== undefined && payloadId !== null, 'valid payloadId should be received') } await baseRequest(t, server, req, 200, expectRes, false) @@ -112,9 +116,11 @@ tape(`${method}: call with known payload`, async (t) => { const { executionPayload, blobsBundle } = res.body.result t.equal( executionPayload.blockHash, - '0xc51a3346df60c3b63c3e564b0f4b21eed69db6a64445b6a2e5a902185d05e796', + '0x38ba1af4c568abce2dd6b4d4082606c3fc790fa0a14787aa9ca5c61fa7511439', 'built expected block' ) + t.equal(executionPayload.excessDataGas, '0x0', 'correct execess data gas') + t.equal(executionPayload.dataGasUsed, '0x20000', 'correct data gas used') const { commitments, proofs, blobs } = blobsBundle t.ok( commitments.length === proofs.length && commitments.length === blobs.length, diff --git a/packages/client/test/rpc/engine/newPayloadV3VersionedHashes.spec.ts b/packages/client/test/rpc/engine/newPayloadV3VersionedHashes.spec.ts index 07d1dc53ed6..2686879f5c2 100644 --- a/packages/client/test/rpc/engine/newPayloadV3VersionedHashes.spec.ts +++ b/packages/client/test/rpc/engine/newPayloadV3VersionedHashes.spec.ts @@ -13,6 +13,8 @@ type Test = tape.Test const method = 'engine_newPayloadV3' +// blocks are missing excessDataGas and dataGasUsed which will be set to default 0 for 4844 blocks +// however its not required to set to correct value to test for versioned hashes test cases const [blockData] = blocks const originalValidate = BlockHeader.prototype._consensusFormatValidation @@ -35,7 +37,7 @@ tape(`${method}: Cancun validations`, (v1) => { { ...blockData, parentHash: '0x2559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858', - blockHash: '0x5493df0b38523c8e61cd7dd72ac21b023dc5357a5f297ff8db95a03f8a9c4179', + blockHash: '0x5ffbb3eef91d4dfbc8d02309cb7e8824040f823707dc234b1727ab14a8ecf0ff', }, ['0x3434', '0x2334'], ] @@ -73,7 +75,7 @@ tape(`${method}: Cancun validations`, (v1) => { { ...blockData, parentHash: '0x2559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858', - blockHash: '0x701f665755524486783d70ea3808f6d013ddfcd03972bd87eace1f29a44a83e8', + blockHash: '0x3044dc57fc1e7e8adbd4c5db53ee58303f312ee7cda31b851ebbd365ae10f200', // two blob transactions but missing versioned hash of second transactions: [txString, txString], }, @@ -93,7 +95,7 @@ tape(`${method}: Cancun validations`, (v1) => { { ...blockData, parentHash: '0x2559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858', - blockHash: '0x701f665755524486783d70ea3808f6d013ddfcd03972bd87eace1f29a44a83e8', + blockHash: '0x3044dc57fc1e7e8adbd4c5db53ee58303f312ee7cda31b851ebbd365ae10f200', // two blob transactions but mismatching versioned hash of second transactions: [txString, txString], }, @@ -113,7 +115,7 @@ tape(`${method}: Cancun validations`, (v1) => { { ...blockData, parentHash: '0x2559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858', - blockHash: '0x701f665755524486783d70ea3808f6d013ddfcd03972bd87eace1f29a44a83e8', + blockHash: '0x3044dc57fc1e7e8adbd4c5db53ee58303f312ee7cda31b851ebbd365ae10f200', // two blob transactions with matching versioned hashes transactions: [txString, txString], }, diff --git a/packages/evm/test/precompiles/14-pointevaluation.spec.ts b/packages/evm/test/precompiles/14-pointevaluation.spec.ts index 5e400cc3a9a..1da5ad4bd16 100644 --- a/packages/evm/test/precompiles/14-pointevaluation.spec.ts +++ b/packages/evm/test/precompiles/14-pointevaluation.spec.ts @@ -24,7 +24,11 @@ tape('Precompiles: point evaluation', async (t) => { if (isBrowser() === true) { t.end() } else { - initKZG(kzg, __dirname + '/../../../client/src/trustedSetups/devnet4.txt') + try { + initKZG(kzg, __dirname + '/../../../client/src/trustedSetups/devnet4.txt') + // eslint-disable-next-line + } catch {} + const genesisJSON = require('../../../client/test/testdata/geth-genesis/eip4844.json') const common = Common.fromGethGenesis(genesisJSON, { chain: 'custom', diff --git a/packages/tx/test/eip4844.spec.ts b/packages/tx/test/eip4844.spec.ts index 63bd169c5bf..94b66a785aa 100644 --- a/packages/tx/test/eip4844.spec.ts +++ b/packages/tx/test/eip4844.spec.ts @@ -21,7 +21,12 @@ import { BlobEIP4844Transaction, TransactionFactory } from '../src' const isBrowser = new Function('try {return this===window;}catch(e){ return false;}') const pk = randomBytes(32) -if (isBrowser() === false) initKZG(kzg, __dirname + '/../../client/src/trustedSetups/devnet4.txt') +if (isBrowser() === false) { + try { + initKZG(kzg, __dirname + '/../../client/src/trustedSetups/devnet4.txt') + // eslint-disable-next-line + } catch {} +} const gethGenesis = require('../../block/test/testdata/4844-hardfork.json') const common = Common.fromGethGenesis(gethGenesis, { diff --git a/packages/vm/src/buildBlock.ts b/packages/vm/src/buildBlock.ts index 568bdd658e9..0311fef14f4 100644 --- a/packages/vm/src/buildBlock.ts +++ b/packages/vm/src/buildBlock.ts @@ -1,4 +1,4 @@ -import { Block, calcExcessDataGas } from '@ethereumjs/block' +import { Block } from '@ethereumjs/block' import { ConsensusType } from '@ethereumjs/common' import { RLP } from '@ethereumjs/rlp' import { Trie } from '@ethereumjs/trie' @@ -61,7 +61,7 @@ export class BlockBuilder { this.headerData = { ...opts.headerData, - parentHash: opts.headerData?.parentHash ?? opts.parentBlock.hash(), + parentHash: opts.parentBlock.hash(), number: opts.headerData?.number ?? opts.parentBlock.header.number + BigInt(1), gasLimit: opts.headerData?.gasLimit ?? opts.parentBlock.header.gasLimit, } @@ -73,6 +73,13 @@ export class BlockBuilder { ) { this.headerData.baseFeePerGas = opts.parentBlock.header.calcNextBaseFee() } + + if ( + this.vm._common.isActivatedEIP(4844) === true && + typeof this.headerData.excessDataGas === 'undefined' + ) { + this.headerData.excessDataGas = opts.parentBlock.header.calcNextExcessDataGas() + } } /** @@ -181,7 +188,7 @@ export class BlockBuilder { if (tx.gasLimit > blockGasRemaining) { throw new Error('tx has a higher gas limit than the remaining gas in the block') } - let excessDataGas = undefined + let dataGasUsed = undefined if (tx instanceof BlobEIP4844Transaction) { if (this.blockOpts.common?.isActivatedEIP(4844) !== true) { throw Error('eip4844 not activated yet for adding a blob transaction') @@ -192,18 +199,13 @@ export class BlockBuilder { throw new Error('block data gas limit reached') } - const parentHeader = await this.vm.blockchain.getBlock( - this.headerData.parentHash! as Uint8Array - ) - excessDataGas = calcExcessDataGas( - parentHeader!.header, - (tx as BlobEIP4844Transaction).blobs?.length ?? 0 - ) + dataGasUsed = this.dataGasUsed } const header = { ...this.headerData, gasUsed: this.gasUsed, - excessDataGas, + // correct excessDataGas should already part of headerData used above + dataGasUsed, } const blockData = { header, transactions: this.transactions } @@ -268,26 +270,12 @@ export class BlockBuilder { const logsBloom = this.logsBloom() const gasUsed = this.gasUsed const timestamp = this.headerData.timestamp ?? Math.round(Date.now() / 1000) - let excessDataGas = undefined - if (this.vm._common.isActivatedEIP(4844)) { - let parentHeader = null - if (this.headerData.parentHash !== undefined) { - parentHeader = await this.vm.blockchain.getBlock(toBytes(this.headerData.parentHash)) - } - if (parentHeader !== null && parentHeader.header._common.isActivatedEIP(4844) === true) { - // Compute total number of blobs in block - const blobTxns = this.transactions.filter((tx) => tx instanceof BlobEIP4844Transaction) - let newBlobs = 0 - for (const txn of blobTxns) { - newBlobs += (txn as BlobEIP4844Transaction).numBlobs() - } - // Compute excess data gas for block - excessDataGas = calcExcessDataGas(parentHeader.header, newBlobs) - } else { - excessDataGas = BigInt(0) - } + let dataGasUsed = undefined + if (this.vm._common.isActivatedEIP(4844) === true) { + dataGasUsed = this.dataGasUsed } + const headerData = { ...this.headerData, stateRoot, @@ -297,7 +285,8 @@ export class BlockBuilder { logsBloom, gasUsed, timestamp, - excessDataGas, + // correct excessDataGas should already be part of headerData used above + dataGasUsed, } if (consensusType === ConsensusType.ProofOfWork) { @@ -327,5 +316,6 @@ export class BlockBuilder { } export async function buildBlock(this: VM, opts: BuildBlockOpts): Promise { + // let opts override excessDataGas if there is some value passed there return new BlockBuilder(this, opts) } diff --git a/packages/vm/src/runTx.ts b/packages/vm/src/runTx.ts index 8df82e6b9b2..545f63183cd 100644 --- a/packages/vm/src/runTx.ts +++ b/packages/vm/src/runTx.ts @@ -1,4 +1,4 @@ -import { Block, getDataGasPrice } from '@ethereumjs/block' +import { Block } from '@ethereumjs/block' import { ConsensusType, Hardfork } from '@ethereumjs/common' import { BlobEIP4844Transaction, Capability } from '@ethereumjs/tx' import { Account, Address, KECCAK256_NULL, bytesToPrefixedHexString, short } from '@ethereumjs/util' @@ -300,7 +300,7 @@ async function _runTx(this: VM, opts: RunTxOpts): Promise { throw new Error(msg) } const parentBlock = await this.blockchain.getBlock(opts.block?.header.parentHash) - dataGasPrice = getDataGasPrice(parentBlock.header) + dataGasPrice = parentBlock.header.getDataGasPrice() if (castTx.maxFeePerDataGas < dataGasPrice) { const msg = _errorMsg( `Transaction's maxFeePerDataGas ${castTx.maxFeePerDataGas}) is less than block dataGasPrice (${dataGasPrice}).`, diff --git a/packages/vm/test/api/EIPs/eip-4844-blobs.spec.ts b/packages/vm/test/api/EIPs/eip-4844-blobs.spec.ts new file mode 100644 index 00000000000..3d542152af7 --- /dev/null +++ b/packages/vm/test/api/EIPs/eip-4844-blobs.spec.ts @@ -0,0 +1,106 @@ +import { Block } from '@ethereumjs/block' +import { Blockchain } from '@ethereumjs/blockchain' +import { Common, Hardfork } from '@ethereumjs/common' +import { BlobEIP4844Transaction } from '@ethereumjs/tx' +import { + Address, + blobsToCommitments, + blobsToProofs, + bytesToPrefixedHexString, + commitmentsToVersionedHashes, + getBlobs, + initKZG, + privateToAddress, +} from '@ethereumjs/util' +import * as kzg from 'c-kzg' +import { bytesToHex, hexToBytes } from 'ethereum-cryptography/utils' +import * as tape from 'tape' + +import genesisJSON = require('../../../../client/test/testdata/geth-genesis/eip4844.json') +import { VM } from '../../../src/vm' +import { setBalance } from '../utils' + +// Hack to detect if running in browser or not +const isBrowser = new Function('try {return this===window;}catch(e){ return false;}') + +const pk = hexToBytes('20'.repeat(32)) +const sender = bytesToPrefixedHexString(privateToAddress(pk)) +if (isBrowser() === false) { + try { + initKZG(kzg, __dirname + '/../../../../client/src/trustedSetups/devnet4.txt') + // eslint-disable-next-line + } catch {} + +} + +tape('EIP4844 tests', (t) => { + t.test('should build a block correctly with blobs', async (st) => { + const common = Common.fromGethGenesis(genesisJSON, { chain: 'eip4844' }) + common.setHardfork(Hardfork.Cancun) + const genesisBlock = Block.fromBlockData({ header: { gasLimit: 50000 } }, { common }) + const blockchain = await Blockchain.create({ + genesisBlock, + common, + validateBlocks: false, + validateConsensus: false, + }) + const vm = await VM.create({ common, blockchain }) + + const address = Address.fromString(sender) + await setBalance(vm, address, 14680063125000000000n) + const vmCopy = await vm.copy() + + const blockBuilder = await vm.buildBlock({ + parentBlock: genesisBlock, + withdrawals: [], + blockOpts: { + calcDifficultyFromHeader: genesisBlock.header, + freeze: false, + }, + headerData: { + gasLimit: 0xffffn, + }, + }) + + // Set up tx + const blobs = getBlobs('hello world') + const commitments = blobsToCommitments(blobs) + const versionedHashes = commitmentsToVersionedHashes(commitments) + const proofs = blobsToProofs(blobs, commitments) + const unsignedTx = BlobEIP4844Transaction.fromTxData( + { + versionedHashes, + blobs, + kzgCommitments: commitments, + kzgProofs: proofs, + maxFeePerGas: 10000000000n, + maxFeePerDataGas: 100000000n, + gasLimit: 0xffffn, + to: hexToBytes('0xffb38a7a99e3e2335be83fc74b7faa19d5531243'), + }, + { common } + ) + const signedTx = unsignedTx.sign(pk) + + await blockBuilder.addTransaction(signedTx) + + const block = await blockBuilder.build() + st.equal(block.transactions.length, 1, 'blob transaction should be included') + st.equal( + bytesToHex(block.transactions[0].hash()), + bytesToHex(signedTx.hash()), + 'blob transaction should be same' + ) + + const dataGasPerBlob = common.param('gasConfig', 'dataGasPerBlob') + st.equal(block.header.dataGasUsed, dataGasPerBlob, 'data gas used for 1 blob should match') + + // block should successfully execute with VM.runBlock and have same outputs + const result = await vmCopy.runBlock({ block, skipBlockValidation: true }) + st.equal(result.gasUsed, block.header.gasUsed) + st.deepEquals(result.receiptsRoot, block.header.receiptTrie) + st.deepEquals(result.stateRoot, block.header.stateRoot) + st.deepEquals(result.logsBloom, block.header.logsBloom) + st.end() + }) +}) diff --git a/packages/vm/test/api/runTx.spec.ts b/packages/vm/test/api/runTx.spec.ts index d061a2afcb9..8a5e3db28b4 100644 --- a/packages/vm/test/api/runTx.spec.ts +++ b/packages/vm/test/api/runTx.spec.ts @@ -895,7 +895,11 @@ tape('EIP 4844 transaction tests', async (t) => { if (isBrowser() === true) { t.end() } else { - initKZG(kzg, __dirname + '/../../../client/src/trustedSetups/devnet4.txt') + try { + initKZG(kzg, __dirname + '/../../../client/src/trustedSetups/devnet4.txt') + // eslint-disable-next-line + } catch {} + const genesisJson = require('../../../block/test/testdata/4844-hardfork.json') const common = Common.fromGethGenesis(genesisJson, { chain: 'customChain',