@@ -46,24 +46,32 @@ type Uint64 = string
46
46
type Uint256 = string
47
47
48
48
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 }
52
55
53
56
export type ForkchoiceStateV1 = {
54
57
headBlockHash : Bytes32
55
58
safeBlockHash : Bytes32
56
59
finalizedBlockHash : Bytes32
57
60
}
58
61
62
+ // PayloadAttributes has higher version fields as optionals to make it easy for typescript
59
63
type PayloadAttributes = {
60
64
timestamp : Uint64
61
65
prevRandao : Bytes32
62
66
suggestedFeeRecipient : Bytes20
67
+ // add higher version fields as optionals to make it easy for typescript
63
68
withdrawals ?: WithdrawalV1 [ ]
69
+ parentBeaconBlockRoot ?: Bytes32
64
70
}
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 }
67
75
68
76
export type PayloadStatusV1 = {
69
77
status : Status
@@ -139,9 +147,14 @@ const payloadAttributesFieldValidatorsV1 = {
139
147
}
140
148
const payloadAttributesFieldValidatorsV2 = {
141
149
...payloadAttributesFieldValidatorsV1 ,
150
+ // withdrawals is optional in V2 because its backward forward compatible with V1
142
151
withdrawals : validators . optional ( validators . array ( validators . withdrawal ( ) ) ) ,
143
152
}
144
-
153
+ const payloadAttributesFieldValidatorsV3 = {
154
+ ...payloadAttributesFieldValidatorsV1 ,
155
+ withdrawals : validators . array ( validators . withdrawal ( ) ) ,
156
+ parentBeaconBlockRoot : validators . bytes32 ,
157
+ }
145
158
/**
146
159
* Formats a block to {@link ExecutionPayloadV1}.
147
160
*/
@@ -406,14 +419,9 @@ export class Engine {
406
419
407
420
this . newPayloadV3 = cmMiddleware (
408
421
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 ] ,
417
425
] ) ,
418
426
( [ payload ] , response ) => this . connectionManager . lastNewPayload ( { payload, response } )
419
427
)
@@ -440,14 +448,20 @@ export class Engine {
440
448
] ) ,
441
449
forkchoiceUpdatedResponseCMHandler
442
450
)
443
-
444
451
this . forkchoiceUpdatedV2 = cmMiddleware (
445
452
middleware ( this . forkchoiceUpdatedV2 . bind ( this ) , 1 , [
446
453
[ validators . object ( forkchoiceFieldValidators ) ] ,
447
454
[ validators . optional ( validators . object ( payloadAttributesFieldValidatorsV2 ) ) ] ,
448
455
] ) ,
449
456
forkchoiceUpdatedResponseCMHandler
450
457
)
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
+ )
451
465
452
466
this . getPayloadV1 = cmMiddleware (
453
467
middleware ( this . getPayloadV1 . bind ( this ) , 1 , [ [ validators . bytes8 ] ] ) ,
@@ -516,14 +530,22 @@ export class Engine {
516
530
* 3. validationError: String|null - validation error message
517
531
*/
518
532
private async newPayload (
519
- params : [ ExecutionPayload , ( Bytes32 [ ] | null ) ?]
533
+ params : [ ExecutionPayload , ( Bytes32 [ ] | null ) ?, ( Bytes32 | null ) ? ]
520
534
) : Promise < PayloadStatusV1 > {
521
- const [ payload , versionedHashes ] = params
535
+ const [ payload , versionedHashes , parentBeaconBlockRoot ] = params
522
536
if ( this . config . synchronized ) {
523
537
this . connectionManager . newPayloadLog ( )
524
538
}
525
539
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
+ )
527
549
if ( ! block || error ) {
528
550
let response = error
529
551
if ( ! response ) {
@@ -741,14 +763,31 @@ export class Engine {
741
763
}
742
764
743
765
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
+
744
775
return this . newPayload ( params )
745
776
}
746
777
747
778
async newPayloadV2 ( params : [ ExecutionPayloadV2 | ExecutionPayloadV1 ] ) : Promise < PayloadStatusV1 > {
748
779
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
+
749
783
const withdrawals = ( params [ 0 ] as ExecutionPayloadV2 ) . withdrawals
750
784
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 ) {
752
791
if ( withdrawals !== undefined && withdrawals !== null ) {
753
792
throw {
754
793
code : INVALID_PARAMS ,
@@ -763,78 +802,28 @@ export class Engine {
763
802
}
764
803
}
765
804
}
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
769
808
}
770
- return newPayload
809
+ return newPayloadRes
771
810
}
772
811
773
- async newPayloadV3 (
774
- params : [ ExecutionPayloadV3 | ExecutionPayloadV2 | ExecutionPayloadV1 , ( Bytes32 [ ] | null ) ?]
775
- ) : Promise < PayloadStatusV1 > {
812
+ async newPayloadV3 ( params : [ ExecutionPayloadV3 , Bytes32 [ ] , Bytes32 ] ) : Promise < PayloadStatusV1 > {
776
813
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 ) {
791
816
throw {
792
817
code : INVALID_PARAMS ,
793
- message : 'Recieved versionedHashes before Cancun is activated' ,
818
+ message : 'NewPayloadV{1|2} MUST be used before Cancun is activated' ,
794
819
}
795
820
}
796
821
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
831
825
}
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
838
827
}
839
828
840
829
/**
@@ -1040,7 +1029,8 @@ export class Engine {
1040
1029
let validResponse
1041
1030
// If payloadAttributes is present, start building block and return payloadId
1042
1031
if ( payloadAttributes ) {
1043
- const { timestamp, prevRandao, suggestedFeeRecipient, withdrawals } = payloadAttributes
1032
+ const { timestamp, prevRandao, suggestedFeeRecipient, withdrawals, parentBeaconBlockRoot } =
1033
+ payloadAttributes
1044
1034
const timestampBigInt = BigInt ( timestamp )
1045
1035
1046
1036
if ( timestampBigInt <= headBlock . header . timestamp ) {
@@ -1059,6 +1049,7 @@ export class Engine {
1059
1049
timestamp,
1060
1050
mixHash : prevRandao ,
1061
1051
coinbase : suggestedFeeRecipient ,
1052
+ parentBeaconBlockRoot,
1062
1053
} ,
1063
1054
withdrawals
1064
1055
)
@@ -1113,6 +1104,24 @@ export class Engine {
1113
1104
return this . forkchoiceUpdated ( params )
1114
1105
}
1115
1106
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
+
1116
1125
/**
1117
1126
* Given payloadId, returns the most recent version of an execution payload
1118
1127
* that is available by the time of the call or responds with an error.
0 commit comments