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
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
127 changes: 127 additions & 0 deletions packages/vm/src/requests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
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)) {
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 CLRequest(withdrawalRequestType, rlpData)
requests.push(request)
}
}
}

export const DEPOSIT_CONTRACT_ADDRESS = '0x00000000219ab540356cBB839Cbe05303d7705Fa'
Copy link
Contributor

@g11tech g11tech May 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should move this into Chain struct or in the chains field and do getParams since each chain will have its own address

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()) {
// 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 = bytesToHex(log[2].slice(pubKeyIdx + 32, pubKeyIdx + 32 + pubKeySize))
const withdrawalCreds = bytesToHex(
log[2].slice(withdrawalCredsIdx + 32, withdrawalCredsIdx + 32 + withdrawalCredsSize)
)
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]))
)
}
}
}
}
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]))
)
}
}
}
}
11 changes: 3 additions & 8 deletions packages/vm/test/api/EIPs/eip-6110.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Account, Address, bytesToHex, hexToBytes, randomBytes } from '@ethereum
import { keccak256 } from 'ethereum-cryptography/keccak.js'
import { assert, describe, it } from 'vitest'

import { DEPOSIT_CONTRACT_ADDRESS } from '../../../src/runBlock.js'
import { DEPOSIT_CONTRACT_ADDRESS } from '../../../src/requests.js'
import { setupVM } from '../utils.js'

const depositContractByteCode = hexToBytes(
Expand Down Expand Up @@ -79,13 +79,8 @@ describe('EIP-7685 buildBlock tests', () => {
sender,
Account.fromAccountData({ balance: 540000000030064771065n })
)
const block = Block.fromBlockData(
{
transactions: [],
},
{ common }
)
vm.blockchain['dbManager']['getHeader'] = () => block.header
const block = Block.fromBlockData({}, { common })
;(vm.blockchain as any)['dbManager']['getHeader'] = () => block.header
const blockBuilder = await vm.buildBlock({ parentBlock: block })
await blockBuilder.addTransaction(depositTx)
const res = await blockBuilder.build()
Expand Down
Loading