Skip to content

Commit 91f6fcb

Browse files
authored
Add SSZ encoding support to MEV client calls. (#6970)
* Add SSZ support to MEV client functions. * Add tests. * Update copyright years. * Update AllTests.
1 parent 05bee3a commit 91f6fcb

13 files changed

+876
-89
lines changed

AllTests-mainnet.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,18 @@ AllTests-mainnet
722722
+ Invalid Authorization Token [Beacon Node] [Preset: mainnet] OK
723723
+ Missing Authorization header [Beacon Node] [Preset: mainnet] OK
724724
```
725+
## MEV calls serialization/deserialization and behavior test suite
726+
```diff
727+
+ /eth/v1/builder/blinded_blocks [json/json] test OK
728+
+ /eth/v1/builder/blinded_blocks [json/ssz] test OK
729+
+ /eth/v1/builder/blinded_blocks [ssz/json] test OK
730+
+ /eth/v1/builder/blinded_blocks [ssz/ssz] test OK
731+
+ /eth/v1/builder/header [json] test OK
732+
+ /eth/v1/builder/header [ssz] test OK
733+
+ /eth/v1/builder/status test OK
734+
+ /eth/v1/builder/validators [json] test OK
735+
+ /eth/v1/builder/validators [ssz] test OK
736+
```
725737
## Message signatures
726738
```diff
727739
+ Aggregate and proof signatures OK

beacon_chain/rpc/rest_constants.nim

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,4 +279,3 @@ const
279279
"Unable to load state for parent block, database corrupt?"
280280
RewardOverflowError* =
281281
"Reward value overflow"
282-
InvalidContentTypeError* = "Invalid content type"

beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim

Lines changed: 114 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export
2626
jsonSerializationResults, rest_keymanager_types
2727

2828
from web3/primitives import Hash32, Quantity
29+
from json import getStr, newJString
2930
export primitives.Hash32, primitives.Quantity
3031

3132
func decodeMediaType*(
@@ -82,8 +83,6 @@ RestJson.useDefaultSerializationFor(
8283
GetForkChoiceResponse,
8384
GetForkScheduleResponse,
8485
GetGenesisResponse,
85-
GetHeaderResponseDeneb,
86-
GetHeaderResponseElectra,
8786
GetKeystoresResponse,
8887
GetNextWithdrawalsResponse,
8988
GetPoolAttesterSlashingsResponse,
@@ -168,8 +167,6 @@ RestJson.useDefaultSerializationFor(
168167
SignedContributionAndProof,
169168
SignedValidatorRegistrationV1,
170169
SignedVoluntaryExit,
171-
SubmitBlindedBlockResponseDeneb,
172-
SubmitBlindedBlockResponseElectra,
173170
SyncAggregate,
174171
SyncAggregatorSelectionData,
175172
SyncCommittee,
@@ -341,6 +338,8 @@ const
341338
UnableDecodeVersionError = "Unable to decode version"
342339
UnableDecodeError = "Unable to decode data"
343340
UnexpectedDecodeError = "Unexpected decoding error"
341+
InvalidContentTypeError* = "Invalid content type"
342+
UnexpectedForkVersionError* = "Unexpected fork version received"
344343

345344
type
346345
EncodeTypes* =
@@ -356,9 +355,6 @@ type
356355
SetGasLimitRequest |
357356
bellatrix_mev.SignedBlindedBeaconBlock |
358357
capella_mev.SignedBlindedBeaconBlock |
359-
deneb_mev.SignedBlindedBeaconBlock |
360-
electra_mev.SignedBlindedBeaconBlock |
361-
fulu_mev.SignedBlindedBeaconBlock |
362358
phase0.AttesterSlashing |
363359
SignedValidatorRegistrationV1 |
364360
SignedVoluntaryExit |
@@ -374,7 +370,10 @@ type
374370
DenebSignedBlockContents |
375371
ElectraSignedBlockContents |
376372
FuluSignedBlockContents |
377-
ForkedMaybeBlindedBeaconBlock
373+
ForkedMaybeBlindedBeaconBlock |
374+
deneb_mev.SignedBlindedBeaconBlock |
375+
electra_mev.SignedBlindedBeaconBlock |
376+
fulu_mev.SignedBlindedBeaconBlock
378377

379378
EncodeArrays* =
380379
seq[phase0.Attestation] |
@@ -392,6 +391,14 @@ type
392391
seq[RestBeaconCommitteeSelection] |
393392
seq[RestSyncCommitteeSelection]
394393

394+
MevDecodeTypes* =
395+
GetHeaderResponseDeneb |
396+
GetHeaderResponseElectra |
397+
GetHeaderResponseFulu |
398+
SubmitBlindedBlockResponseDeneb |
399+
SubmitBlindedBlockResponseElectra |
400+
SubmitBlindedBlockResponseFulu
401+
395402
DecodeTypes* =
396403
DataEnclosedObject |
397404
DataMetaEnclosedObject |
@@ -3266,11 +3273,67 @@ proc decodeBodyJsonOrSsz*(
32663273
return err(
32673274
RestErrorMessage.init(Http400, UnableDecodeError,
32683275
[exc.formatMsg("<data>")]))
3269-
ok(data.toSeq)
3276+
ok(data.asSeq)
32703277
else:
32713278
err(RestErrorMessage.init(Http415, InvalidContentTypeError,
32723279
[$body.contentType]))
32733280

3281+
proc decodeBytesJsonOrSsz*(
3282+
T: typedesc[MevDecodeTypes],
3283+
data: openArray[byte],
3284+
contentType: Opt[ContentTypeData],
3285+
version: string
3286+
): Result[T, RestErrorMessage] =
3287+
var res {.noinit.}: T
3288+
3289+
let
3290+
typeFork = kind(typeof(res.data))
3291+
consensusFork = ConsensusFork.decodeString(version).valueOr:
3292+
return err(RestErrorMessage.init(Http400, UnableDecodeVersionError,
3293+
[version, $error]))
3294+
if typeFork != consensusFork:
3295+
return err(
3296+
RestErrorMessage.init(Http400, UnexpectedForkVersionError,
3297+
["eth-consensus-version", consensusFork.toString(),
3298+
typeFork.toString()]))
3299+
3300+
if contentType == ApplicationJsonMediaType:
3301+
res =
3302+
try:
3303+
RestJson.decode(
3304+
data,
3305+
T,
3306+
requireAllFields = true,
3307+
allowUnknownFields = true)
3308+
except SerializationError as exc:
3309+
debug "Failed to deserialize REST JSON data",
3310+
err = exc.formatMsg("<data>")
3311+
return err(
3312+
RestErrorMessage.init(Http400, UnableDecodeError,
3313+
[exc.formatMsg("<data>")]))
3314+
let jsonFork = ConsensusFork.decodeString(res.version.getStr()).valueOr:
3315+
return err(RestErrorMessage.init(Http400, UnableDecodeVersionError,
3316+
[res.version.getStr(), $error]))
3317+
if typeFork != jsonFork:
3318+
return err(
3319+
RestErrorMessage.init(Http400, UnexpectedForkVersionError,
3320+
["json-version", res.version.getStr(),
3321+
typeFork.toString()]))
3322+
ok(res)
3323+
elif contentType == OctetStreamMediaType:
3324+
ok(T(
3325+
version: newJString(typeFork.toString()),
3326+
data:
3327+
try:
3328+
SSZ.decode(data, typeof(res.data))
3329+
except SerializationError as exc:
3330+
return err(
3331+
RestErrorMessage.init(Http400, UnableDecodeError,
3332+
[exc.formatMsg("<data>")]))))
3333+
else:
3334+
err(RestErrorMessage.init(Http415, InvalidContentTypeError,
3335+
[$contentType]))
3336+
32743337
proc decodeBody*[T](t: typedesc[T],
32753338
body: ContentBody): Result[T, cstring] =
32763339
if body.contentType != ApplicationJsonMediaType:
@@ -3324,6 +3387,31 @@ proc decodeBodyJsonOrSsz*[T](t: typedesc[T],
33243387
err(RestErrorMessage.init(Http415, InvalidContentTypeError,
33253388
[$body.contentType]))
33263389

3390+
proc encodeBytes*(value: seq[SignedValidatorRegistrationV1],
3391+
contentType: string): RestResult[seq[byte]] =
3392+
case contentType
3393+
of "application/json":
3394+
try:
3395+
var
3396+
stream = memoryOutput()
3397+
writer = JsonWriter[RestJson].init(stream)
3398+
writer.writeArray(value)
3399+
ok(stream.getOutput(seq[byte]))
3400+
except IOError:
3401+
return err("Input/output error")
3402+
except SerializationError:
3403+
return err("Serialization error")
3404+
of "application/octet-stream":
3405+
try:
3406+
ok(SSZ.encode(
3407+
init(
3408+
List[SignedValidatorRegistrationV1, Limit VALIDATOR_REGISTRY_LIMIT],
3409+
value)))
3410+
except SerializationError:
3411+
return err("Serialization error")
3412+
else:
3413+
err("Content-Type not supported")
3414+
33273415
proc encodeBytes*[T: EncodeTypes](value: T,
33283416
contentType: string): RestResult[seq[byte]] =
33293417
case contentType
@@ -3363,29 +3451,26 @@ proc encodeBytes*[T: EncodeArrays](value: T,
33633451
err("Content-Type not supported")
33643452

33653453
proc encodeBytes*[T: EncodeOctetTypes](
3366-
value: T,
3367-
contentType: string
3368-
): RestResult[seq[byte]] =
3454+
value: T,
3455+
contentType: string
3456+
): RestResult[seq[byte]] =
33693457
case contentType
33703458
of "application/json":
3371-
let data =
3372-
try:
3373-
var stream = memoryOutput()
3374-
var writer = JsonWriter[RestJson].init(stream)
3375-
writer.writeValue(value)
3376-
stream.getOutput(seq[byte])
3377-
except IOError:
3378-
return err("Input/output error")
3379-
except SerializationError:
3380-
return err("Serialization error")
3381-
ok(data)
3459+
try:
3460+
var
3461+
stream = memoryOutput()
3462+
writer = JsonWriter[RestJson].init(stream)
3463+
writer.writeValue(value)
3464+
ok(stream.getOutput(seq[byte]))
3465+
except IOError:
3466+
err("Input/output error")
3467+
except SerializationError:
3468+
err("Serialization error")
33823469
of "application/octet-stream":
3383-
let data =
3384-
try:
3385-
SSZ.encode(value)
3386-
except CatchableError:
3387-
return err("Serialization error")
3388-
ok(data)
3470+
try:
3471+
ok(SSZ.encode(value))
3472+
except CatchableError:
3473+
err("Serialization error")
33893474
else:
33903475
err("Content-Type not supported")
33913476

beacon_chain/spec/eth2_apis/rest_types.nim

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -518,9 +518,6 @@ type
518518
GetEpochCommitteesResponse* = DataEnclosedObject[seq[RestBeaconStatesCommittees]]
519519
GetForkScheduleResponse* = DataEnclosedObject[seq[Fork]]
520520
GetGenesisResponse* = DataEnclosedObject[RestGenesis]
521-
GetHeaderResponseDeneb* = DataVersionEnclosedObject[deneb_mev.SignedBuilderBid]
522-
GetHeaderResponseElectra* = DataVersionEnclosedObject[electra_mev.SignedBuilderBid]
523-
GetHeaderResponseFulu* = DataVersionEnclosedObject[fulu_mev.SignedBuilderBid]
524521
GetNetworkIdentityResponse* = DataEnclosedObject[RestNetworkIdentity]
525522
GetPeerCountResponse* = DataMetaEnclosedObject[RestPeerCount]
526523
GetPeerResponse* = DataMetaEnclosedObject[RestNodePeer]
@@ -546,14 +543,18 @@ type
546543
GetEpochSyncCommitteesResponse* = DataEnclosedObject[RestEpochSyncCommittee]
547544
ProduceAttestationDataResponse* = DataEnclosedObject[AttestationData]
548545
ProduceSyncCommitteeContributionResponse* = DataEnclosedObject[SyncCommitteeContribution]
549-
SubmitBlindedBlockResponseDeneb* = DataEnclosedObject[deneb_mev.ExecutionPayloadAndBlobsBundle]
550-
SubmitBlindedBlockResponseElectra* = DataEnclosedObject[electra_mev.ExecutionPayloadAndBlobsBundle]
551-
SubmitBlindedBlockResponseFulu* = DataEnclosedObject[fulu_mev.ExecutionPayloadAndBlobsBundle]
552546
GetValidatorsActivityResponse* = DataEnclosedObject[seq[RestActivityItem]]
553547
GetValidatorsLivenessResponse* = DataEnclosedObject[seq[RestLivenessItem]]
554548
SubmitBeaconCommitteeSelectionsResponse* = DataEnclosedObject[seq[RestBeaconCommitteeSelection]]
555549
SubmitSyncCommitteeSelectionsResponse* = DataEnclosedObject[seq[RestSyncCommitteeSelection]]
556550

551+
GetHeaderResponseDeneb* = DataVersionEnclosedObject[deneb_mev.SignedBuilderBid]
552+
GetHeaderResponseElectra* = DataVersionEnclosedObject[electra_mev.SignedBuilderBid]
553+
GetHeaderResponseFulu* = DataVersionEnclosedObject[fulu_mev.SignedBuilderBid]
554+
SubmitBlindedBlockResponseDeneb* = DataVersionEnclosedObject[deneb_mev.ExecutionPayloadAndBlobsBundle]
555+
SubmitBlindedBlockResponseElectra* = DataVersionEnclosedObject[electra_mev.ExecutionPayloadAndBlobsBundle]
556+
SubmitBlindedBlockResponseFulu* = DataVersionEnclosedObject[fulu_mev.ExecutionPayloadAndBlobsBundle]
557+
557558
RestNodeValidity* {.pure.} = enum
558559
valid = "VALID",
559560
invalid = "INVALID",

beacon_chain/spec/forks.nim

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,9 @@ template kind*(
441441
deneb.SigVerifiedSignedBeaconBlock |
442442
deneb.MsgTrustedSignedBeaconBlock |
443443
deneb.TrustedSignedBeaconBlock |
444-
deneb_mev.SignedBlindedBeaconBlock]): ConsensusFork =
444+
deneb_mev.SignedBlindedBeaconBlock |
445+
deneb_mev.SignedBuilderBid |
446+
deneb_mev.ExecutionPayloadAndBlobsBundle]): ConsensusFork =
445447
ConsensusFork.Deneb
446448

447449
template kind*(
@@ -464,7 +466,9 @@ template kind*(
464466
electra.SingleAttestation |
465467
electra.AggregateAndProof |
466468
electra.SignedAggregateAndProof |
467-
electra_mev.SignedBlindedBeaconBlock]): ConsensusFork =
469+
electra_mev.SignedBlindedBeaconBlock |
470+
electra_mev.SignedBuilderBid |
471+
electra_mev.ExecutionPayloadAndBlobsBundle]): ConsensusFork =
468472
ConsensusFork.Electra
469473

470474
template kind*(
@@ -483,7 +487,9 @@ template kind*(
483487
fulu.SigVerifiedSignedBeaconBlock |
484488
fulu.MsgTrustedSignedBeaconBlock |
485489
fulu.TrustedSignedBeaconBlock |
486-
fulu_mev.SignedBlindedBeaconBlock]): ConsensusFork =
490+
fulu_mev.SignedBlindedBeaconBlock |
491+
fulu_mev.SignedBuilderBid |
492+
fulu_mev.ExecutionPayloadAndBlobsBundle]): ConsensusFork =
487493
ConsensusFork.Fulu
488494

489495
template BeaconState*(kind: static ConsensusFork): auto =
Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# beacon_chain
2-
# Copyright (c) 2023-2024 Status Research & Development GmbH
2+
# Copyright (c) 2023-2025 Status Research & Development GmbH
33
# Licensed and distributed under either of
44
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
55
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@@ -13,36 +13,56 @@ import
1313

1414
export chronos, client, rest_types, eth2_rest_serialization
1515

16+
proc getStatus*(): RestPlainResponse {.
17+
rest, endpoint: "/eth/v1/builder/status",
18+
meth: MethodGet.}
19+
## https://ethereum.github.io/builder-specs/#/Builder/status
20+
1621
proc registerValidator*(body: seq[SignedValidatorRegistrationV1]
1722
): RestPlainResponse {.
1823
rest, endpoint: "/eth/v1/builder/validators",
1924
meth: MethodPost, connection: {Dedicated, Close}.}
2025
## https://github.com/ethereum/builder-specs/blob/v0.4.0/apis/builder/validators.yaml
2126
## https://github.com/ethereum/beacon-APIs/blob/v2.3.0/apis/validator/register_validator.yaml
2227

23-
proc getHeaderDeneb*(slot: Slot,
24-
parent_hash: Eth2Digest,
25-
pubkey: ValidatorPubKey
26-
): RestPlainResponse {.
27-
rest, endpoint: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}",
28-
meth: MethodGet, connection: {Dedicated, Close}.}
28+
proc getHeaderDenebPlain*(
29+
slot: Slot,
30+
parent_hash: Eth2Digest,
31+
pubkey: ValidatorPubKey
32+
): RestPlainResponse {.
33+
rest, endpoint: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}",
34+
meth: MethodGet, connection: {Dedicated, Close}.}
2935
## https://github.com/ethereum/builder-specs/blob/v0.4.0/apis/builder/header.yaml
3036

37+
proc getHeaderDeneb*(
38+
client: RestClientRef,
39+
slot: Slot,
40+
parent_hash: Eth2Digest,
41+
pubkey: ValidatorPubKey
42+
): Future[RestPlainResponse] {.
43+
async: (raises: [CancelledError, RestEncodingError, RestDnsResolveError,
44+
RestCommunicationError], raw: true).} =
45+
client.getHeaderDenebPlain(
46+
slot, parent_hash, pubkey,
47+
restAcceptType = "application/octet-stream,application/json;q=0.5",
48+
)
49+
3150
proc submitBlindedBlockPlain*(
3251
body: deneb_mev.SignedBlindedBeaconBlock
3352
): RestPlainResponse {.
34-
rest, endpoint: "/eth/v1/builder/blinded_blocks",
35-
meth: MethodPost, connection: {Dedicated, Close}.}
53+
rest, endpoint: "/eth/v1/builder/blinded_blocks",
54+
meth: MethodPost, connection: {Dedicated, Close}.}
3655
## https://github.com/ethereum/builder-specs/blob/v0.4.0/apis/builder/blinded_blocks.yaml
3756

3857
proc submitBlindedBlock*(
3958
client: RestClientRef,
4059
body: deneb_mev.SignedBlindedBeaconBlock
4160
): Future[RestPlainResponse] {.
4261
async: (raises: [CancelledError, RestEncodingError, RestDnsResolveError,
43-
RestCommunicationError]).} =
62+
RestCommunicationError], raw: true).} =
4463
## https://github.com/ethereum/builder-specs/blob/v0.4.0/apis/builder/blinded_blocks.yaml
45-
await client.submitBlindedBlockPlain(
64+
client.submitBlindedBlockPlain(
4665
body,
66+
restAcceptType = "application/octet-stream,application/json;q=0.5",
4767
extraHeaders = @[("eth-consensus-version", toString(ConsensusFork.Deneb))]
4868
)

0 commit comments

Comments
 (0)