Skip to content

Commit 7f47055

Browse files
committed
client: apply engine api changes for devnet 8
1 parent 8946609 commit 7f47055

File tree

1 file changed

+91
-82
lines changed

1 file changed

+91
-82
lines changed

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

+91-82
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,32 @@ type Uint64 = string
4646
type Uint256 = string
4747

4848
type WithdrawalV1 = Exclude<ExecutionPayload['withdrawals'], undefined>[number]
49-
export type ExecutionPayloadV1 = Omit<ExecutionPayload, 'withdrawals' | 'excessDataGas'>
50-
export type ExecutionPayloadV2 = ExecutionPayload & { withdrawals: WithdrawalV1[] }
51-
export type ExecutionPayloadV3 = ExecutionPayload & { excessDataGas: Uint64; dataGasUsed: Uint64 }
49+
50+
// ExecutionPayload has higher version fields as optionals to make it easy for typescript
51+
export type ExecutionPayloadV1 = ExecutionPayload
52+
export type ExecutionPayloadV2 = ExecutionPayloadV1 & { withdrawals: WithdrawalV1[] }
53+
// parentBeaconBlockRoot comes separate in new payloads and needs to be added to payload data
54+
export type ExecutionPayloadV3 = ExecutionPayloadV2 & { excessDataGas: Uint64; dataGasUsed: Uint64 }
5255

5356
export type ForkchoiceStateV1 = {
5457
headBlockHash: Bytes32
5558
safeBlockHash: Bytes32
5659
finalizedBlockHash: Bytes32
5760
}
5861

62+
// PayloadAttributes has higher version fields as optionals to make it easy for typescript
5963
type PayloadAttributes = {
6064
timestamp: Uint64
6165
prevRandao: Bytes32
6266
suggestedFeeRecipient: Bytes20
67+
// add higher version fields as optionals to make it easy for typescript
6368
withdrawals?: WithdrawalV1[]
69+
parentBeaconBlockRoot?: Bytes32
6470
}
65-
type PayloadAttributesV1 = Omit<PayloadAttributes, 'withdrawals'>
66-
type PayloadAttributesV2 = PayloadAttributes & { withdrawals: WithdrawalV1[] }
71+
72+
type PayloadAttributesV1 = Omit<PayloadAttributes, 'withdrawals' | 'parentBeaconBlockRoot'>
73+
type PayloadAttributesV2 = PayloadAttributesV1 & { withdrawals: WithdrawalV1[] }
74+
type PayloadAttributesV3 = PayloadAttributesV2 & { parentBeaconBlockRoot: Bytes32 }
6775

6876
export type PayloadStatusV1 = {
6977
status: Status
@@ -139,9 +147,14 @@ const payloadAttributesFieldValidatorsV1 = {
139147
}
140148
const payloadAttributesFieldValidatorsV2 = {
141149
...payloadAttributesFieldValidatorsV1,
150+
// withdrawals is optional in V2 because its backward forward compatible with V1
142151
withdrawals: validators.optional(validators.array(validators.withdrawal())),
143152
}
144-
153+
const payloadAttributesFieldValidatorsV3 = {
154+
...payloadAttributesFieldValidatorsV1,
155+
withdrawals: validators.array(validators.withdrawal()),
156+
parentBeaconBlockRoot: validators.bytes32,
157+
}
145158
/**
146159
* Formats a block to {@link ExecutionPayloadV1}.
147160
*/
@@ -406,14 +419,9 @@ export class Engine {
406419

407420
this.newPayloadV3 = cmMiddleware(
408421
middleware(this.newPayloadV3.bind(this), 1, [
409-
[
410-
validators.either(
411-
validators.object(executionPayloadV1FieldValidators),
412-
validators.object(executionPayloadV2FieldValidators),
413-
validators.object(executionPayloadV3FieldValidators)
414-
),
415-
],
416-
[validators.optional(validators.array(validators.bytes32))],
422+
[validators.object(executionPayloadV3FieldValidators)],
423+
[validators.array(validators.bytes32)],
424+
[validators.bytes32],
417425
]),
418426
([payload], response) => this.connectionManager.lastNewPayload({ payload, response })
419427
)
@@ -440,14 +448,20 @@ export class Engine {
440448
]),
441449
forkchoiceUpdatedResponseCMHandler
442450
)
443-
444451
this.forkchoiceUpdatedV2 = cmMiddleware(
445452
middleware(this.forkchoiceUpdatedV2.bind(this), 1, [
446453
[validators.object(forkchoiceFieldValidators)],
447454
[validators.optional(validators.object(payloadAttributesFieldValidatorsV2))],
448455
]),
449456
forkchoiceUpdatedResponseCMHandler
450457
)
458+
this.forkchoiceUpdatedV3 = cmMiddleware(
459+
middleware(this.forkchoiceUpdatedV3.bind(this), 1, [
460+
[validators.object(forkchoiceFieldValidators)],
461+
[validators.optional(validators.object(payloadAttributesFieldValidatorsV3))],
462+
]),
463+
forkchoiceUpdatedResponseCMHandler
464+
)
451465

452466
this.getPayloadV1 = cmMiddleware(
453467
middleware(this.getPayloadV1.bind(this), 1, [[validators.bytes8]]),
@@ -516,14 +530,22 @@ export class Engine {
516530
* 3. validationError: String|null - validation error message
517531
*/
518532
private async newPayload(
519-
params: [ExecutionPayload, (Bytes32[] | null)?]
533+
params: [ExecutionPayload, (Bytes32[] | null)?, (Bytes32 | null)?]
520534
): Promise<PayloadStatusV1> {
521-
const [payload, versionedHashes] = params
535+
const [payload, versionedHashes, parentBeaconBlockRoot] = params
522536
if (this.config.synchronized) {
523537
this.connectionManager.newPayloadLog()
524538
}
525539
const { parentHash, blockHash } = payload
526-
const { block, error } = await assembleBlock(payload, this.chain)
540+
// newpayloadv3 comes with parentBeaconBlockRoot out of the payload
541+
const { block, error } = await assembleBlock(
542+
{
543+
...payload,
544+
// ExecutionPayload only handles undefined
545+
parentBeaconBlockRoot: parentBeaconBlockRoot ?? undefined,
546+
},
547+
this.chain
548+
)
527549
if (!block || error) {
528550
let response = error
529551
if (!response) {
@@ -741,14 +763,31 @@ export class Engine {
741763
}
742764

743765
async newPayloadV1(params: [ExecutionPayloadV1]): Promise<PayloadStatusV1> {
766+
const shanghaiTimestamp = this.chain.config.chainCommon.hardforkTimestamp(Hardfork.Shanghai)
767+
const ts = parseInt(params[0].timestamp)
768+
if (shanghaiTimestamp !== null && ts >= shanghaiTimestamp) {
769+
throw {
770+
code: INVALID_PARAMS,
771+
message: 'NewPayloadV2 MUST be used after Cancun is activated',
772+
}
773+
}
774+
744775
return this.newPayload(params)
745776
}
746777

747778
async newPayloadV2(params: [ExecutionPayloadV2 | ExecutionPayloadV1]): Promise<PayloadStatusV1> {
748779
const shanghaiTimestamp = this.chain.config.chainCommon.hardforkTimestamp(Hardfork.Shanghai)
780+
const eip4844Timestamp = this.chain.config.chainCommon.hardforkTimestamp(Hardfork.Cancun)
781+
const ts = parseInt(params[0].timestamp)
782+
749783
const withdrawals = (params[0] as ExecutionPayloadV2).withdrawals
750784

751-
if (shanghaiTimestamp === null || parseInt(params[0].timestamp) < shanghaiTimestamp) {
785+
if (eip4844Timestamp !== null && ts >= eip4844Timestamp) {
786+
throw {
787+
code: INVALID_PARAMS,
788+
message: 'NewPayloadV3 MUST be used after Cancun is activated',
789+
}
790+
} else if (shanghaiTimestamp === null || parseInt(params[0].timestamp) < shanghaiTimestamp) {
752791
if (withdrawals !== undefined && withdrawals !== null) {
753792
throw {
754793
code: INVALID_PARAMS,
@@ -763,78 +802,28 @@ export class Engine {
763802
}
764803
}
765804
}
766-
const newPayload = await this.newPayload(params)
767-
if (newPayload.status === Status.INVALID_BLOCK_HASH) {
768-
newPayload.status = Status.INVALID
805+
const newPayloadRes = await this.newPayload(params)
806+
if (newPayloadRes.status === Status.INVALID_BLOCK_HASH) {
807+
newPayloadRes.status = Status.INVALID
769808
}
770-
return newPayload
809+
return newPayloadRes
771810
}
772811

773-
async newPayloadV3(
774-
params: [ExecutionPayloadV3 | ExecutionPayloadV2 | ExecutionPayloadV1, (Bytes32[] | null)?]
775-
): Promise<PayloadStatusV1> {
812+
async newPayloadV3(params: [ExecutionPayloadV3, Bytes32[], Bytes32]): Promise<PayloadStatusV1> {
776813
const eip4844Timestamp = this.chain.config.chainCommon.hardforkTimestamp(Hardfork.Cancun)
777-
if (
778-
eip4844Timestamp !== null &&
779-
parseInt(params[0].timestamp) >= eip4844Timestamp &&
780-
(params[1] === undefined || params[1] === null)
781-
) {
782-
throw {
783-
code: INVALID_PARAMS,
784-
message: 'Missing versionedHashes after Cancun is activated',
785-
}
786-
} else if (
787-
(eip4844Timestamp === null || parseInt(params[0].timestamp) < eip4844Timestamp) &&
788-
params[1] !== undefined &&
789-
params[1] !== null
790-
) {
814+
const ts = parseInt(params[0].timestamp)
815+
if (eip4844Timestamp === null || ts < eip4844Timestamp) {
791816
throw {
792817
code: INVALID_PARAMS,
793-
message: 'Recieved versionedHashes before Cancun is activated',
818+
message: 'NewPayloadV{1|2} MUST be used before Cancun is activated',
794819
}
795820
}
796821

797-
const shanghaiTimestamp = this.chain.config.chainCommon.hardforkTimestamp(Hardfork.Shanghai)
798-
if (shanghaiTimestamp === null || parseInt(params[0].timestamp) < shanghaiTimestamp) {
799-
if ('withdrawals' in params[0]) {
800-
throw {
801-
code: INVALID_PARAMS,
802-
message: 'ExecutionPayloadV1 MUST be used before Shanghai is activated',
803-
}
804-
}
805-
} else if (
806-
eip4844Timestamp === null ||
807-
(parseInt(params[0].timestamp) >= shanghaiTimestamp &&
808-
parseInt(params[0].timestamp) < eip4844Timestamp)
809-
) {
810-
if (
811-
'extraDataGas' in params[0] ||
812-
'dataGasUsed' in params[0] ||
813-
!('withdrawals' in params[0])
814-
) {
815-
throw {
816-
code: INVALID_PARAMS,
817-
message: 'ExecutionPayloadV2 MUST be used if Shanghai is activated and Cancun is not',
818-
}
819-
}
820-
} else if (parseInt(params[0].timestamp) >= eip4844Timestamp) {
821-
if (
822-
!('extraData' in params[0]) ||
823-
!('dataGasUsed' in params[0]) ||
824-
!('withdrawals' in params[0])
825-
) {
826-
throw {
827-
code: INVALID_PARAMS,
828-
message: 'ExecutionPayloadV3 MUST be used after Cancun is activated',
829-
}
830-
}
822+
const newPayloadRes = await this.newPayload(params)
823+
if (newPayloadRes.status === Status.INVALID_BLOCK_HASH) {
824+
newPayloadRes.status = Status.INVALID
831825
}
832-
833-
const newPayload = await this.newPayload(params)
834-
if (newPayload.status === Status.INVALID_BLOCK_HASH) {
835-
newPayload.status = Status.INVALID
836-
}
837-
return newPayload
826+
return newPayloadRes
838827
}
839828

840829
/**
@@ -1040,7 +1029,8 @@ export class Engine {
10401029
let validResponse
10411030
// If payloadAttributes is present, start building block and return payloadId
10421031
if (payloadAttributes) {
1043-
const { timestamp, prevRandao, suggestedFeeRecipient, withdrawals } = payloadAttributes
1032+
const { timestamp, prevRandao, suggestedFeeRecipient, withdrawals, parentBeaconBlockRoot } =
1033+
payloadAttributes
10441034
const timestampBigInt = BigInt(timestamp)
10451035

10461036
if (timestampBigInt <= headBlock.header.timestamp) {
@@ -1059,6 +1049,7 @@ export class Engine {
10591049
timestamp,
10601050
mixHash: prevRandao,
10611051
coinbase: suggestedFeeRecipient,
1052+
parentBeaconBlockRoot,
10621053
},
10631054
withdrawals
10641055
)
@@ -1113,6 +1104,24 @@ export class Engine {
11131104
return this.forkchoiceUpdated(params)
11141105
}
11151106

1107+
private async forkchoiceUpdatedV3(
1108+
params: [forkchoiceState: ForkchoiceStateV1, payloadAttributes: PayloadAttributesV3 | undefined]
1109+
): Promise<ForkchoiceResponseV1 & { headBlock?: Block }> {
1110+
const payloadAttributes = params[1]
1111+
if (payloadAttributes !== undefined && payloadAttributes !== null) {
1112+
const cancunTimestamp = this.chain.config.chainCommon.hardforkTimestamp(Hardfork.Cancun)
1113+
const ts = BigInt(payloadAttributes.timestamp)
1114+
if (ts < cancunTimestamp!) {
1115+
throw {
1116+
code: INVALID_PARAMS,
1117+
message: 'PayloadAttributesV{1|2} MUST be used before Cancun is activated',
1118+
}
1119+
}
1120+
}
1121+
1122+
return this.forkchoiceUpdated(params)
1123+
}
1124+
11161125
/**
11171126
* Given payloadId, returns the most recent version of an execution payload
11181127
* that is available by the time of the call or responds with an error.

0 commit comments

Comments
 (0)