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

common: add Cancun CFI EIPs for devnet8 and fix eip-4788 block building issues #2892

Merged
merged 7 commits into from
Jul 17, 2023
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
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 @@ -29,6 +29,7 @@ export type BeaconPayloadJson = {
withdrawals?: BeaconWithdrawal[]
data_gas_used?: string
excess_data_gas?: string
parent_beacon_block_root?: string
}

/**
Expand Down Expand Up @@ -68,6 +69,9 @@ export function executionPayloadFromBeaconPayload(payload: BeaconPayloadJson): E
if (payload.excess_data_gas !== undefined && payload.excess_data_gas !== null) {
executionPayload.excessDataGas = bigIntToHex(BigInt(payload.excess_data_gas))
}
if (payload.parent_beacon_block_root !== undefined && payload.parent_beacon_block_root !== null) {
executionPayload.parentBeaconBlockRoot = payload.parent_beacon_block_root
}

return executionPayload
}
5 changes: 4 additions & 1 deletion packages/block/src/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export class BlockHeader {
withdrawalsRoot: this.common.isActivatedEIP(4895) ? KECCAK256_RLP : undefined,
dataGasUsed: this.common.isActivatedEIP(4844) ? BigInt(0) : undefined,
excessDataGas: this.common.isActivatedEIP(4844) ? BigInt(0) : undefined,
parentBeaconBlockRoot: this.common.isActivatedEIP(4788) ? KECCAK256_RLP : undefined,
parentBeaconBlockRoot: this.common.isActivatedEIP(4788) ? zeros(32) : undefined,
}

const baseFeePerGas =
Expand Down Expand Up @@ -652,6 +652,9 @@ export class BlockHeader {
rawItems.push(bigIntToUnpaddedBytes(this.dataGasUsed!))
rawItems.push(bigIntToUnpaddedBytes(this.excessDataGas!))
}
if (this.common.isActivatedEIP(4788) === true) {
rawItems.push(this.parentBeaconBlockRoot!)
}

return rawItems
}
Expand Down
2 changes: 2 additions & 0 deletions packages/block/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export interface JsonRpcBlock {
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
parentBeaconBlockRoot?: string // If EIP-4788 is enabled for this block, returns parent beacon block root
}

// Note: all these strings are 0x-prefixed
Expand Down Expand Up @@ -220,4 +221,5 @@ export type ExecutionPayload = {
withdrawals?: WithdrawalV1[] // Array of withdrawal objects
dataGasUsed?: PrefixedHexString // QUANTITY, 64 Bits
excessDataGas?: PrefixedHexString // QUANTITY, 64 Bits
parentBeaconBlockRoot?: PrefixedHexString // QUANTITY, 64 Bits
}
4 changes: 2 additions & 2 deletions packages/block/test/eip4788block.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Chain, Common, Hardfork } from '@ethereumjs/common'
import { KECCAK256_RLP, bytesToHex, zeros } from '@ethereumjs/util'
import { bytesToHex, zeros } from '@ethereumjs/util'
import { assert, describe, it } from 'vitest'

import { BlockHeader } from '../src/header.js'
Expand Down Expand Up @@ -63,7 +63,7 @@ describe('EIP4788 header tests', () => {
)
assert.equal(
block.toJSON().header?.parentBeaconBlockRoot,
bytesToHex(KECCAK256_RLP),
bytesToHex(zeros(32)),
'JSON output includes excessDataGas'
)
})
Expand Down
2 changes: 1 addition & 1 deletion packages/block/test/from-beacon-payload.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe('[fromExecutionPayloadJson]: 4844 devnet 5', () => {
const block = await Block.fromBeaconPayloadJson(
{
...payload87475,
block_hash: '0x5be157c3b687537d20a252ad072e8d6a458108eb1b95944368f5a8f8f3325b07',
block_hash: '0x573714bdd0ca5e47bc32008751c4fc74237f8cb354fbc1475c1d0ece38236ea4',
},
{ common }
)
Expand Down
5 changes: 3 additions & 2 deletions packages/block/test/testdata/payload-slot-87335.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"timestamp": "1683551220",
"extra_data": "0x4e65746865726d696e64",
"base_fee_per_gas": "7",
"block_hash": "0xc8f941ddbc909b59693e98a786eadc2c695fc49de778ea1150ac092945956b74",
"block_hash": "0xdb9615aeeae255df092f27d42478eccbf7921c3fd537d5f1ce5ac5120ee4b7a4",
"transactions": [
"0x03f89d850120b996ed3685012a1a646085012a1a64608303345094ffb38a7a99e3e2335be83fc74b7faa19d55312418308a80280c085012a1a6460e1a00153a6a1e053cf4c5a09e84088ed8ad7cb53d76c8168f1b82f7cfebfcd06da1a01a007785223eec68459d72265f10bdb30ec3415252a63100605a03142fa211ebbe9a07dbbf9e081fa7b9a01202e4d9ee0e0e513f80efbbab6c784635429905389ce86",
"0x03f889850120b996ed81f0847735940084b2d05e158307a1208001855f495f4955c084b2d05e15e1a001d343d3cd62abd9c5754cbe5128c25ea90786a8ae75fb79c8cf95f4dcdd08ec80a014103732b5a9789bbf5ea859ed904155398abbef343f8fd63007efb70795d382a07272e847382789a092eadf08e2b9002e727376f8466fff0e4d4639fd60a528f2",
Expand All @@ -20,5 +20,6 @@
],
"withdrawals": [],
"excess_data_gas": "262144",
"data_gas_used": "524288"
"data_gas_used": "524288",
"parent_beacon_block_root": "0x1344ac29ccbab1c5208e04edf83d897b9679143cda921caf9959d4e7547267ea"
}
5 changes: 3 additions & 2 deletions packages/block/test/testdata/payload-slot-87475.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"timestamp": "1683552900",
"extra_data": "0x4e65746865726d696e64",
"base_fee_per_gas": "9",
"block_hash": "0x5be157c3b687537d20a252ad072e8d6a458108eb1b95944368f5a8f8f3325b07",
"block_hash": "0x573714bdd0ca5e47bc32008751c4fc74237f8cb354fbc1475c1d0ece38236ea4",
"transactions": [
"0x02f8f3850120b996ed1a85012a1645a985012a1645b0830186a0948e4fc0f136ba56a151fd1530d4d6695df6a0fb4a80b880d08cc67f792879a8e1d0d5569a51af02f456f410ad93b1de0c038b667b7c5577898a95d960f0527fb7298b05f6a2c83ed6f508eee540edb1248b9235bb26fa566927967d32652b88610110527fe29d11468a1e028eedc6143170491b87a32609c236cb7068ebb1799c616c393061013052604a61015053600d6101515360b661c001a0f49f3f2b301cc72120fd1dcb1fe218012623035f5824d17343e802a4741c4e60a02a40bf4d7a5d52c8f554b04c635a898650584c5452be62a5304e50ca59e4d55f",
"0x03f8dc850120b996ed04840bebc200843b9aca0783033450948a185c5a7941b20253a2923690dd54a9e7bfd0a980b844a8d55bda000000000000000000000000573d9cd570267bb9d1547192e51e5c8d017d70340000000000000000000000000000000000000000000000000000000000000000c08411e1a300e1a001a974d0ba16f7a378867bb0dd359d78fdf831a2b73823a8a1eea34e6615cb9980a02860847930ba2d79763f1c9196ad364dc7bc28f633d16cb1715a12b27a6db735a02d0434073f7d217a035e484dd7e6513bfe64035b16b06478113a07c58c79ffa9",
Expand All @@ -20,5 +20,6 @@
],
"withdrawals": [],
"excess_data_gas": "131072",
"data_gas_used": "393216"
"data_gas_used": "393216",
"parent_beacon_block_root": "0xc76b6a3716175e2d3f03d63eb75ddba0868d24a369d95416c49229c46a3370fe"
}
8 changes: 4 additions & 4 deletions packages/client/test/rpc/engine/getPayloadV3.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '0x771d2330f20db47cef611364dc643ab75e1e99159fe37dc86942ab03c6a6344b',
safeBlockHash: '0x771d2330f20db47cef611364dc643ab75e1e99159fe37dc86942ab03c6a6344b',
finalizedBlockHash: '0x771d2330f20db47cef611364dc643ab75e1e99159fe37dc86942ab03c6a6344b',
headBlockHash: '0xb5785cb83fccc2280113e494cad4f6659eb73977421a78588b6e251a0563d9da',
safeBlockHash: '0xb5785cb83fccc2280113e494cad4f6659eb73977421a78588b6e251a0563d9da',
finalizedBlockHash: '0xb5785cb83fccc2280113e494cad4f6659eb73977421a78588b6e251a0563d9da',
}
const validPayloadAttributes = {
timestamp: '0x2f',
Expand Down Expand Up @@ -116,7 +116,7 @@ tape(`${method}: call with known payload`, async (t) => {
const { executionPayload, blobsBundle } = res.body.result
t.equal(
executionPayload.blockHash,
'0x9db3128f029d4043d32786a8896fbaadac4c07ec475213a43534ec06079f08b1',
'0x0a4f946a9dac3f6d2b86d02dfa6cf221b4fe72bbaff51b50cee4c5784156dd52',
'built expected block'
)
t.equal(executionPayload.excessDataGas, '0x0', 'correct execess data gas')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ tape(`${method}: Cancun validations`, (v1) => {
{
...blockData,
parentHash: '0x2559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858',
blockHash: '0x5ffbb3eef91d4dfbc8d02309cb7e8824040f823707dc234b1727ab14a8ecf0ff',
blockHash: '0x42942949c4ed512cd85c2cb54ca88591338cbb0564d3a2bea7961a639ef29d64',
withdrawals: [],
dataGasUsed: '0x0',
excessDataGas: '0x0',
Expand Down Expand Up @@ -78,7 +78,7 @@ tape(`${method}: Cancun validations`, (v1) => {
{
...blockData,
parentHash: '0x2559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858',
blockHash: '0x8346f46bbac879e28dd521762b50de3946fbdb84d1af466eeb1f3c4c9893abff',
blockHash: '0x141462264b2c27594e8cfcafcadd3545e08c657af4e5882096191632dd4cfc1c',
withdrawals: [],
dataGasUsed: '0x40000',
excessDataGas: '0x0',
Expand All @@ -101,7 +101,7 @@ tape(`${method}: Cancun validations`, (v1) => {
{
...blockData,
parentHash: '0x2559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858',
blockHash: '0x8346f46bbac879e28dd521762b50de3946fbdb84d1af466eeb1f3c4c9893abff',
blockHash: '0x141462264b2c27594e8cfcafcadd3545e08c657af4e5882096191632dd4cfc1c',
withdrawals: [],
dataGasUsed: '0x40000',
excessDataGas: '0x0',
Expand All @@ -124,7 +124,7 @@ tape(`${method}: Cancun validations`, (v1) => {
{
...blockData,
parentHash: '0x2559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858',
blockHash: '0x8346f46bbac879e28dd521762b50de3946fbdb84d1af466eeb1f3c4c9893abff',
blockHash: '0x141462264b2c27594e8cfcafcadd3545e08c657af4e5882096191632dd4cfc1c',
withdrawals: [],
dataGasUsed: '0x40000',
excessDataGas: '0x0',
Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/hardforks/cancun.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "cancun",
"comment": "Next feature hardfork after the shanghai having proto-danksharding EIP 4844 blobs (still WIP hence not for production use)",
"comment": "Next feature hardfork after the shanghai having proto-danksharding EIP 4844 blobs (still WIP hence not for production use), transient storage opcodes, parent beacon block root availability in EVM and selfdestruct only in same transaction",
"url": "https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/cancun.md",
"status": "Experimental",
"eips": [4844]
"eips": [4844, 1153, 4788, 5656, 6780]
}
42 changes: 38 additions & 4 deletions packages/vm/src/buildBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,23 @@ import { ConsensusType } from '@ethereumjs/common'
import { RLP } from '@ethereumjs/rlp'
import { Trie } from '@ethereumjs/trie'
import { BlobEIP4844Transaction } from '@ethereumjs/tx'
import { Address, GWEI_TO_WEI, TypeOutput, Withdrawal, toBytes, toType } from '@ethereumjs/util'
import {
Address,
GWEI_TO_WEI,
TypeOutput,
Withdrawal,
toBytes,
toType,
zeros,
} from '@ethereumjs/util'

import { Bloom } from './bloom/index.js'
import { calculateMinerReward, encodeReceipt, rewardAccount } from './runBlock.js'
import {
accumulateParentBeaconBlockRoot,
calculateMinerReward,
encodeReceipt,
rewardAccount,
} from './runBlock.js'

import type { BuildBlockOpts, BuilderOpts, RunTxResult, SealBlockOpts } from './types.js'
import type { VM } from './vm.js'
Expand Down Expand Up @@ -64,6 +77,7 @@ export class BlockBuilder {
parentHash: opts.parentBlock.hash(),
number: opts.headerData?.number ?? opts.parentBlock.header.number + BigInt(1),
gasLimit: opts.headerData?.gasLimit ?? opts.parentBlock.header.gasLimit,
timestamp: opts.headerData?.timestamp ?? Math.round(Date.now() / 1000),
}
this.withdrawals = opts.withdrawals?.map(Withdrawal.fromWithdrawalData)

Expand Down Expand Up @@ -274,7 +288,8 @@ export class BlockBuilder {
const receiptTrie = await this.receiptTrie()
const logsBloom = this.logsBloom()
const gasUsed = this.gasUsed
const timestamp = this.headerData.timestamp ?? Math.round(Date.now() / 1000)
// timestamp should already be set in constructor
const timestamp = this.headerData.timestamp ?? BigInt(0)

let dataGasUsed = undefined
if (this.vm.common.isActivatedEIP(4844) === true) {
Expand Down Expand Up @@ -318,9 +333,28 @@ export class BlockBuilder {

return block
}

async initState() {
if (this.vm.common.isActivatedEIP(4788)) {
if (!this.checkpointed) {
await this.vm.evm.journal.checkpoint()
this.checkpointed = true
}

const { parentBeaconBlockRoot, timestamp } = this.headerData
// timestamp should already be set in constructor
const timestampBigInt = toType(timestamp ?? 0, TypeOutput.BigInt)
const parentBeaconBlockRootBuf =
toType(parentBeaconBlockRoot!, TypeOutput.Uint8Array) ?? zeros(32)

await accumulateParentBeaconBlockRoot.bind(this.vm)(parentBeaconBlockRootBuf, timestampBigInt)
}
}
}

export async function buildBlock(this: VM, opts: BuildBlockOpts): Promise<BlockBuilder> {
// let opts override excessDataGas if there is some value passed there
return new BlockBuilder(this, opts)
const blockBuilder = new BlockBuilder(this, opts)
await blockBuilder.initState()
return blockBuilder
}
65 changes: 39 additions & 26 deletions packages/vm/src/runBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,35 +262,15 @@ async function applyBlock(this: VM, block: Block, opts: RunBlockOpts) {
}
}
if (this.common.isActivatedEIP(4788)) {
// Save the parentBeaconBlockRoot to the beaconroot stateful precompile ring buffers
const root = block.header.parentBeaconBlockRoot!
const timestamp = block.header.timestamp
const historicalRootsLength = BigInt(this.common.param('vm', 'historicalRootsLength'))
const timestampIndex = timestamp % historicalRootsLength
const timestampExtended = timestampIndex + historicalRootsLength

/**
* Note: (by Jochem)
* If we don't do this (put account if undefined / non-existant), block runner crashes because the beacon root address does not exist
* This is hence (for me) again a reason why it should /not/ throw if the address does not exist
* All ethereum accounts have empty storage by default
*/

if ((await this.stateManager.getAccount(parentBeaconBlockRootAddress)) === undefined) {
await this.stateManager.putAccount(parentBeaconBlockRootAddress, new Account())
if (this.DEBUG) {
debug(`accumulate parentBeaconBlockRoot`)
}

await this.stateManager.putContractStorage(
parentBeaconBlockRootAddress,
setLengthLeft(bigIntToBytes(timestampIndex), 32),
bigIntToBytes(block.header.timestamp)
)
await this.stateManager.putContractStorage(
parentBeaconBlockRootAddress,
setLengthLeft(bigIntToBytes(timestampExtended), 32),
root
await accumulateParentBeaconBlockRoot.bind(this)(
block.header.parentBeaconBlockRoot!,
block.header.timestamp
)
}

// Apply transactions
if (this.DEBUG) {
debug(`Apply transactions`)
Expand All @@ -308,6 +288,39 @@ async function applyBlock(this: VM, block: Block, opts: RunBlockOpts) {
return blockResults
}

export async function accumulateParentBeaconBlockRoot(
this: VM,
root: Uint8Array,
timestamp: bigint
) {
// Save the parentBeaconBlockRoot to the beaconroot stateful precompile ring buffers
const historicalRootsLength = BigInt(this.common.param('vm', 'historicalRootsLength'))
const timestampIndex = timestamp % historicalRootsLength
const timestampExtended = timestampIndex + historicalRootsLength

/**
* Note: (by Jochem)
* If we don't do this (put account if undefined / non-existant), block runner crashes because the beacon root address does not exist
* This is hence (for me) again a reason why it should /not/ throw if the address does not exist
* All ethereum accounts have empty storage by default
*/

if ((await this.stateManager.getAccount(parentBeaconBlockRootAddress)) === undefined) {
await this.stateManager.putAccount(parentBeaconBlockRootAddress, new Account())
}

await this.stateManager.putContractStorage(
parentBeaconBlockRootAddress,
setLengthLeft(bigIntToBytes(timestampIndex), 32),
bigIntToBytes(timestamp)
)
await this.stateManager.putContractStorage(
parentBeaconBlockRootAddress,
setLengthLeft(bigIntToBytes(timestampExtended), 32),
root
)
}

/**
* Applies the transactions in a block, computing the receipts
* as well as gas usage and some relevant data. This method is
Expand Down
12 changes: 9 additions & 3 deletions packages/vm/test/api/EIPs/eip-4844-blobs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
hexToBytes,
initKZG,
privateToAddress,
zeros,
} from '@ethereumjs/util'
import * as kzg from 'c-kzg'
import { assert, describe, it } from 'vitest'
Expand All @@ -34,9 +35,14 @@ if (isBrowser() === false) {

describe('EIP4844 tests', () => {
it('should build a block correctly with blobs', async () => {
const common = Common.fromGethGenesis(genesisJSON, { chain: 'eip4844' })
common.setHardfork(Hardfork.Cancun)
const genesisBlock = Block.fromBlockData({ header: { gasLimit: 50000 } }, { common })
const common = Common.fromGethGenesis(genesisJSON, {
chain: 'eip4844',
hardfork: Hardfork.Cancun,
})
const genesisBlock = Block.fromBlockData(
{ header: { gasLimit: 50000, parentBeaconBlockRoot: zeros(32) } },
{ common }
)
const blockchain = await Blockchain.create({
genesisBlock,
common,
Expand Down