Skip to content

Commit a62ed49

Browse files
committed
fix the newpayload engine codeflow to validate the cl requests
1 parent 9b5caf2 commit a62ed49

File tree

2 files changed

+74
-40
lines changed

2 files changed

+74
-40
lines changed

packages/client/src/rpc/modules/engine/engine.ts

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import {
3232
recursivelyFindParents,
3333
validExecutedChainBlock,
3434
validHash,
35-
validate4844BlobVersionedHashes,
3635
validateHardforkRange,
3736
} from './util/index.js'
3837
import {
@@ -374,6 +373,9 @@ export class Engine {
374373
...payload,
375374
// ExecutionPayload only handles undefined
376375
parentBeaconBlockRoot: parentBeaconBlockRoot ?? undefined,
376+
},
377+
{
378+
blobVersionedHashes: blobVersionedHashes ?? undefined,
377379
executionRequests: executionRequests ?? undefined,
378380
},
379381
this.chain,
@@ -395,41 +397,6 @@ export class Engine {
395397
return response
396398
}
397399

398-
/**
399-
* Validate blob versioned hashes in the context of EIP-4844 blob transactions
400-
*/
401-
if (headBlock.common.isActivatedEIP(4844)) {
402-
let validationError: string | null = null
403-
if (blobVersionedHashes === undefined || blobVersionedHashes === null) {
404-
validationError = `Error verifying blobVersionedHashes: received none`
405-
} else {
406-
validationError = validate4844BlobVersionedHashes(headBlock, blobVersionedHashes)
407-
}
408-
409-
// if there was a validation error return invalid
410-
if (validationError !== null) {
411-
this.config.logger.debug(validationError)
412-
const latestValidHash = await validHash(
413-
hexToBytes(parentHash as PrefixedHexString),
414-
this.chain,
415-
this.chainCache,
416-
)
417-
const response = { status: Status.INVALID, latestValidHash, validationError }
418-
// skip marking the block invalid as this is more of a data issue from CL
419-
return response
420-
}
421-
} else if (blobVersionedHashes !== undefined && blobVersionedHashes !== null) {
422-
const validationError = `Invalid blobVersionedHashes before EIP-4844 is activated`
423-
const latestValidHash = await validHash(
424-
hexToBytes(parentHash as PrefixedHexString),
425-
this.chain,
426-
this.chainCache,
427-
)
428-
const response = { status: Status.INVALID, latestValidHash, validationError }
429-
// skip marking the block invalid as this is more of a data issue from CL
430-
return response
431-
}
432-
433400
/**
434401
* Stats and hardfork updates
435402
*/
@@ -1341,7 +1308,7 @@ export class Engine {
13411308
}
13421309
// The third arg returned is the minerValue which we will use to
13431310
// value the block
1344-
const [block, receipts, value, blobs] = built
1311+
const [block, receipts, value, blobs, requests] = built
13451312

13461313
// do a blocking call even if execution might be busy for the moment and skip putting
13471314
// it into chain till CL confirms with full data via new payload like versioned hashes
@@ -1355,7 +1322,7 @@ export class Engine {
13551322
/**
13561323
* Creates the payload in ExecutionPayloadV1 format to be returned
13571324
*/
1358-
const executionPayload = blockToExecutionPayload(block, value, blobs)
1325+
const executionPayload = blockToExecutionPayload(block, value, blobs, requests)
13591326

13601327
let checkNotBeforeHf: Hardfork | null
13611328
let checkNotAfterHf: Hardfork | null

packages/client/src/rpc/modules/engine/util/newPayload.ts

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { createBlockFromExecutionPayload } from '@ethereumjs/block'
1+
import { createBlockFromExecutionPayload, genRequestsRoot } from '@ethereumjs/block'
22
import { Blob4844Tx } from '@ethereumjs/tx'
3-
import { hexToBytes } from '@ethereumjs/util'
3+
import { bytesToHex, createCLRequest, equalsBytes, hexToBytes } from '@ethereumjs/util'
4+
import { sha256 } from 'ethereum-cryptography/sha256'
45

56
import { short } from '../../../../util/index.js'
67
import { Status } from '../types.js'
@@ -12,12 +13,18 @@ import type { ChainCache, PayloadStatusV1 } from '../types.js'
1213
import type { Block, ExecutionPayload } from '@ethereumjs/block'
1314
import type { PrefixedHexString } from '@ethereumjs/util'
1415

16+
type CLValidationData = {
17+
blobVersionedHashes?: PrefixedHexString[]
18+
executionRequests?: PrefixedHexString[]
19+
}
20+
1521
/**
1622
* Returns a block from a payload.
1723
* If errors, returns {@link PayloadStatusV1}
1824
*/
1925
export const assembleBlock = async (
2026
payload: ExecutionPayload,
27+
clValidationData: CLValidationData,
2128
chain: Chain,
2229
chainCache: ChainCache,
2330
): Promise<{ block?: Block; error?: PayloadStatusV1 }> => {
@@ -32,6 +39,47 @@ export const assembleBlock = async (
3239
// TODO: validateData is also called in applyBlock while runBlock, may be it can be optimized
3340
// by removing/skipping block data validation from there
3441
await block.validateData()
42+
43+
// Validate CL data to see if it matches with the assembled block
44+
const { blobVersionedHashes, executionRequests } = clValidationData
45+
46+
/**
47+
* Validate blob versioned hashes in the context of EIP-4844 blob transactions
48+
*/
49+
if (block.common.isActivatedEIP(4844)) {
50+
let validationError: string | null = null
51+
if (blobVersionedHashes === undefined) {
52+
validationError = `Error verifying blobVersionedHashes: received none`
53+
} else {
54+
validationError = validate4844BlobVersionedHashes(block, blobVersionedHashes)
55+
}
56+
57+
// if there was a validation error return invalid
58+
if (validationError !== null) {
59+
throw validationError
60+
}
61+
} else if (blobVersionedHashes !== undefined) {
62+
const validationError = `Invalid blobVersionedHashes before EIP-4844 is activated`
63+
throw validationError
64+
}
65+
66+
if (block.common.isActivatedEIP(7685)) {
67+
let validationError: string | null = null
68+
if (executionRequests === undefined) {
69+
validationError = `Error verifying executionRequests: received none`
70+
} else {
71+
validationError = validate7685ExecutionRequests(block, executionRequests)
72+
}
73+
74+
// if there was a validation error return invalid
75+
if (validationError !== null) {
76+
throw validationError
77+
}
78+
} else if (executionRequests !== undefined) {
79+
const validationError = `Invalid executionRequests before EIP-7685 is activated`
80+
throw validationError
81+
}
82+
3583
return { block }
3684
} catch (error) {
3785
const validationError = `Error assembling block from payload: ${error}`
@@ -82,3 +130,22 @@ export const validate4844BlobVersionedHashes = (
82130
}
83131
return validationError
84132
}
133+
134+
export const validate7685ExecutionRequests = (
135+
headBlock: Block,
136+
executionRequests: PrefixedHexString[],
137+
): string | null => {
138+
let validationError: string | null = null
139+
140+
// Collect versioned hashes in the flat array `txVersionedHashes` to match with received
141+
const requests = executionRequests.map((req) => createCLRequest(hexToBytes(req)))
142+
const sha256Function = headBlock.common.customCrypto.sha256 ?? sha256
143+
const requestsRoot = genRequestsRoot(requests, sha256Function)
144+
145+
if (!equalsBytes(requestsRoot, headBlock.header.requestsRoot!)) {
146+
validationError = `Invalid requestsRoot received=${bytesToHex(
147+
headBlock.header.requestsRoot!,
148+
)} expected=${bytesToHex(requestsRoot)}`
149+
}
150+
return validationError
151+
}

0 commit comments

Comments
 (0)