Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SSZ encoding support to MEV client calls. #6970

Open
wants to merge 4 commits into
base: unstable
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
12 changes: 12 additions & 0 deletions AllTests-mainnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,18 @@ AllTests-mainnet
+ Invalid Authorization Token [Beacon Node] [Preset: mainnet] OK
+ Missing Authorization header [Beacon Node] [Preset: mainnet] OK
```
## MEV calls serialization/deserialization and behavior test suite
```diff
+ /eth/v1/builder/blinded_blocks [json/json] test OK
+ /eth/v1/builder/blinded_blocks [json/ssz] test OK
+ /eth/v1/builder/blinded_blocks [ssz/json] test OK
+ /eth/v1/builder/blinded_blocks [ssz/ssz] test OK
+ /eth/v1/builder/header [json] test OK
+ /eth/v1/builder/header [ssz] test OK
+ /eth/v1/builder/status test OK
+ /eth/v1/builder/validators [json] test OK
+ /eth/v1/builder/validators [ssz] test OK
```
## Message signatures
```diff
+ Aggregate and proof signatures OK
Expand Down
1 change: 0 additions & 1 deletion beacon_chain/rpc/rest_constants.nim
Original file line number Diff line number Diff line change
Expand Up @@ -271,4 +271,3 @@ const
"Unable to load state for parent block, database corrupt?"
RewardOverflowError* =
"Reward value overflow"
InvalidContentTypeError* = "Invalid content type"
143 changes: 114 additions & 29 deletions beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export
jsonSerializationResults, rest_keymanager_types

from web3/primitives import Hash32, Quantity
from json import getStr, newJString
export primitives.Hash32, primitives.Quantity

func decodeMediaType*(
Expand Down Expand Up @@ -82,8 +83,6 @@ RestJson.useDefaultSerializationFor(
GetForkChoiceResponse,
GetForkScheduleResponse,
GetGenesisResponse,
GetHeaderResponseDeneb,
GetHeaderResponseElectra,
GetKeystoresResponse,
GetNextWithdrawalsResponse,
GetPoolAttesterSlashingsResponse,
Expand Down Expand Up @@ -168,8 +167,6 @@ RestJson.useDefaultSerializationFor(
SignedContributionAndProof,
SignedValidatorRegistrationV1,
SignedVoluntaryExit,
SubmitBlindedBlockResponseDeneb,
SubmitBlindedBlockResponseElectra,
SyncAggregate,
SyncAggregatorSelectionData,
SyncCommittee,
Expand Down Expand Up @@ -341,6 +338,8 @@ const
UnableDecodeVersionError = "Unable to decode version"
UnableDecodeError = "Unable to decode data"
UnexpectedDecodeError = "Unexpected decoding error"
InvalidContentTypeError* = "Invalid content type"
UnexpectedForkVersionError* = "Unexpected fork version received"

type
EncodeTypes* =
Expand All @@ -356,9 +355,6 @@ type
SetGasLimitRequest |
bellatrix_mev.SignedBlindedBeaconBlock |
capella_mev.SignedBlindedBeaconBlock |
deneb_mev.SignedBlindedBeaconBlock |
electra_mev.SignedBlindedBeaconBlock |
fulu_mev.SignedBlindedBeaconBlock |
phase0.AttesterSlashing |
SignedValidatorRegistrationV1 |
SignedVoluntaryExit |
Expand All @@ -374,7 +370,10 @@ type
DenebSignedBlockContents |
ElectraSignedBlockContents |
FuluSignedBlockContents |
ForkedMaybeBlindedBeaconBlock
ForkedMaybeBlindedBeaconBlock |
deneb_mev.SignedBlindedBeaconBlock |
electra_mev.SignedBlindedBeaconBlock |
fulu_mev.SignedBlindedBeaconBlock

EncodeArrays* =
seq[phase0.Attestation] |
Expand All @@ -392,6 +391,14 @@ type
seq[RestBeaconCommitteeSelection] |
seq[RestSyncCommitteeSelection]

MevDecodeTypes* =
GetHeaderResponseDeneb |
GetHeaderResponseElectra |
GetHeaderResponseFulu |
SubmitBlindedBlockResponseDeneb |
SubmitBlindedBlockResponseElectra |
SubmitBlindedBlockResponseFulu

DecodeTypes* =
DataEnclosedObject |
DataMetaEnclosedObject |
Expand Down Expand Up @@ -3266,11 +3273,67 @@ proc decodeBodyJsonOrSsz*(
return err(
RestErrorMessage.init(Http400, UnableDecodeError,
[exc.formatMsg("<data>")]))
ok(data.toSeq)
ok(data.asSeq)
else:
err(RestErrorMessage.init(Http415, InvalidContentTypeError,
[$body.contentType]))

proc decodeBytesJsonOrSsz*(
T: typedesc[MevDecodeTypes],
data: openArray[byte],
contentType: Opt[ContentTypeData],
version: string
): Result[T, RestErrorMessage] =
var res {.noinit.}: T

let
typeFork = kind(typeof(res.data))
consensusFork = ConsensusFork.decodeString(version).valueOr:
return err(RestErrorMessage.init(Http400, UnableDecodeVersionError,
[version, $error]))
if typeFork != consensusFork:
return err(
RestErrorMessage.init(Http400, UnexpectedForkVersionError,
["eth-consensus-version", consensusFork.toString(),
typeFork.toString()]))

if contentType == ApplicationJsonMediaType:
res =
try:
RestJson.decode(
data,
T,
requireAllFields = true,
allowUnknownFields = true)
except SerializationError as exc:
debug "Failed to deserialize REST JSON data",
err = exc.formatMsg("<data>")
return err(
RestErrorMessage.init(Http400, UnableDecodeError,
[exc.formatMsg("<data>")]))
let jsonFork = ConsensusFork.decodeString(res.version.getStr()).valueOr:
return err(RestErrorMessage.init(Http400, UnableDecodeVersionError,
[res.version.getStr(), $error]))
if typeFork != jsonFork:
return err(
RestErrorMessage.init(Http400, UnexpectedForkVersionError,
["json-version", res.version.getStr(),
typeFork.toString()]))
ok(res)
elif contentType == OctetStreamMediaType:
ok(T(
version: newJString(typeFork.toString()),
data:
try:
SSZ.decode(data, typeof(res.data))
except SerializationError as exc:
return err(
RestErrorMessage.init(Http400, UnableDecodeError,
[exc.formatMsg("<data>")]))))
else:
err(RestErrorMessage.init(Http415, InvalidContentTypeError,
[$contentType]))

proc decodeBody*[T](t: typedesc[T],
body: ContentBody): Result[T, cstring] =
if body.contentType != ApplicationJsonMediaType:
Expand Down Expand Up @@ -3324,6 +3387,31 @@ proc decodeBodyJsonOrSsz*[T](t: typedesc[T],
err(RestErrorMessage.init(Http415, InvalidContentTypeError,
[$body.contentType]))

proc encodeBytes*(value: seq[SignedValidatorRegistrationV1],
contentType: string): RestResult[seq[byte]] =
case contentType
of "application/json":
try:
var
stream = memoryOutput()
writer = JsonWriter[RestJson].init(stream)
writer.writeArray(value)
ok(stream.getOutput(seq[byte]))
except IOError:
return err("Input/output error")
except SerializationError:
return err("Serialization error")
of "application/octet-stream":
try:
ok(SSZ.encode(
init(
List[SignedValidatorRegistrationV1, Limit VALIDATOR_REGISTRY_LIMIT],
value)))
except SerializationError:
return err("Serialization error")
else:
err("Content-Type not supported")

proc encodeBytes*[T: EncodeTypes](value: T,
contentType: string): RestResult[seq[byte]] =
case contentType
Expand Down Expand Up @@ -3363,29 +3451,26 @@ proc encodeBytes*[T: EncodeArrays](value: T,
err("Content-Type not supported")

proc encodeBytes*[T: EncodeOctetTypes](
value: T,
contentType: string
): RestResult[seq[byte]] =
value: T,
contentType: string
): RestResult[seq[byte]] =
case contentType
of "application/json":
let data =
try:
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
writer.writeValue(value)
stream.getOutput(seq[byte])
except IOError:
return err("Input/output error")
except SerializationError:
return err("Serialization error")
ok(data)
try:
var
stream = memoryOutput()
writer = JsonWriter[RestJson].init(stream)
writer.writeValue(value)
ok(stream.getOutput(seq[byte]))
except IOError:
err("Input/output error")
except SerializationError:
err("Serialization error")
of "application/octet-stream":
let data =
try:
SSZ.encode(value)
except CatchableError:
return err("Serialization error")
ok(data)
try:
ok(SSZ.encode(value))
except CatchableError:
err("Serialization error")
else:
err("Content-Type not supported")

Expand Down
15 changes: 8 additions & 7 deletions beacon_chain/spec/eth2_apis/rest_types.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2018-2024 Status Research & Development GmbH
# Copyright (c) 2018-2025 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
Expand Down Expand Up @@ -518,9 +518,6 @@ type
GetEpochCommitteesResponse* = DataEnclosedObject[seq[RestBeaconStatesCommittees]]
GetForkScheduleResponse* = DataEnclosedObject[seq[Fork]]
GetGenesisResponse* = DataEnclosedObject[RestGenesis]
GetHeaderResponseDeneb* = DataVersionEnclosedObject[deneb_mev.SignedBuilderBid]
GetHeaderResponseElectra* = DataVersionEnclosedObject[electra_mev.SignedBuilderBid]
GetHeaderResponseFulu* = DataVersionEnclosedObject[fulu_mev.SignedBuilderBid]
GetNetworkIdentityResponse* = DataEnclosedObject[RestNetworkIdentity]
GetPeerCountResponse* = DataMetaEnclosedObject[RestPeerCount]
GetPeerResponse* = DataMetaEnclosedObject[RestNodePeer]
Expand All @@ -546,14 +543,18 @@ type
GetEpochSyncCommitteesResponse* = DataEnclosedObject[RestEpochSyncCommittee]
ProduceAttestationDataResponse* = DataEnclosedObject[AttestationData]
ProduceSyncCommitteeContributionResponse* = DataEnclosedObject[SyncCommitteeContribution]
SubmitBlindedBlockResponseDeneb* = DataEnclosedObject[deneb_mev.ExecutionPayloadAndBlobsBundle]
SubmitBlindedBlockResponseElectra* = DataEnclosedObject[electra_mev.ExecutionPayloadAndBlobsBundle]
SubmitBlindedBlockResponseFulu* = DataEnclosedObject[fulu_mev.ExecutionPayloadAndBlobsBundle]
GetValidatorsActivityResponse* = DataEnclosedObject[seq[RestActivityItem]]
GetValidatorsLivenessResponse* = DataEnclosedObject[seq[RestLivenessItem]]
SubmitBeaconCommitteeSelectionsResponse* = DataEnclosedObject[seq[RestBeaconCommitteeSelection]]
SubmitSyncCommitteeSelectionsResponse* = DataEnclosedObject[seq[RestSyncCommitteeSelection]]

GetHeaderResponseDeneb* = DataVersionEnclosedObject[deneb_mev.SignedBuilderBid]
GetHeaderResponseElectra* = DataVersionEnclosedObject[electra_mev.SignedBuilderBid]
GetHeaderResponseFulu* = DataVersionEnclosedObject[fulu_mev.SignedBuilderBid]
SubmitBlindedBlockResponseDeneb* = DataVersionEnclosedObject[deneb_mev.ExecutionPayloadAndBlobsBundle]
SubmitBlindedBlockResponseElectra* = DataVersionEnclosedObject[electra_mev.ExecutionPayloadAndBlobsBundle]
SubmitBlindedBlockResponseFulu* = DataVersionEnclosedObject[fulu_mev.ExecutionPayloadAndBlobsBundle]

RestNodeValidity* {.pure.} = enum
valid = "VALID",
invalid = "INVALID",
Expand Down
12 changes: 9 additions & 3 deletions beacon_chain/spec/forks.nim
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,9 @@ template kind*(
deneb.SigVerifiedSignedBeaconBlock |
deneb.MsgTrustedSignedBeaconBlock |
deneb.TrustedSignedBeaconBlock |
deneb_mev.SignedBlindedBeaconBlock]): ConsensusFork =
deneb_mev.SignedBlindedBeaconBlock |
deneb_mev.SignedBuilderBid |
deneb_mev.ExecutionPayloadAndBlobsBundle]): ConsensusFork =
ConsensusFork.Deneb

template kind*(
Expand All @@ -464,7 +466,9 @@ template kind*(
electra.SingleAttestation |
electra.AggregateAndProof |
electra.SignedAggregateAndProof |
electra_mev.SignedBlindedBeaconBlock]): ConsensusFork =
electra_mev.SignedBlindedBeaconBlock |
electra_mev.SignedBuilderBid |
electra_mev.ExecutionPayloadAndBlobsBundle]): ConsensusFork =
ConsensusFork.Electra

template kind*(
Expand All @@ -483,7 +487,9 @@ template kind*(
fulu.SigVerifiedSignedBeaconBlock |
fulu.MsgTrustedSignedBeaconBlock |
fulu.TrustedSignedBeaconBlock |
fulu_mev.SignedBlindedBeaconBlock]): ConsensusFork =
fulu_mev.SignedBlindedBeaconBlock |
fulu_mev.SignedBuilderBid |
fulu_mev.ExecutionPayloadAndBlobsBundle]): ConsensusFork =
ConsensusFork.Fulu

template BeaconState*(kind: static ConsensusFork): auto =
Expand Down
42 changes: 31 additions & 11 deletions beacon_chain/spec/mev/rest_deneb_mev_calls.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Copyright (c) 2023-2025 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
Expand All @@ -13,36 +13,56 @@ import

export chronos, client, rest_types, eth2_rest_serialization

proc getStatus*(): RestPlainResponse {.
rest, endpoint: "/eth/v1/builder/status",
meth: MethodGet.}
## https://ethereum.github.io/builder-specs/#/Builder/status

proc registerValidator*(body: seq[SignedValidatorRegistrationV1]
): RestPlainResponse {.
rest, endpoint: "/eth/v1/builder/validators",
meth: MethodPost, connection: {Dedicated, Close}.}
## https://github.com/ethereum/builder-specs/blob/v0.4.0/apis/builder/validators.yaml
## https://github.com/ethereum/beacon-APIs/blob/v2.3.0/apis/validator/register_validator.yaml

proc getHeaderDeneb*(slot: Slot,
parent_hash: Eth2Digest,
pubkey: ValidatorPubKey
): RestPlainResponse {.
rest, endpoint: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}",
meth: MethodGet, connection: {Dedicated, Close}.}
proc getHeaderDenebPlain*(
slot: Slot,
parent_hash: Eth2Digest,
pubkey: ValidatorPubKey
): RestPlainResponse {.
rest, endpoint: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}",
meth: MethodGet, connection: {Dedicated, Close}.}
## https://github.com/ethereum/builder-specs/blob/v0.4.0/apis/builder/header.yaml

proc getHeaderDeneb*(
client: RestClientRef,
slot: Slot,
parent_hash: Eth2Digest,
pubkey: ValidatorPubKey
): Future[RestPlainResponse] {.
async: (raises: [CancelledError, RestEncodingError, RestDnsResolveError,
RestCommunicationError], raw: true).} =
client.getHeaderDenebPlain(
slot, parent_hash, pubkey,
restAcceptType = "application/octet-stream,application/json;q=0.5",
)

proc submitBlindedBlockPlain*(
body: deneb_mev.SignedBlindedBeaconBlock
): RestPlainResponse {.
rest, endpoint: "/eth/v1/builder/blinded_blocks",
meth: MethodPost, connection: {Dedicated, Close}.}
rest, endpoint: "/eth/v1/builder/blinded_blocks",
meth: MethodPost, connection: {Dedicated, Close}.}
## https://github.com/ethereum/builder-specs/blob/v0.4.0/apis/builder/blinded_blocks.yaml

proc submitBlindedBlock*(
client: RestClientRef,
body: deneb_mev.SignedBlindedBeaconBlock
): Future[RestPlainResponse] {.
async: (raises: [CancelledError, RestEncodingError, RestDnsResolveError,
RestCommunicationError]).} =
RestCommunicationError], raw: true).} =
## https://github.com/ethereum/builder-specs/blob/v0.4.0/apis/builder/blinded_blocks.yaml
await client.submitBlindedBlockPlain(
client.submitBlindedBlockPlain(
body,
restAcceptType = "application/octet-stream,application/json;q=0.5",
extraHeaders = @[("eth-consensus-version", toString(ConsensusFork.Deneb))]
)
Loading
Loading