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

EIP-6110: Supply Validator Deposits on Chain #3303

Closed
wants to merge 45 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f90d50b
Add deposit type to block
scorbajio Mar 5, 2024
6a809e7
Add deposits type to execution payload
scorbajio Mar 5, 2024
6933a85
Add newPayloadV6110 endpoint
scorbajio Mar 8, 2024
862f2e0
Add EIP-6110 to common
scorbajio Mar 8, 2024
aeb893a
Merge branch 'master' of github.com:ethereumjs/ethereumjs-monorepo in…
scorbajio Mar 8, 2024
d416d67
Return INVALID_PARAMS in case wrong structure is used
scorbajio Mar 8, 2024
f667f8a
Merge branch 'master' of github.com:ethereumjs/ethereumjs-monorepo in…
scorbajio Mar 12, 2024
a2d0259
Add deposits type
scorbajio Mar 13, 2024
b3d5576
Use Array to wrap deposits
scorbajio Mar 13, 2024
bfe233c
Handle response of newPayloadV6110 similar to newPayloadV2 as suggest…
scorbajio Mar 13, 2024
e3cc5b8
Fix linting issue
scorbajio Mar 13, 2024
7b81ff3
Set minimumHardfork for eip 6110 to shanghai to avoid error when usin…
scorbajio Mar 14, 2024
682c283
Add deposits to blocks and headers
scorbajio Mar 14, 2024
1b6aa4f
Add and update tests
scorbajio Mar 14, 2024
0230708
Fix linting issues
scorbajio Mar 14, 2024
a38b76f
Fix tests
scorbajio Mar 14, 2024
7dfa0ca
Undo changes to from-beacon-payload.ts
scorbajio Mar 14, 2024
5b5addf
Update blockchain db to generate block with deposits
scorbajio Mar 15, 2024
2d098af
Add deposits to BuildBlockOpts
scorbajio Mar 15, 2024
3e32945
Include deposits in miner
scorbajio Mar 15, 2024
08c1182
Merge branch 'master' into eip-6110-implementation
scorbajio Mar 22, 2024
63d2609
Fix test
scorbajio Mar 22, 2024
475b38c
Add deposits to client package
scorbajio Mar 22, 2024
76c8066
Add eip 6110 to evm package
scorbajio Mar 23, 2024
b8e27c9
Merge branch 'master' of github.com:ethereumjs/ethereumjs-monorepo in…
scorbajio Mar 25, 2024
8a97673
Put optional deposits param at the end of param inputs for block cons…
scorbajio Mar 26, 2024
86025e7
Fix tests
scorbajio Mar 26, 2024
b531b37
Add deposit root generation and checking
scorbajio Mar 26, 2024
17f4aa0
Merge branch 'master' of github.com:ethereumjs/ethereumjs-monorepo in…
scorbajio Mar 26, 2024
fbb9644
Fix errors
scorbajio Mar 27, 2024
b1f6a35
Fix errors
scorbajio Mar 27, 2024
3f5096d
Fix lint errors
scorbajio Mar 27, 2024
2582599
Merge branch 'master' into eip-6110-implementation
scorbajio Mar 27, 2024
e08ced6
Merge branch 'master' of github.com:ethereumjs/ethereumjs-monorepo in…
scorbajio Mar 27, 2024
6af6ade
Fix bug
scorbajio Mar 27, 2024
c1e6809
Merge branch 'eip-6110-implementation' of github.com:ethereumjs/ether…
scorbajio Mar 27, 2024
6a403c6
Merge branch 'master' of github.com:ethereumjs/ethereumjs-monorepo in…
scorbajio Apr 9, 2024
a33d7d3
Resolve merge conflicts
holgerd77 Apr 26, 2024
1652979
Fix small typo, remove unnecessary boolean comparisons for boolean re…
holgerd77 Apr 26, 2024
fae47aa
Lower minimum 6110 HF in Common to Cancun, use Cancun + EIP activatio…
holgerd77 Apr 26, 2024
5795096
PrefixedHexString-related fixes
holgerd77 Apr 26, 2024
fa3a07e
Lint fix
holgerd77 Apr 26, 2024
b7321a6
Merge branch 'master' into eip-6110-implementation
scorbajio Apr 27, 2024
ef381ce
Merge branch master of github.com:ethereumjs/ethereumjs-monorepo into…
scorbajio May 2, 2024
9055185
Ignore all type errors related to PrefixedHexStrings changes
scorbajio May 2, 2024
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
388 changes: 220 additions & 168 deletions package-lock.json

Large diffs are not rendered by default.

192 changes: 181 additions & 11 deletions packages/block/src/block.ts
Expand Up @@ -4,6 +4,8 @@ import { Trie } from '@ethereumjs/trie'
import { BlobEIP4844Transaction, Capability, TransactionFactory } from '@ethereumjs/tx'
import {
BIGINT_0,
Deposit,
CLRequest,
KECCAK256_RLP,
KECCAK256_RLP_ARRAY,
Withdrawal,
Expand Down Expand Up @@ -41,7 +43,14 @@ import type {
TxOptions,
TypedTransaction,
} from '@ethereumjs/tx'
import type { EthersProvider, WithdrawalBytes } from '@ethereumjs/util'
import type {
DepositBytes,
EthersProvider,
WithdrawalBytes,
CLRequestType,
PrefixedHexString,
RequestBytes,
} from '@ethereumjs/util'

/**
* An object that represents the block.
Expand All @@ -51,6 +60,8 @@ export class Block {
public readonly transactions: TypedTransaction[] = []
public readonly uncleHeaders: BlockHeader[] = []
public readonly withdrawals?: Withdrawal[]
public readonly deposits?: Deposit[]
public readonly requests?: CLRequestType[]
public readonly common: Common
protected keccakFunction: (msg: Uint8Array) => Uint8Array

Expand All @@ -64,8 +75,17 @@ export class Block {
protected cache: {
txTrieRoot?: Uint8Array
withdrawalsTrieRoot?: Uint8Array
requestsRoot?: Uint8Array
} = {}

public static async genDepositsTrieRoot(deposits: Deposit[], emptyTrie?: Trie) {
const trie = emptyTrie ?? new Trie()
for (const [i, deposit] of deposits.entries()) {
await trie.put(RLP.encode(i), RLP.encode(deposit.raw()))
}
return trie.root()
}

/**
* Returns the withdrawals trie root for array of Withdrawal.
* @param wts array of Withdrawal to compute the root of
Expand All @@ -92,6 +112,28 @@ export class Block {
return trie.root()
}

/**
* Returns the requests trie root for an array of CLRequests
* @param requests - an array of CLRequests
* @param emptyTrie optional empty trie used to generate the root
* @returns a 32 byte Uint8Array representing the requests trie root
*/
public static async genRequestsTrieRoot(requests: CLRequest[], emptyTrie?: Trie) {
// Requests should be sorted in monotonically ascending order based on type
// and whatever internal sorting logic is defined by each request type
if (requests.length > 1) {
for (let x = 1; x < requests.length; x++) {
if (requests[x].type < requests[x - 1].type)
throw new Error('requests are not sorted in ascending order')
}
}
const trie = emptyTrie ?? new Trie()
for (const [i, req] of requests.entries()) {
await trie.put(RLP.encode(i), req.serialize())
}
return trie.root()
}

/**
* Static constructor to create a block from a block data dictionary
*
Expand All @@ -105,6 +147,8 @@ export class Block {
uncleHeaders: uhsData,
withdrawals: withdrawalsData,
executionWitness: executionWitnessData,
deposits: depositData,
requests: clRequests,
} = blockData

const header = BlockHeader.fromHeaderData(headerData, opts)
Expand Down Expand Up @@ -143,7 +187,18 @@ export class Block {
// stub till that time
const executionWitness = executionWitnessData

return new Block(header, transactions, uncleHeaders, withdrawals, opts, executionWitness)
const deposits = depositData?.map(Deposit.fromDepositData)

return new Block(
header,
transactions,
uncleHeaders,
withdrawals,
opts,
deposits,
clRequests,
executionWitness
)
}

/**
Expand Down Expand Up @@ -177,7 +232,15 @@ export class Block {

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

if (
Expand All @@ -189,6 +252,15 @@ export class Block {
)
}

if (
header.common.isActivatedEIP(6110) &&
(depositBytes === undefined || !Array.isArray(depositBytes))
) {
throw new Error(
'Invalid serialized block input: EIP-6110 is active, and no deposits were provided as array'
)
}

// parse transactions
const transactions = []
for (const txData of txsData ?? []) {
Expand Down Expand Up @@ -227,6 +299,24 @@ export class Block {
}))
?.map(Withdrawal.fromWithdrawalData)

const deposits = header.common.isActivatedEIP(6110)
? (depositBytes as DepositBytes[])
?.map(([pubkey, withdrawalCredentials, amount, signature, index]) => ({
pubkey,
withdrawalCredentials,
amount,
signature,
index,
}))
?.map(Deposit.fromDepositData)
: undefined

let requests
if (header.common.isActivatedEIP(7685)) {
requests = (requestBytes as RequestBytes[]).map(
(bytes) => new CLRequest(bytes[0], bytes.slice(1))
)
}
// 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
Expand All @@ -242,7 +332,16 @@ export class Block {
}
}

return new Block(header, transactions, uncleHeaders, withdrawals, opts, executionWitness)
return new Block(
header,
transactions,
uncleHeaders,
withdrawals,
opts,
deposits,
requests,
executionWitness
)
}

/**
Expand Down Expand Up @@ -334,22 +433,27 @@ export class Block {
feeRecipient: coinbase,
transactions,
withdrawals: withdrawalsData,
requestsRoot,
executionWitness,
} = payload

const txs = []
for (const [index, serializedTx] of transactions.entries()) {
try {
const tx = TransactionFactory.fromSerializedData(hexToBytes(serializedTx), {
common: opts?.common,
})
const tx = TransactionFactory.fromSerializedData(
hexToBytes(serializedTx as PrefixedHexString),
{
common: opts?.common,
}
)
txs.push(tx)
} catch (error) {
const validationError = `Invalid tx at index ${index}: ${error}`
throw validationError
}
}

const reqRoot = requestsRoot === null ? undefined : requestsRoot
const transactionsTrie = await Block.genTransactionsTrieRoot(
txs,
new Trie({ common: opts?.common })
Expand All @@ -360,12 +464,17 @@ export class Block {
: undefined
const header: HeaderData = {
...payload,
//@ts-ignore
number,
//@ts-ignore
receiptTrie,
transactionsTrie,
withdrawalsRoot,
//@ts-ignore
mixHash,
//@ts-ignore
coinbase,
requestsRoot: reqRoot,
}

// we are not setting setHardfork as common is already set to the correct hf
Expand All @@ -380,7 +489,7 @@ export class Block {
throw Error('Missing executionWitness for EIP-6800 activated executionPayload')
}
// Verify blockHash matches payload
if (!equalsBytes(block.hash(), hexToBytes(payload.blockHash))) {
if (!equalsBytes(block.hash(), hexToBytes(payload.blockHash as PrefixedHexString))) {
const validationError = `Invalid blockHash, expected: ${
payload.blockHash
}, received: ${bytesToHex(block.hash())}`
Expand Down Expand Up @@ -414,6 +523,8 @@ export class Block {
uncleHeaders: BlockHeader[] = [],
withdrawals?: Withdrawal[],
opts: BlockOptions = {},
deposits?: Deposit[],
requests?: CLRequest[],
executionWitness?: VerkleExecutionWitness | null
) {
this.header = header ?? BlockHeader.fromHeaderData({}, opts)
Expand All @@ -422,7 +533,9 @@ export class Block {

this.transactions = transactions
this.withdrawals = withdrawals ?? (this.common.isActivatedEIP(4895) ? [] : undefined)
this.deposits = deposits ?? (this.common.isActivatedEIP(6110) ? [] : undefined)
this.executionWitness = executionWitness
this.requests = requests ?? (this.common.isActivatedEIP(7685) ? [] : undefined)
// null indicates an intentional absence of value or unavailability
// undefined indicates that the executionWitness should be initialized with the default state
if (this.common.isActivatedEIP(6800) && this.executionWitness === undefined) {
Expand Down Expand Up @@ -463,6 +576,10 @@ export class Block {
throw new Error('Cannot have a withdrawals field if EIP 4895 is not active')
}

if (!this.common.isActivatedEIP(6110) && deposits !== undefined) {
throw new Error('Cannot have a deposits field if EIP 6110 is not active')
}

if (
!this.common.isActivatedEIP(6800) &&
executionWitness !== undefined &&
Expand All @@ -471,6 +588,18 @@ export class Block {
throw new Error(`Cannot have executionWitness field if EIP 6800 is not active `)
}

if (!this.common.isActivatedEIP(7685) && requests !== undefined) {
throw new Error(`Cannot have requests field if EIP 7685 is not active`)
}

// Requests should be sorted in monotonically ascending order based on type
// and whatever internal sorting logic is defined by each request type
if (requests !== undefined && requests.length > 1) {
for (let x = 1; x < requests.length; x++) {
if (requests[x].type < requests[x - 1].type)
throw new Error('requests are not sorted in ascending order')
}
}
const freeze = opts?.freeze ?? true
if (freeze) {
Object.freeze(this)
Expand All @@ -492,6 +621,10 @@ export class Block {
if (withdrawalsRaw) {
bytesArray.push(withdrawalsRaw)
}
const depositsRaw = this.deposits?.map((deposit) => deposit.raw())
if (depositsRaw) {
bytesArray.push(depositsRaw)
}
if (this.executionWitness !== undefined && this.executionWitness !== null) {
const executionWitnessBytes = RLP.encode(JSON.stringify(this.executionWitness))
bytesArray.push(executionWitnessBytes as any)
Expand Down Expand Up @@ -546,6 +679,25 @@ export class Block {
return result
}

async requestsTrieIsValid(): Promise<boolean> {
if (!this.common.isActivatedEIP(7685)) {
throw new Error('EIP 7685 is not activated')
}

let result
if (this.requests!.length === 0) {
result = equalsBytes(this.header.requestsRoot!, KECCAK256_RLP)
return result
}

if (this.cache.requestsRoot === undefined) {
this.cache.requestsRoot = await Block.genRequestsTrieRoot(this.requests!)
}

result = equalsBytes(this.cache.requestsRoot, this.header.requestsRoot!)

return result
}
/**
* Validates transaction signatures and minimum gas requirements.
* @returns {string[]} an array of error strings
Expand All @@ -559,7 +711,7 @@ export class Block {
// eslint-disable-next-line prefer-const
for (let [i, tx] of this.transactions.entries()) {
const errs = tx.getValidationErrors()
if (this.common.isActivatedEIP(1559) === true) {
if (this.common.isActivatedEIP(1559)) {
if (tx.supports(Capability.EIP1559FeeMarket)) {
tx = tx as FeeMarketEIP1559Transaction
if (tx.maxFeePerGas < this.header.baseFeePerGas!) {
Expand All @@ -572,7 +724,7 @@ export class Block {
}
}
}
if (this.common.isActivatedEIP(4844) === true) {
if (this.common.isActivatedEIP(4844)) {
if (tx instanceof BlobEIP4844Transaction) {
blobGasUsed += BigInt(tx.numBlobs()) * blobGasPerBlob
if (blobGasUsed > blobGasLimit) {
Expand All @@ -587,7 +739,7 @@ export class Block {
}
}

if (this.common.isActivatedEIP(4844) === true) {
if (this.common.isActivatedEIP(4844)) {
if (blobGasUsed !== this.header.blobGasUsed) {
errors.push(`invalid blobGasUsed expected=${this.header.blobGasUsed} actual=${blobGasUsed}`)
}
Expand Down Expand Up @@ -755,6 +907,17 @@ export class Block {
return result
}

async depositsTrieIsValid(): Promise<boolean> {
if (!this.common.isActivatedEIP(6110)) {
throw new Error('EIP 6110 is not activated')
}
const depositsRoot = await Block.genDepositsTrieRoot(
this.deposits!,
new Trie({ common: this.common })
)
return equalsBytes(depositsRoot, this.header.depositsRoot!)
}

/**
* Consistency checks for uncles included in the block, if any.
*
Expand Down Expand Up @@ -811,11 +974,18 @@ export class Block {
withdrawals: this.withdrawals.map((wt) => wt.toJSON()),
}
: {}
const depositsAttr = this.deposits
? {
deposits: this.deposits.map((deposit) => deposit.toJSON()),
}
: {}
return {
header: this.header.toJSON(),
transactions: this.transactions.map((tx) => tx.toJSON()),
uncleHeaders: this.uncleHeaders.map((uh) => uh.toJSON()),
requests: this.requests?.map((req) => bytesToHex(req.serialize())),
...withdrawalsAttr,
...depositsAttr,
}
}

Expand Down