Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions packages/block/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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')

Expand All @@ -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}`
)
}
}
Expand All @@ -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
}

Expand Down Expand Up @@ -563,21 +568,38 @@ 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 ${
tx.maxFeePerDataGas
} < 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}`
Expand Down
4 changes: 4 additions & 0 deletions packages/block/src/from-beacon-payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type BeaconPayloadJson = {
block_hash: string
transactions: string[]
withdrawals?: BeaconWithdrawal[]
data_gas_used?: string
excess_data_gas?: string
}

Expand Down Expand Up @@ -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))
}
Expand Down
4 changes: 4 additions & 0 deletions packages/block/src/header-from-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export function blockHeaderFromRpc(blockParams: JsonRpcBlock, options?: BlockOpt
nonce,
baseFeePerGas,
withdrawalsRoot,
dataGasUsed,
excessDataGas,
} = blockParams

const blockHeader = BlockHeader.fromHeaderData(
Expand All @@ -49,6 +51,8 @@ export function blockHeaderFromRpc(blockParams: JsonRpcBlock, options?: BlockOpt
nonce,
baseFeePerGas,
withdrawalsRoot,
dataGasUsed,
excessDataGas,
},
options
)
Expand Down
63 changes: 60 additions & 3 deletions packages/block/src/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -194,13 +195,16 @@ 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,
}

const baseFeePerGas =
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

Expand All @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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!))
}

Expand Down Expand Up @@ -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
Expand Down
64 changes: 3 additions & 61 deletions packages/block/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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) {
Expand All @@ -69,6 +69,7 @@ export function valuesArrayToHeaderData(values: BlockHeaderBytes): HeaderData {
nonce,
baseFeePerGas,
withdrawalsRoot,
dataGasUsed,
excessDataGas,
}
}
Expand All @@ -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) {
Expand All @@ -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
}
8 changes: 1 addition & 7 deletions packages/block/src/index.ts
Original file line number Diff line number Diff line change
@@ -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'
6 changes: 5 additions & 1 deletion packages/block/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export interface HeaderData {
nonce?: BytesLike
baseFeePerGas?: BigIntLike
withdrawalsRoot?: BytesLike
dataGasUsed?: BigIntLike
excessDataGas?: BigIntLike
}

Expand Down Expand Up @@ -167,6 +168,7 @@ export interface JsonHeader {
nonce?: string
baseFeePerGas?: string
withdrawalsRoot?: string
dataGasUsed?: string
excessDataGas?: string
}

Expand Down Expand Up @@ -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<JsonRpcWithdrawal> // 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
}

Expand All @@ -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
}
Loading