Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement EIP 7002 (exits) #3336

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 59 additions & 6 deletions packages/block/src/block.ts
Expand Up @@ -5,6 +5,7 @@
import {
BIGINT_0,
Deposit,
Exit,
KECCAK256_RLP,
Withdrawal,
bigIntToHex,
Expand Down Expand Up @@ -41,7 +42,7 @@
TxOptions,
TypedTransaction,
} from '@ethereumjs/tx'
import type { DepositBytes, EthersProvider, WithdrawalBytes } from '@ethereumjs/util'
import type { DepositBytes, EthersProvider, ExitBytes, WithdrawalBytes } from '@ethereumjs/util'

/**
* An object that represents the block.
Expand All @@ -52,6 +53,7 @@
public readonly uncleHeaders: BlockHeader[] = []
public readonly withdrawals?: Withdrawal[]
public readonly deposits?: Deposit[]
public readonly exits?: Exit[]
public readonly common: Common
protected keccakFunction: (msg: Uint8Array) => Uint8Array

Expand Down Expand Up @@ -112,6 +114,7 @@
transactions: txsData,
uncleHeaders: uhsData,
withdrawals: withdrawalsData,
exits: exitData,
executionWitness: executionWitnessData,
deposits: depositData,
} = blockData
Expand Down Expand Up @@ -151,6 +154,7 @@
// The witness data is planned to come in rlp serialized bytes so leave this
// stub till that time
const executionWitness = executionWitnessData
const exits = exitData?.map(Exit.fromExitData)

const deposits = depositData?.map(Deposit.fromDepositData)

Expand All @@ -161,7 +165,8 @@
withdrawals,
opts,
executionWitness,
deposits
deposits,
exits
)
}

Expand Down Expand Up @@ -196,8 +201,15 @@

// 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, withdrawalBytes, depositBytes, executionWitnessBytes] =
values
const [
headerData,
txsData,
uhsData,
withdrawalBytes,
depositBytes,
exitBytes,
executionWitnessBytes,
] = values
const header = BlockHeader.fromValuesArray(headerData, opts)

if (
Expand All @@ -217,6 +229,14 @@
'Invalid serialized block input: EIP-6110 is active, and no deposits were provided as array'
)
}
if (
header.common.isActivatedEIP(7002) &&
(exitBytes === undefined || !Array.isArray(exitBytes))

Check warning on line 234 in packages/block/src/block.ts

View check run for this annotation

Codecov / codecov/patch

packages/block/src/block.ts#L234

Added line #L234 was not covered by tests
) {
throw new Error(
'Invalid serialized block input: EIP-7002 is active, and no exits were provided as array'
)

Check warning on line 238 in packages/block/src/block.ts

View check run for this annotation

Codecov / codecov/patch

packages/block/src/block.ts#L236-L238

Added lines #L236 - L238 were not covered by tests
}

// parse transactions
const transactions = []
Expand Down Expand Up @@ -267,6 +287,16 @@
}))
?.map(Deposit.fromDepositData)
: undefined
// TODO fix my type, also figure out why we cannot parse this the same as WithdrawalBytes
// @ts-ignore
const exits = header.common.isActivatedEIP(7002)
? (exitBytes as ExitBytes[])
?.map(([address, validatorPubkey]) => ({
address,
validatorPubkey,
}))

Check warning on line 297 in packages/block/src/block.ts

View check run for this annotation

Codecov / codecov/patch

packages/block/src/block.ts#L293-L297

Added lines #L293 - L297 were not covered by tests
?.map(Exit.fromExitData)
: undefined

// executionWitness are not part of the EL fetched blocks via eth_ bodies method
// they are currently only available via the engine api constructed blocks
Expand All @@ -290,7 +320,8 @@
withdrawals,
opts,
executionWitness,
deposits
deposits,
exits
)
}

Expand Down Expand Up @@ -464,7 +495,8 @@
withdrawals?: Withdrawal[],
opts: BlockOptions = {},
executionWitness?: VerkleExecutionWitness | null,
deposits?: Deposit[]
deposits?: Deposit[],
exits?: Exit[]
) {
this.header = header ?? BlockHeader.fromHeaderData({}, opts)
this.common = this.header.common
Expand All @@ -473,6 +505,7 @@
this.transactions = transactions
this.withdrawals = withdrawals ?? (this.common.isActivatedEIP(4895) ? [] : undefined)
this.deposits = deposits ?? (this.common.isActivatedEIP(6110) ? [] : undefined)
this.exits = exits ?? (this.common.isActivatedEIP(7002) ? [] : undefined)
this.executionWitness = executionWitness
// null indicates an intentional absence of value or unavailability
// undefined indicates that the executionWitness should be initialized with the default state
Expand Down Expand Up @@ -526,6 +559,10 @@
throw new Error(`Cannot have executionWitness field if EIP 6800 is not active `)
}

if (!this.common.isActivatedEIP(7002) && exits !== undefined && exits !== null) {
throw new Error(`Cannot have exits field if EIP 7002 is not active`)

Check warning on line 563 in packages/block/src/block.ts

View check run for this annotation

Codecov / codecov/patch

packages/block/src/block.ts#L563

Added line #L563 was not covered by tests
}

const freeze = opts?.freeze ?? true
if (freeze) {
Object.freeze(this)
Expand All @@ -551,6 +588,10 @@
if (depositsRaw) {
bytesArray.push(depositsRaw)
}
const exitsRaw = this.exits?.map((ex) => ex.raw())
if (exitsRaw) {
bytesArray.push(exitsRaw)
}

Check warning on line 594 in packages/block/src/block.ts

View check run for this annotation

Codecov / codecov/patch

packages/block/src/block.ts#L593-L594

Added lines #L593 - L594 were not covered by tests
if (this.executionWitness !== undefined && this.executionWitness !== null) {
const executionWitnessBytes = RLP.encode(JSON.stringify(this.executionWitness))
bytesArray.push(executionWitnessBytes as any)
Expand Down Expand Up @@ -724,6 +765,12 @@
throw new Error(`Invalid block: ethereumjs stateless client needs executionWitness`)
}
}

if (this.common.isActivatedEIP(7002)) {
if (this.exits === undefined || this.exits === null) {
throw new Error('Invalid block: missing exits')
}
}

Check warning on line 773 in packages/block/src/block.ts

View check run for this annotation

Codecov / codecov/patch

packages/block/src/block.ts#L770-L773

Added lines #L770 - L773 were not covered by tests
}

/**
Expand Down Expand Up @@ -873,12 +920,18 @@
deposits: this.deposits.map((deposit) => deposit.toJSON()),
}
: {}
const exitsAttr = this.exits
? {
exits: this.exits.map((exit) => exit.toJSON()),
}

Check warning on line 926 in packages/block/src/block.ts

View check run for this annotation

Codecov / codecov/patch

packages/block/src/block.ts#L924-L926

Added lines #L924 - L926 were not covered by tests
: {}
return {
header: this.header.toJSON(),
transactions: this.transactions.map((tx) => tx.toJSON()),
uncleHeaders: this.uncleHeaders.map((uh) => uh.toJSON()),
...withdrawalsAttr,
...depositsAttr,
...exitsAttr,
}
}

Expand Down
9 changes: 8 additions & 1 deletion packages/block/src/from-rpc.ts
Expand Up @@ -55,7 +55,14 @@ export function blockFromRpc(
const uncleHeaders = uncles.map((uh) => blockHeaderFromRpc(uh, options))

return Block.fromBlockData(
{ header, transactions, uncleHeaders, withdrawals: blockParams.withdrawals },
{
header,
transactions,
uncleHeaders,
withdrawals: blockParams.withdrawals,
deposits: blockParams.deposits,
exits: blockParams.exits,
},
options
)
}
34 changes: 33 additions & 1 deletion packages/block/src/header.ts
Expand Up @@ -64,6 +64,7 @@
public readonly blobGasUsed?: bigint
public readonly excessBlobGas?: bigint
public readonly parentBeaconBlockRoot?: Uint8Array
public readonly exitsRoot?: Uint8Array

public readonly common: Common

Expand Down Expand Up @@ -118,7 +119,8 @@
*/
public static fromValuesArray(values: BlockHeaderBytes, opts: BlockOptions = {}) {
const headerData = valuesArrayToHeaderData(values)
const { number, baseFeePerGas, excessBlobGas, blobGasUsed, parentBeaconBlockRoot } = headerData
const { number, baseFeePerGas, excessBlobGas, blobGasUsed, parentBeaconBlockRoot, exitsRoot } =
headerData
const header = BlockHeader.fromHeaderData(headerData, opts)
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (header.common.isActivatedEIP(1559) && baseFeePerGas === undefined) {
Expand All @@ -139,6 +141,9 @@
if (header.common.isActivatedEIP(4788) && parentBeaconBlockRoot === undefined) {
throw new Error('invalid header. parentBeaconBlockRoot should be provided')
}
if (header.common.isActivatedEIP(7002) && exitsRoot === undefined) {
throw new Error('invalid header. exitsRoot should be provided')

Check warning on line 145 in packages/block/src/header.ts

View check run for this annotation

Codecov / codecov/patch

packages/block/src/header.ts#L145

Added line #L145 was not covered by tests
}
return header
}
/**
Expand Down Expand Up @@ -224,6 +229,7 @@
blobGasUsed: this.common.isActivatedEIP(4844) ? BIGINT_0 : undefined,
excessBlobGas: this.common.isActivatedEIP(4844) ? BIGINT_0 : undefined,
parentBeaconBlockRoot: this.common.isActivatedEIP(4788) ? zeros(32) : undefined,
exitsRoot: this.common.isActivatedEIP(7002) ? zeros(32) : undefined,
}

const baseFeePerGas =
Expand All @@ -239,6 +245,8 @@
const parentBeaconBlockRoot =
toType(headerData.parentBeaconBlockRoot, TypeOutput.Uint8Array) ??
hardforkDefaults.parentBeaconBlockRoot
const exitsRoot =
toType(headerData.exitsRoot, TypeOutput.Uint8Array) ?? hardforkDefaults.exitsRoot

if (!this.common.isActivatedEIP(1559) && baseFeePerGas !== undefined) {
throw new Error('A base fee for a block can only be set with EIP1559 being activated')
Expand Down Expand Up @@ -272,6 +280,10 @@
)
}

if (!this.common.isActivatedEIP(7200) && exitsRoot !== undefined) {
throw new Error('A exitsRoot for a header can only be provided with EIP7002 being activated')

Check warning on line 284 in packages/block/src/header.ts

View check run for this annotation

Codecov / codecov/patch

packages/block/src/header.ts#L284

Added line #L284 was not covered by tests
}

this.parentHash = parentHash
this.uncleHash = uncleHash
this.coinbase = coinbase
Expand All @@ -293,6 +305,7 @@
this.blobGasUsed = blobGasUsed
this.excessBlobGas = excessBlobGas
this.parentBeaconBlockRoot = parentBeaconBlockRoot
this.exitsRoot = exitsRoot
this._genericFormatValidation()
this._validateDAOExtraData()

Expand Down Expand Up @@ -431,6 +444,19 @@
throw new Error(msg)
}
}

if (this.common.isActivatedEIP(7002) === true) {
if (this.exitsRoot === undefined) {
const msg = this._errorMsg('EIP7002 has no exitsRoot field')
throw new Error(msg)
}
if (this.exitsRoot.length !== 32) {
const msg = this._errorMsg(
`exitsRoot must be 32 bytes, received ${this.exitsRoot.length} bytes`
)
throw new Error(msg)
}
}

Check warning on line 459 in packages/block/src/header.ts

View check run for this annotation

Codecov / codecov/patch

packages/block/src/header.ts#L449-L459

Added lines #L449 - L459 were not covered by tests
}

/**
Expand Down Expand Up @@ -721,6 +747,9 @@
if (this.common.isActivatedEIP(4788) === true) {
rawItems.push(this.parentBeaconBlockRoot!)
}
if (this.common.isActivatedEIP(7002)) {
rawItems.push(this.exitsRoot!)

Check warning on line 751 in packages/block/src/header.ts

View check run for this annotation

Codecov / codecov/patch

packages/block/src/header.ts#L751

Added line #L751 was not covered by tests
}

return rawItems
}
Expand Down Expand Up @@ -990,6 +1019,9 @@
if (this.common.isActivatedEIP(4788) === true) {
jsonDict.parentBeaconBlockRoot = bytesToHex(this.parentBeaconBlockRoot!)
}
if (this.common.isActivatedEIP(7002)) {
jsonDict.exitsRoot = bytesToHex(this.exitsRoot!)
}

Check warning on line 1024 in packages/block/src/header.ts

View check run for this annotation

Codecov / codecov/patch

packages/block/src/header.ts#L1023-L1024

Added lines #L1023 - L1024 were not covered by tests
return jsonDict
}

Expand Down
29 changes: 26 additions & 3 deletions packages/block/src/types.ts
Expand Up @@ -7,7 +7,10 @@ import type {
BytesLike,
DepositBytes,
DepositData,
ExitBytes,
ExitData,
JsonRpcDeposit,
JsonRpcExit,
JsonRpcWithdrawal,
PrefixedHexString,
WithdrawalBytes,
Expand Down Expand Up @@ -140,6 +143,7 @@ export interface HeaderData {
blobGasUsed?: BigIntLike
excessBlobGas?: BigIntLike
parentBeaconBlockRoot?: BytesLike
exitsRoot?: BytesLike
}

/**
Expand All @@ -161,10 +165,15 @@ export interface BlockData {
* EIP-6100: Deposits in EL (experimental)
*/
deposits?: Array<DepositData>
/**
* EIP-7002: Exits in EL (experimental)
*/
exits?: Array<ExitData>
}

export type WithdrawalsBytes = WithdrawalBytes[]
export type DepositsBytes = DepositBytes[]
export type ExitsBytes = ExitBytes[]
export type ExecutionWitnessBytes = Uint8Array

export type BlockBytes =
Expand All @@ -175,16 +184,26 @@ export type BlockBytes =
TransactionsBytes,
UncleHeadersBytes,
WithdrawalsBytes,
ExecutionWitnessBytes
DepositsBytes,
ExitsBytes
// The type here is the BlockBytes without 6800, but with 6110 + 7002
]
| [BlockHeaderBytes, TransactionsBytes, UncleHeadersBytes, WithdrawalsBytes, DepositsBytes]
| [
BlockHeaderBytes,
TransactionsBytes,
UncleHeadersBytes,
WithdrawalsBytes,
DepositsBytes,
ExecutionWitnessBytes
ExitBytes,
ExecutionWitnessBytes // This includes 6800 with 6110 + 7002
]
| [BlockHeaderBytes, TransactionsBytes, UncleHeadersBytes, WithdrawalsBytes, DepositsBytes]
| [
BlockHeaderBytes,
TransactionsBytes,
UncleHeadersBytes,
WithdrawalsBytes,
ExecutionWitnessBytes // This only includes 6800 sso without 6110 + 7002
]

/**
Expand Down Expand Up @@ -215,6 +234,7 @@ export interface JsonBlock {
uncleHeaders?: JsonHeader[]
withdrawals?: JsonRpcWithdrawal[]
deposits?: JsonRpcDeposit[]
exits?: JsonRpcExit[]
executionWitness?: VerkleExecutionWitness | null
}

Expand Down Expand Up @@ -243,6 +263,7 @@ export interface JsonHeader {
blobGasUsed?: string
excessBlobGas?: string
parentBeaconBlockRoot?: string
exitsRoot?: string
}

/*
Expand Down Expand Up @@ -274,6 +295,8 @@ export interface JsonRpcBlock {
withdrawalsRoot?: string // If EIP-4895 is enabled for this block, the root of the withdrawal trie of the block.
deposits?: Array<JsonRpcDeposit> // If EIP-6110 is enabled for this block, array of deposits
depositsRoot?: string // If EIP-6110 is enabled for this block, the root of the deposit trie of the block.
exits?: Array<JsonRpcExit> // If EIP-7002 is enabled for this block, array of exits
exitsRoot?: string // If EIP-7002 is enabled for this block, the root of the exits trie of the block.
blobGasUsed?: string // If EIP-4844 is enabled for this block, returns the blob gas used for the block
excessBlobGas?: string // If EIP-4844 is enabled for this block, returns the excess blob gas for the block
parentBeaconBlockRoot?: string // If EIP-4788 is enabled for this block, returns parent beacon block root
Expand Down