Skip to content

Commit e31a65b

Browse files
authored
util,block,client,evm,vm: add EIP 7251 el triggered consolidations request type (#3477)
* util,block,client,vm: add EIP 7251 el triggered consolidations request type * add eip 7251 el triggered consolidations plumbing * accumulate the consolidations into requests from the 7251 system contract * add and debug the newpayloadv4 spec with consolidation related fixes * apply feedback
1 parent 669925f commit e31a65b

File tree

14 files changed

+323
-71
lines changed

14 files changed

+323
-71
lines changed

packages/block/src/block.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
BIGINT_0,
77
CLRequestFactory,
88
CLRequestType,
9+
ConsolidationRequest,
910
DepositRequest,
1011
KECCAK256_RLP,
1112
KECCAK256_RLP_ARRAY,
@@ -424,6 +425,7 @@ export class Block {
424425
withdrawals: withdrawalsData,
425426
depositRequests,
426427
withdrawalRequests,
428+
consolidationRequests,
427429
executionWitness,
428430
} = payload
429431

@@ -454,8 +456,13 @@ export class Block {
454456

455457
const hasDepositRequests = depositRequests !== undefined && depositRequests !== null
456458
const hasWithdrawalRequests = withdrawalRequests !== undefined && withdrawalRequests !== null
459+
const hasConsolidationRequests =
460+
consolidationRequests !== undefined && consolidationRequests !== null
461+
457462
const requests =
458-
hasDepositRequests || hasWithdrawalRequests ? ([] as CLRequest<CLRequestType>[]) : undefined
463+
hasDepositRequests || hasWithdrawalRequests || hasConsolidationRequests
464+
? ([] as CLRequest<CLRequestType>[])
465+
: undefined
459466

460467
if (depositRequests !== undefined && depositRequests !== null) {
461468
for (const dJson of depositRequests) {
@@ -467,6 +474,11 @@ export class Block {
467474
requests!.push(WithdrawalRequest.fromJSON(wJson))
468475
}
469476
}
477+
if (consolidationRequests !== undefined && consolidationRequests !== null) {
478+
for (const cJson of consolidationRequests) {
479+
requests!.push(ConsolidationRequest.fromJSON(cJson))
480+
}
481+
}
470482

471483
const requestsRoot = requests
472484
? await Block.genRequestsTrieRoot(requests, new Trie({ common: opts?.common }))
@@ -1006,6 +1018,7 @@ export class Block {
10061018
// lets add the request fields first and then iterate over requests to fill them up
10071019
depositRequests: this.common.isActivatedEIP(6110) ? [] : undefined,
10081020
withdrawalRequests: this.common.isActivatedEIP(7002) ? [] : undefined,
1021+
consolidationRequests: this.common.isActivatedEIP(7251) ? [] : undefined,
10091022
}
10101023

10111024
if (this.requests !== undefined) {
@@ -1018,11 +1031,16 @@ export class Block {
10181031
case CLRequestType.Withdrawal:
10191032
executionPayload.withdrawalRequests!.push((request as WithdrawalRequest).toJSON())
10201033
continue
1034+
1035+
case CLRequestType.Consolidation:
1036+
executionPayload.consolidationRequests!.push((request as ConsolidationRequest).toJSON())
1037+
continue
10211038
}
10221039
}
10231040
} else if (
10241041
executionPayload.depositRequests !== undefined ||
1025-
executionPayload.withdrawalRequests !== undefined
1042+
executionPayload.withdrawalRequests !== undefined ||
1043+
executionPayload.consolidationRequests !== undefined
10261044
) {
10271045
throw Error(`Undefined requests for activated deposit or withdrawal requests`)
10281046
}

packages/block/src/from-beacon-payload.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ type BeaconWithdrawalRequest = {
2424
amount: PrefixedHexString
2525
}
2626

27+
type BeaconConsolidationRequest = {
28+
source_address: PrefixedHexString
29+
source_pubkey: PrefixedHexString
30+
target_pubkey: PrefixedHexString
31+
}
32+
2733
// Payload json that one gets using the beacon apis
2834
// curl localhost:5052/eth/v2/beacon/blocks/56610 | jq .data.message.body.execution_payload
2935
export type BeaconPayloadJson = {
@@ -48,6 +54,7 @@ export type BeaconPayloadJson = {
4854
// requests data
4955
deposit_requests?: BeaconDepositRequest[]
5056
withdrawal_requests?: BeaconWithdrawalRequest[]
57+
consolidation_requests?: BeaconConsolidationRequest[]
5158

5259
// the casing of VerkleExecutionWitness remains same camel case for now
5360
execution_witness?: VerkleExecutionWitness
@@ -164,6 +171,13 @@ export function executionPayloadFromBeaconPayload(payload: BeaconPayloadJson): E
164171
amount: breq.amount,
165172
}))
166173
}
174+
if (payload.consolidation_requests !== undefined && payload.consolidation_requests !== null) {
175+
executionPayload.consolidationRequests = payload.consolidation_requests.map((breq) => ({
176+
sourceAddress: breq.source_address,
177+
sourcePubkey: breq.source_pubkey,
178+
targetPubkey: breq.target_pubkey,
179+
}))
180+
}
167181

168182
if (payload.execution_witness !== undefined && payload.execution_witness !== null) {
169183
// the casing structure in payload could be camel case or snake depending upon the CL

packages/block/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
BytesLike,
88
CLRequest,
99
CLRequestType,
10+
ConsolidationRequestV1,
1011
DepositRequestV1,
1112
JsonRpcWithdrawal,
1213
PrefixedHexString,
@@ -269,4 +270,5 @@ export type ExecutionPayload = {
269270
executionWitness?: VerkleExecutionWitness | null // QUANTITY, 64 Bits, null implies not available
270271
depositRequests?: DepositRequestV1[] // Array of 6110 deposit requests
271272
withdrawalRequests?: WithdrawalRequestV1[] // Array of 7002 withdrawal requests
273+
consolidationRequests?: ConsolidationRequestV1[] // Array of 7251 consolidation requests
272274
}

packages/block/test/eip7685block.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function getRandomDepositRequest(): CLRequest<CLRequestType> {
2626
function getRandomWithdrawalRequest(): CLRequest<CLRequestType> {
2727
const withdrawalRequestData = {
2828
sourceAddress: randomBytes(20),
29-
validatorPubkey: randomBytes(48),
29+
validatorPublicKey: randomBytes(48),
3030
amount: bytesToBigInt(randomBytes(8)),
3131
}
3232
return WithdrawalRequest.fromRequestData(withdrawalRequestData) as CLRequest<CLRequestType>

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import { UNKNOWN_PAYLOAD } from '../../error-code.js'
22

33
import type { Skeleton } from '../../../service/index.js'
44
import type { Block, ExecutionPayload } from '@ethereumjs/block'
5-
import type { DepositRequestV1, PrefixedHexString, WithdrawalRequestV1 } from '@ethereumjs/util'
5+
import type {
6+
ConsolidationRequestV1,
7+
DepositRequestV1,
8+
PrefixedHexString,
9+
WithdrawalRequestV1,
10+
} from '@ethereumjs/util'
611

712
export enum Status {
813
ACCEPTED = 'ACCEPTED',
@@ -31,6 +36,7 @@ export type ExecutionPayloadV3 = ExecutionPayloadV2 & { excessBlobGas: Uint64; b
3136
export type ExecutionPayloadV4 = ExecutionPayloadV3 & {
3237
depositRequests: DepositRequestV1[]
3338
withdrawalRequests: WithdrawalRequestV1[]
39+
consolidationRequests: ConsolidationRequestV1[]
3440
}
3541

3642
export type ForkchoiceStateV1 = {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const executionPayloadV4FieldValidators = {
3030
...executionPayloadV3FieldValidators,
3131
depositRequests: validators.array(validators.depositRequest()),
3232
withdrawalRequests: validators.array(validators.withdrawalRequest()),
33+
consolidationRequests: validators.array(validators.consolidationRequest()),
3334
}
3435

3536
export const forkchoiceFieldValidators = {

packages/client/src/rpc/validation.ts

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -440,10 +440,10 @@ export const validators = {
440440
}
441441
}
442442

443-
const wt = params[index]
443+
const clReq = params[index]
444444

445445
for (const field of requiredFields) {
446-
if (wt[field] === undefined) {
446+
if (clReq[field] === undefined) {
447447
return {
448448
code: INVALID_PARAMS,
449449
message: `invalid argument ${index}: required field ${field}`,
@@ -458,25 +458,25 @@ export const validators = {
458458
}
459459

460460
// validate pubkey
461-
for (const field of [wt.pubkey]) {
461+
for (const field of [clReq.pubkey]) {
462462
const v = validate(field, this.bytes48)
463463
if (v !== undefined) return v
464464
}
465465

466466
// validate withdrawalCredentials
467-
for (const field of [wt.withdrawalCredentials]) {
467+
for (const field of [clReq.withdrawalCredentials]) {
468468
const v = validate(field, this.bytes32)
469469
if (v !== undefined) return v
470470
}
471471

472472
// validate amount, index
473-
for (const field of [wt.amount, wt.index]) {
473+
for (const field of [clReq.amount, clReq.index]) {
474474
const v = validate(field, this.bytes8)
475475
if (v !== undefined) return v
476476
}
477477

478478
// validate signature
479-
for (const field of [wt.signature]) {
479+
for (const field of [clReq.signature]) {
480480
const v = validate(field, this.bytes96)
481481
if (v !== undefined) return v
482482
}
@@ -494,10 +494,10 @@ export const validators = {
494494
}
495495
}
496496

497-
const wt = params[index]
497+
const clReq = params[index]
498498

499499
for (const field of requiredFields) {
500-
if (wt[field] === undefined) {
500+
if (clReq[field] === undefined) {
501501
return {
502502
code: INVALID_PARAMS,
503503
message: `invalid argument ${index}: required field ${field}`,
@@ -512,26 +512,74 @@ export const validators = {
512512
}
513513

514514
// validate sourceAddress
515-
for (const field of [wt.sourceAddress]) {
515+
for (const field of [clReq.sourceAddress]) {
516516
const v = validate(field, this.address)
517517
if (v !== undefined) return v
518518
}
519519

520520
// validate validatorPubkey
521-
for (const field of [wt.validatorPubkey]) {
521+
for (const field of [clReq.validatorPubkey]) {
522522
const v = validate(field, this.bytes48)
523523
if (v !== undefined) return v
524524
}
525525

526526
// validate amount
527-
for (const field of [wt.amount]) {
527+
for (const field of [clReq.amount]) {
528528
const v = validate(field, this.bytes8)
529529
if (v !== undefined) return v
530530
}
531531
}
532532
}
533533
},
534534

535+
get consolidationRequest() {
536+
return (requiredFields: string[] = ['sourceAddress', 'sourcePubkey', 'targetPubkey']) => {
537+
return (params: any[], index: number) => {
538+
if (typeof params[index] !== 'object') {
539+
return {
540+
code: INVALID_PARAMS,
541+
message: `invalid argument ${index}: argument must be an object`,
542+
}
543+
}
544+
545+
const clReq = params[index]
546+
547+
for (const field of requiredFields) {
548+
if (clReq[field] === undefined) {
549+
return {
550+
code: INVALID_PARAMS,
551+
message: `invalid argument ${index}: required field ${field}`,
552+
}
553+
}
554+
}
555+
556+
const validate = (field: any, validator: Function) => {
557+
if (field === undefined) return
558+
const v = validator([field], 0)
559+
if (v !== undefined) return v
560+
}
561+
562+
// validate sourceAddress
563+
for (const field of [clReq.sourceAddress]) {
564+
const v = validate(field, this.address)
565+
if (v !== undefined) return v
566+
}
567+
568+
// validate validatorPubkey
569+
for (const field of [clReq.sourcePubkey]) {
570+
const v = validate(field, this.bytes48)
571+
if (v !== undefined) return v
572+
}
573+
574+
// validate amount
575+
for (const field of [clReq.targetPubkey]) {
576+
const v = validate(field, this.bytes48)
577+
if (v !== undefined) return v
578+
}
579+
}
580+
}
581+
},
582+
535583
/**
536584
* object validator to check if type is object with
537585
* required keys and expected validation of values

packages/client/test/rpc/engine/newPayloadV4.spec.ts

Lines changed: 20 additions & 6 deletions
Large diffs are not rendered by default.

packages/common/src/eips.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,27 @@ export const EIPs: EIPsDict = {
601601
},
602602
},
603603
},
604+
7251: {
605+
comment: 'Execution layer triggered consolidations (experimental)',
606+
url: 'https://eips.ethereum.org/EIPS/eip-7251',
607+
status: Status.Draft,
608+
minimumHardfork: Hardfork.Paris,
609+
requiredEIPs: [7685],
610+
vm: {
611+
consolidationRequestType: {
612+
v: BigInt(0x02),
613+
d: 'The withdrawal request type for EIP-7685',
614+
},
615+
systemAddress: {
616+
v: BigInt('0xfffffffffffffffffffffffffffffffffffffffe'),
617+
d: 'The system address to perform operations on the consolidation requests predeploy address',
618+
},
619+
consolidationRequestPredeployAddress: {
620+
v: BigInt('0x00b42dbF2194e931E80326D950320f7d9Dbeac02'),
621+
d: 'Address of the consolidations contract',
622+
},
623+
},
624+
},
604625
7516: {
605626
comment: 'BLOBBASEFEE opcode',
606627
url: 'https://eips.ethereum.org/EIPS/eip-7516',

packages/common/src/hardforks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,7 @@ export const hardforks: HardforksDict = {
841841
'Next feature hardfork after cancun, internally used for pectra testing/implementation (incomplete/experimental)',
842842
url: 'https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/prague.md',
843843
status: Status.Draft,
844-
eips: [2537, 2935, 3074, 6110, 7002, 7685],
844+
eips: [2537, 2935, 3074, 6110, 7002, 7251, 7685],
845845
},
846846
osaka: {
847847
name: 'osaka',

0 commit comments

Comments
 (0)