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

CLRequests cleanup #3393

Merged
merged 10 commits into from
May 3, 2024
5 changes: 5 additions & 0 deletions packages/common/src/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const chains: ChainsDict = {
nonce: '0x0000000000000042',
extraData: '0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa',
},
depositContractAddress: '0x00000000219ab540356cBB839Cbe05303d7705Fa',
hardforks: [
{
name: 'chainstart',
Expand Down Expand Up @@ -119,6 +120,10 @@ export const chains: ChainsDict = {
timestamp: '1710338135',
forkHash: '0x9f3d2254',
},
{
name: 'prague',
block: null,
},
],
bootstrapNodes: [
{
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/hardforks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@ export const hardforks: HardforksDict = {
'Next feature hardfork after cancun, internally used for pectra testing/implementation (incomplete/experimental)',
url: 'https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/prague.md',
status: Status.Draft,
eips: [2537, 2935, 3074, 6110, 7002, 7685],
eips: [2537, 3074, 6110, 7002, 7685],
},
osaka: {
name: 'osaka',
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface ChainConfig {
bootstrapNodes: BootstrapNodeConfig[]
dnsNetworks?: string[]
consensus: ConsensusConfig
depositContractAddress?: string
}

// TODO: Remove the string type and only keep PrefixedHexString
Expand Down
2 changes: 1 addition & 1 deletion packages/vm/src/buildBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import {
} from '@ethereumjs/util'

import { Bloom } from './bloom/index.js'
import { accumulateRequests } from './requests.js'
import {
accumulateParentBeaconBlockRoot,
accumulateParentBlockHash,
accumulateRequests,
calculateMinerReward,
encodeReceipt,
rewardAccount,
Expand Down
137 changes: 137 additions & 0 deletions packages/vm/src/requests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { Common } from '@ethereumjs/common'
import { RLP } from '@ethereumjs/rlp'
import {
Address,
CLRequest,
bigIntToBytes,
bytesToHex,
bytesToInt,
setLengthLeft,
} from '@ethereumjs/util'

import type { RunTxResult } from './types'
import type { VM } from './vm.js'

/**
* This helper method generates a list of all CL requests that can be included in a pending block
* @param vm VM instance (used in deriving partial withdrawal requests)
* @param txResults (used in deriving deposit requests)
* @returns a list of CL requests in ascending order by type
*/
export const accumulateRequests = async (
vm: VM,
txResults: RunTxResult[]
): Promise<CLRequest[]> => {
const requests: CLRequest[] = []
const common = vm.common

if (common.isActivatedEIP(6110)) {
const depositContractAddress =
Common.getInitializedChains()[vm.common.chainName()]?.depositContractAddress ??
Common.getInitializedChains().mainnet.depositContractAddress
if (depositContractAddress === undefined)
throw new Error('deposit contract address required with EIP 6110')
await accumulateDeposits(depositContractAddress, txResults, requests)
}

if (common.isActivatedEIP(7002)) {
await accumulateEIP7002Requests(vm, requests)
}

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 in ascending order')
}
}
return requests
}

const accumulateEIP7002Requests = async (vm: VM, requests: CLRequest[]): Promise<void> => {
// Partial withdrawals logic
const addressBytes = setLengthLeft(
bigIntToBytes(vm.common.param('vm', 'withdrawalRequestPredeployAddress')),
20
)
const withdrawalsAddress = Address.fromString(bytesToHex(addressBytes))

const code = await vm.stateManager.getContractCode(withdrawalsAddress)

if (code.length === 0) {
throw new Error(
'Attempt to accumulate EIP-7002 requests failed: the contract does not exist. Ensure the deployment tx has been run, or that the required contract code is stored'
)
}

const systemAddressBytes = setLengthLeft(
bigIntToBytes(vm.common.param('vm', 'systemAddress')),
20
)
const systemAddress = Address.fromString(bytesToHex(systemAddressBytes))

const results = await vm.evm.runCall({
caller: systemAddress,
gasLimit: BigInt(1_000_000),
to: withdrawalsAddress,
})

const resultsBytes = results.execResult.returnValue
if (resultsBytes.length > 0) {
const withdrawalRequestType = Number(vm.common.param('vm', 'withdrawalRequestType'))
// Each request is 76 bytes
for (let startByte = 0; startByte < resultsBytes.length; startByte += 76) {
const slicedBytes = resultsBytes.slice(startByte, startByte + 76)
const sourceAddress = slicedBytes.slice(0, 20) // 20 Bytes
const validatorPubkey = slicedBytes.slice(20, 68) // 48 Bytes
const amount = slicedBytes.slice(68, 76) // 8 Bytes / Uint64
const rlpData = RLP.encode([sourceAddress, validatorPubkey, amount])
const request = new CLRequest(withdrawalRequestType, rlpData)
requests.push(request)
}
}
}

const accumulateDeposits = async (
depositContractAddress: string,
txResults: RunTxResult[],
requests: CLRequest[]
) => {
for (const [_, tx] of txResults.entries()) {
for (let i = 0; i < tx.receipt.logs.length; i++) {
const log = tx.receipt.logs[i]
if (bytesToHex(log[0]).toLowerCase() === depositContractAddress.toLowerCase()) {
// Extracts validator pubkey, withdrawal credential, deposit amount, signature,
// and validator index from Deposit Event log.
// The event fields are non-indexed so contained in one byte array (log[2]) so parsing is as follows:
// 1. Read the first 32 bytes to get the starting position of the first field.
// 2. Continue reading the byte array in 32 byte increments to get all the field starting positions
// 3. Read 32 bytes starting with the first field position to get the size of the first field
// 4. Read the bytes from first field position + 32 + the size of the first field to get the first field value
// 5. Repeat steps 3-4 for each field
const pubKeyIdx = bytesToInt(log[2].slice(0, 32))
const pubKeySize = bytesToInt(log[2].slice(pubKeyIdx, pubKeyIdx + 32))
const withdrawalCredsIdx = bytesToInt(log[2].slice(32, 64))
const withdrawalCredsSize = bytesToInt(
log[2].slice(withdrawalCredsIdx, withdrawalCredsIdx + 32)
)
const amountIdx = bytesToInt(log[2].slice(64, 96))
const amountSize = bytesToInt(log[2].slice(amountIdx, amountIdx + 32))
const sigIdx = bytesToInt(log[2].slice(96, 128))
const sigSize = bytesToInt(log[2].slice(sigIdx, sigIdx + 32))
const indexIdx = bytesToInt(log[2].slice(128, 160))
const indexSize = bytesToInt(log[2].slice(indexIdx, indexIdx + 32))
const pubkey = log[2].slice(pubKeyIdx + 32, pubKeyIdx + 32 + pubKeySize)
const withdrawalCreds = log[2].slice(
withdrawalCredsIdx + 32,
withdrawalCredsIdx + 32 + withdrawalCredsSize
)
const amount = log[2].slice(amountIdx + 32, amountIdx + 32 + amountSize)
const signature = log[2].slice(sigIdx + 32, sigIdx + 32 + sigSize)
const index = log[2].slice(indexIdx + 32, indexIdx + 32 + indexSize)
requests.push(
new CLRequest(0x0, RLP.encode([pubkey, withdrawalCreds, amount, signature, index]))
)
}
}
}
}
119 changes: 2 additions & 117 deletions packages/vm/src/runBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ import {
BIGINT_0,
BIGINT_1,
BIGINT_8,
CLRequest,
GWEI_TO_WEI,
KECCAK256_RLP,
bigIntToBytes,
bigIntToHex,
bytesToHex,
bytesToInt,
concatBytes,
equalsBytes,
hexToBytes,
Expand All @@ -28,6 +26,7 @@ import {
import debugDefault from 'debug'

import { Bloom } from './bloom/index.js'
import { accumulateRequests } from './requests.js'

import type {
AfterBlockEvent,
Expand All @@ -42,7 +41,7 @@ import type {
import type { VM } from './vm.js'
import type { Common } from '@ethereumjs/common'
import type { EVM, EVMInterface } from '@ethereumjs/evm'
import type { CLRequestType, PrefixedHexString } from '@ethereumjs/util'
import type { CLRequest, PrefixedHexString } from '@ethereumjs/util'

const { debug: createDebugLogger } = debugDefault

Expand Down Expand Up @@ -953,117 +952,3 @@ const DAOConfig = {
],
DAORefundContract: 'bf4ed7b27f1d666546e30d74d50d173d20bca754',
}

export class ValidatorWithdrawalRequest extends CLRequest implements CLRequestType {
constructor(type: number, bytes: Uint8Array) {
super(type, bytes)
}

serialize() {
return concatBytes(Uint8Array.from([this.type]), this.bytes)
}
}

/**
* This helper method generates a list of all CL requests that can be included in a pending block
* @param _vm VM instance from which to derive CL requests
* @returns an list of CL requests in ascending order by type
*/
export const accumulateRequests = async (
vm: VM,
txResults: RunTxResult[]
): Promise<CLRequest[]> => {
const requests: CLRequest[] = []
const common = vm.common

if (common.isActivatedEIP(6110)) {
await accumulateDeposits(txResults, requests)
}

if (common.isActivatedEIP(7002)) {
await _accumulateEIP7002Requests(vm, requests)
}

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 in ascending order')
}
}
return requests
}

const _accumulateEIP7002Requests = async (vm: VM, requests: CLRequest[]): Promise<void> => {
// Partial withdrawals logic
const addressBytes = setLengthLeft(
bigIntToBytes(vm.common.param('vm', 'withdrawalRequestPredeployAddress')),
20
)
const withdrawalsAddress = Address.fromString(bytesToHex(addressBytes))

const code = await vm.stateManager.getContractCode(withdrawalsAddress)

if (code.length === 0) {
throw new Error(
'Attempt to accumulate EIP-7002 requests failed: the contract does not exist. Ensure the deployment tx has been run, or that the required contract code is stored'
)
}

const systemAddressBytes = setLengthLeft(
bigIntToBytes(vm.common.param('vm', 'systemAddress')),
20
)
const systemAddress = Address.fromString(bytesToHex(systemAddressBytes))

const results = await vm.evm.runCall({
caller: systemAddress,
gasLimit: BigInt(1_000_000),
to: withdrawalsAddress,
})

const resultsBytes = results.execResult.returnValue
if (resultsBytes.length > 0) {
const withdrawalRequestType = Number(vm.common.param('vm', 'withdrawalRequestType'))
// Each request is 76 bytes
for (let startByte = 0; startByte < resultsBytes.length; startByte += 76) {
const slicedBytes = resultsBytes.slice(startByte, startByte + 76)
const sourceAddress = slicedBytes.slice(0, 20) // 20 Bytes
const validatorPubkey = slicedBytes.slice(20, 68) // 48 Bytes
const amount = slicedBytes.slice(68, 76) // 8 Bytes / Uint64
const rlpData = RLP.encode([sourceAddress, validatorPubkey, amount])
const request = new ValidatorWithdrawalRequest(withdrawalRequestType, rlpData)
requests.push(request)
}
}
}

export const DEPOSIT_CONTRACT_ADDRESS = '0x00000000219ab540356cBB839Cbe05303d7705Fa'
const accumulateDeposits = async (txResults: RunTxResult[], requests: CLRequest[]) => {
for (const [_, tx] of txResults.entries()) {
for (let i = 0; i < tx.receipt.logs.length; i++) {
const log = tx.receipt.logs[i]
if (bytesToHex(log[0]).toLowerCase() === DEPOSIT_CONTRACT_ADDRESS.toLowerCase()) {
const pubKeyIdx = bytesToInt(log[2].slice(0, 32))
const pubKeySize = bytesToInt(log[2].slice(pubKeyIdx, pubKeyIdx + 32))
const withcredsIdx = bytesToInt(log[2].slice(32, 64))
const withcredsSize = bytesToInt(log[2].slice(withcredsIdx, withcredsIdx + 32))
const amountIdx = bytesToInt(log[2].slice(64, 96))
const amountSize = bytesToInt(log[2].slice(amountIdx, amountIdx + 32))
const sigIdx = bytesToInt(log[2].slice(96, 128))
const sigSize = bytesToInt(log[2].slice(sigIdx, sigIdx + 32))
const indexIdx = bytesToInt(log[2].slice(128, 160))
const indexSize = bytesToInt(log[2].slice(indexIdx, indexIdx + 32))
const pubkey = bytesToHex(log[2].slice(pubKeyIdx + 32, pubKeyIdx + 32 + pubKeySize))
const withdrawalCreds = bytesToHex(
log[2].slice(withcredsIdx + 32, withcredsIdx + 32 + withcredsSize)
)
const amount = bytesToHex(log[2].slice(amountIdx + 32, amountIdx + 32 + amountSize))
const signature = bytesToHex(log[2].slice(sigIdx + 32, sigIdx + 32 + sigSize))
const index = bytesToHex(log[2].slice(indexIdx + 32, indexIdx + 32 + indexSize))
requests.push(
new CLRequest(0x0, RLP.encode([pubkey, withdrawalCreds, amount, signature, index]))
)
}
}
}
}
Loading
Loading