Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
7 changes: 6 additions & 1 deletion packages/client/src/miner/pendingBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,12 @@ export class PendingBlock {
}
const blockStatus = builder.getStatus()
if (blockStatus.status === BuildStatus.Build) {
return [blockStatus.block, builder.transactionReceipts, builder.minerValue]
return [
blockStatus.block,
builder.transactionReceipts,
builder.minerValue,
this.blobsBundles.get(payloadId),
]
}
const { vm, headerData } = builder as unknown as { vm: VM; headerData: HeaderData }

Expand Down
12 changes: 12 additions & 0 deletions packages/client/src/rpc/error-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,15 @@ export const INVALID_PARAMS = -32602
export const INTERNAL_ERROR = -32603
export const TOO_LARGE_REQUEST = -38004
export const UNSUPPORTED_FORK = -38005
export const UNKNOWN_PAYLOAD = -32001

export const validEngineCodes = [
PARSE_ERROR,
INVALID_REQUEST,
METHOD_NOT_FOUND,
INVALID_PARAMS,
INTERNAL_ERROR,
TOO_LARGE_REQUEST,
UNSUPPORTED_FORK,
UNKNOWN_PAYLOAD,
]
143 changes: 121 additions & 22 deletions packages/client/src/rpc/modules/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ import {

import { PendingBlock } from '../../miner'
import { short } from '../../util'
import { INTERNAL_ERROR, INVALID_PARAMS, TOO_LARGE_REQUEST, UNSUPPORTED_FORK } from '../error-code'
import {
INTERNAL_ERROR,
INVALID_PARAMS,
TOO_LARGE_REQUEST,
UNKNOWN_PAYLOAD,
UNSUPPORTED_FORK,
validEngineCodes,
} from '../error-code'
import { CLConnectionManager, middleware as cmMiddleware } from '../util/CLConnectionManager'
import { middleware, validators } from '../validation'

Expand All @@ -24,6 +31,7 @@ import type { VMExecution } from '../../execution'
import type { BlobsBundle } from '../../miner'
import type { FullEthereumService } from '../../service'
import type { ExecutionPayload } from '@ethereumjs/block'
import type { Common } from '@ethereumjs/common'
import type { VM } from '@ethereumjs/vm'

const zeroBlockHash = zeros(32)
Expand Down Expand Up @@ -103,7 +111,7 @@ type ExecutionPayloadBodyV1 = {

const EngineError = {
UnknownPayload: {
code: -32001,
code: UNKNOWN_PAYLOAD,
message: 'Unknown payload',
},
}
Expand Down Expand Up @@ -360,6 +368,34 @@ const getPayloadBody = (block: Block): ExecutionPayloadBodyV1 => {
}
}

function validateHardforkRange(
chainCommon: Common,
methodVersion: number,
checkNotBeforeHf: Hardfork | null,
checkNotAfterHf: Hardfork | null,
timestamp: bigint
) {
if (checkNotBeforeHf !== null) {
const hfTimeStamp = chainCommon.hardforkTimestamp(checkNotBeforeHf)
if (hfTimeStamp !== null && timestamp < hfTimeStamp) {
throw {
code: UNSUPPORTED_FORK,
message: `V${methodVersion} cannot be called pre-${checkNotBeforeHf}`,
}
}
}

if (checkNotAfterHf !== null) {
const nextHFTimestamp = chainCommon.nextHardforkBlockOrTimestamp(checkNotAfterHf)
if (nextHFTimestamp !== null && timestamp >= nextHFTimestamp) {
throw {
code: UNSUPPORTED_FORK,
message: `V${methodVersion + 1} MUST be called post-${checkNotAfterHf}`,
}
}
}
}

/**
* engine_* RPC module
* @memberof module:rpc/modules
Expand Down Expand Up @@ -1090,6 +1126,23 @@ export class Engine {
private async forkchoiceUpdatedV1(
params: [forkchoiceState: ForkchoiceStateV1, payloadAttributes: PayloadAttributesV1 | undefined]
): Promise<ForkchoiceResponseV1 & { headBlock?: Block }> {
const payloadAttributes = params[1]
if (payloadAttributes !== undefined && payloadAttributes !== null) {
if (Object.keys(payloadAttributes).length > 3) {
throw {
code: INVALID_PARAMS,
message: 'PayloadAttributesV1 MUST be used for forkchoiceUpdatedV2',
}
}
validateHardforkRange(
this.chain.config.chainCommon,
1,
Copy link
Member

Choose a reason for hiding this comment

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

I think it is somewhat implicit, but this check for forkchoiceUpdatedV1 does not seem to be in the engine api specs. It states that it must be updated for engine_forkchoiceUpdatedV2 (so if you call it >= Cancun it throws Unsupported fork) but by spec currently it does not list engine_forkchiceUpdatedV1, see https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#update-the-methods-of-previous-forks

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmmm yes, should we PR to update specs because obviously lower version can't be used in higher version fork because of missing fields

Copy link
Member

Choose a reason for hiding this comment

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

I think there are some small-ish problems wiht the current spec and those changes:

  1. We have 3 files, Paris / Shanghai / Cancun. In Cancun we introduce new rules which override the rules of the other files (this becomes clear once you read all files). The rule which it overrides if for example this Shanghai rule: Client software MUST return -32602: Invalid params error if the wrong version of the structure is used in the method call. (of engine_newPayloadV2)
  2. And building upon the previous: in Cancun we should use INVALID_PARAMS if we call engine_newPayloadV1 (if we are on Shanghai), but this thus implies we are aware (and ready) for Cancun. If we don't know about Cancun and we want to change the spec, then now the behavior changes: if we are on Shanghai, and we call engine_newPayloadV1, then we should return UNSUPPORTED_FORK.

So if we change the spec this starts to get somewhat confusing. I do think we should change the spec though because it is more logical. I am also not aware what happens if we change the behavior, are there CLs relying on exact error codes to change behavior, or do they treat each error as an error in general?

Copy link
Contributor Author

@g11tech g11tech Aug 21, 2023

Choose a reason for hiding this comment

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

may be we can address those issues separately, for now we can may be merge this as it passes cancun fully (and V1 checks also makes sense)

null,
Hardfork.Paris,
BigInt(payloadAttributes.timestamp)
)
}

return this.forkchoiceUpdated(params)
}

Expand All @@ -1101,6 +1154,24 @@ export class Engine {
): Promise<ForkchoiceResponseV1 & { headBlock?: Block }> {
const payloadAttributes = params[1]
if (payloadAttributes !== undefined && payloadAttributes !== null) {
if (
Object.values(payloadAttributes).filter((attr) => attr !== null && attr !== undefined)
.length > 4
) {
throw {
code: INVALID_PARAMS,
message: 'PayloadAttributesV{1|2} MUST be used for forkchoiceUpdatedV2',
}
}

validateHardforkRange(
this.chain.config.chainCommon,
2,
null,
Hardfork.Shanghai,
BigInt(payloadAttributes.timestamp)
)

const shanghaiTimestamp = this.chain.config.chainCommon.hardforkTimestamp(Hardfork.Shanghai)
const ts = BigInt(payloadAttributes.timestamp)
const withdrawals = (payloadAttributes as PayloadAttributesV2).withdrawals
Expand Down Expand Up @@ -1137,14 +1208,24 @@ export class Engine {
): Promise<ForkchoiceResponseV1 & { headBlock?: Block }> {
const payloadAttributes = params[1]
if (payloadAttributes !== undefined && payloadAttributes !== null) {
const cancunTimestamp = this.chain.config.chainCommon.hardforkTimestamp(Hardfork.Cancun)
const ts = BigInt(payloadAttributes.timestamp)
if (ts < cancunTimestamp!) {
if (
Object.values(payloadAttributes).filter((attr) => attr !== null && attr !== undefined)
.length > 5
) {
throw {
code: UNSUPPORTED_FORK,
message: 'PayloadAttributesV{1|2} MUST be used before Cancun is activated',
code: INVALID_PARAMS,
message: 'PayloadAttributesV3 MUST be used for forkchoiceUpdatedV3',
}
}

validateHardforkRange(
this.chain.config.chainCommon,
3,
Hardfork.Cancun,
// this could be valid post cancun as well, if not then update the valid till hf here
null,
BigInt(payloadAttributes.timestamp)
)
}

return this.forkchoiceUpdated(params)
Expand All @@ -1158,7 +1239,7 @@ export class Engine {
* 1. payloadId: DATA, 8 bytes - identifier of the payload building process
* @returns Instance of {@link ExecutionPayloadV1} or an error
*/
private async getPayload(params: [Bytes8], payloadVersion?: number) {
private async getPayload(params: [Bytes8], payloadVersion: number) {
const payloadId = params[0]
try {
const built = await this.pendingBlock.build(payloadId)
Expand All @@ -1178,22 +1259,40 @@ export class Engine {
this.executedBlocks.set(bytesToUnprefixedHex(block.hash()), block)
const executionPayload = blockToExecutionPayload(block, value, blobs)

if (payloadVersion === 3) {
const cancunTimestamp = this.chain.config.chainCommon.hardforkTimestamp(Hardfork.Cancun)
if (
cancunTimestamp !== null &&
BigInt(executionPayload.executionPayload.timestamp) < cancunTimestamp
) {
throw {
code: UNSUPPORTED_FORK,
message: 'getPayloadV3 cannot be called on payloads pre-Cancun',
}
}
let checkNotBeforeHf: Hardfork | null
let checkNotAfterHf: Hardfork | null

switch (payloadVersion) {
case 3:
checkNotBeforeHf = Hardfork.Cancun
checkNotAfterHf = Hardfork.Cancun
break

case 2:
// no checks to be done for before as valid till paris
checkNotBeforeHf = null
checkNotAfterHf = Hardfork.Shanghai
break

case 1:
checkNotBeforeHf = null
checkNotAfterHf = Hardfork.Paris
break

default:
throw Error(`Invalid payloadVersion=${payloadVersion}`)
}

validateHardforkRange(
Copy link
Member

Choose a reason for hiding this comment

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

Same here with engine_getPayloadV1

this.chain.config.chainCommon,
payloadVersion,
checkNotBeforeHf,
checkNotAfterHf,
BigInt(executionPayload.executionPayload.timestamp)
)
return executionPayload
} catch (error: any) {
if (error === EngineError.UnknownPayload) throw error
if (validEngineCodes.includes(error.code)) throw error
throw {
code: INTERNAL_ERROR,
message: error.message ?? error,
Expand All @@ -1202,12 +1301,12 @@ export class Engine {
}

async getPayloadV1(params: [Bytes8]) {
const { executionPayload } = await this.getPayload(params)
const { executionPayload } = await this.getPayload(params, 1)
Copy link
Member

Choose a reason for hiding this comment

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

Same comment here

return executionPayload
}

async getPayloadV2(params: [Bytes8]) {
const { executionPayload, blockValue } = await this.getPayload(params)
const { executionPayload, blockValue } = await this.getPayload(params, 2)
return { executionPayload, blockValue }
}

Expand Down