Add support for POST /eth/v2/beacon/blocks (#5214)

* Add support for POST /eth/v2/beacon/blocks

* More descriptive errors

* Address review feedback

* Return 500 (not 400) for a missing implementation case
This commit is contained in:
henridf 2023-08-11 20:08:54 -07:00 committed by GitHub
parent 9ceed40090
commit 9efd26c2e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 197 additions and 4 deletions

View File

@ -894,8 +894,68 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
# TODO
# Add POST /eth/v2/beacon/blocks
# https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlockV2
router.api(MethodPost, "/eth/v2/beacon/blocks") do (
contentBody: Option[ContentBody]) -> RestApiResponse:
let res =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
if request.headers.getString("broadcast_validation") != "gossip":
# TODO (henridf): support 'consensus' and 'consensus_and_equivocation'
# broadcast_validation
return RestApiResponse.jsonError(
Http500, "gossip broadcast_validation only supported")
let
body = contentBody.get()
version = request.headers.getString("eth-consensus-version")
var
restBlock = decodeBodyJsonOrSsz(RestPublishedSignedBlockContents,
body, version).valueOr:
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError,
$error)
forked = ForkedSignedBeaconBlock.init(restBlock)
# TODO (henridf): handle broadcast_validation flag
if restBlock.kind != node.dag.cfg.consensusForkAtEpoch(
getForkedBlockField(forked, slot).epoch):
doAssert strictVerification notin node.dag.updateFlags
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError)
case restBlock.kind
of ConsensusFork.Phase0:
var blck = restBlock.phase0Data
blck.root = hash_tree_root(blck.message)
await node.router.routeSignedBeaconBlock(blck,
Opt.none(SignedBlobSidecars))
of ConsensusFork.Altair:
var blck = restBlock.altairData
blck.root = hash_tree_root(blck.message)
await node.router.routeSignedBeaconBlock(blck,
Opt.none(SignedBlobSidecars))
of ConsensusFork.Bellatrix:
var blck = restBlock.bellatrixData
blck.root = hash_tree_root(blck.message)
await node.router.routeSignedBeaconBlock(blck,
Opt.none(SignedBlobSidecars))
of ConsensusFork.Capella:
var blck = restBlock.capellaData
blck.root = hash_tree_root(blck.message)
await node.router.routeSignedBeaconBlock(blck,
Opt.none(SignedBlobSidecars))
of ConsensusFork.Deneb:
var blck = restBlock.denebData.signed_block
blck.root = hash_tree_root(blck.message)
await node.router.routeSignedBeaconBlock(
blck, Opt.some(asSeq restBlock.denebData.signed_blob_sidecars))
if res.isErr():
return RestApiResponse.jsonError(
Http503, BeaconNodeInSyncError, $res.error())
if res.get().isNone():
return RestApiResponse.jsonError(Http202, BlockValidationError)
return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
# https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock
# https://github.com/ethereum/beacon-APIs/blob/v2.4.0/apis/beacon/blocks/blinded_blocks.yaml

View File

@ -3027,7 +3027,7 @@ proc decodeBody*(
t: typedesc[RestPublishedSignedBlockContents],
body: ContentBody,
version: string
): Result[RestPublishedSignedBlockContents, cstring] =
): Result[RestPublishedSignedBlockContents, string] =
if body.contentType == ApplicationJsonMediaType:
let data =
try:
@ -3043,7 +3043,9 @@ proc decodeBody*(
return err("Unexpected deserialization error")
ok(data)
elif body.contentType == OctetStreamMediaType:
let consensusFork = ? ConsensusFork.decodeString(version)
let consensusFork =
decodeEthConsensusVersion(version).valueOr:
return err("Invalid or Unsupported consensus version")
case consensusFork
of ConsensusFork.Phase0:
let blck =
@ -3116,6 +3118,98 @@ proc decodeBody*[T](t: typedesc[T],
return err("Unexpected deserialization error")
ok(data)
proc decodeBodyJsonOrSsz*(
t: typedesc[RestPublishedSignedBlockContents],
body: ContentBody,
version: string
): Result[RestPublishedSignedBlockContents, string] =
if body.contentType == OctetStreamMediaType:
decodeBody(RestPublishedSignedBlockContents, body, version)
elif body.contentType == ApplicationJsonMediaType:
let consensusFork =
decodeEthConsensusVersion(version).valueOr:
return err("Invalid or Unsupported consensus version")
case consensusFork
of ConsensusFork.Phase0:
let blck =
try:
RestJson.decode(body.data, phase0.SignedBeaconBlock,
requireAllFields = true,
allowUnknownFields = true)
except SerializationError as exc:
debug "Failed to deserialize REST JSON data",
err = exc.formatMsg("<data>"),
data = string.fromBytes(body.data)
return err("Unable to deserialize JSON for fork " &
version & ": " & exc.formatMsg("<data>"))
except CatchableError as exc:
return err("Unexpected JSON deserialization error: " & exc.msg)
ok(RestPublishedSignedBlockContents(
kind: ConsensusFork.Phase0, phase0Data: blck))
of ConsensusFork.Altair:
let blck =
try:
RestJson.decode(body.data, altair.SignedBeaconBlock,
requireAllFields = true,
allowUnknownFields = true)
except SerializationError as exc:
debug "Failed to deserialize REST JSON data",
err = exc.formatMsg("<data>"),
data = string.fromBytes(body.data)
return err("Unable to deserialize data")
except CatchableError:
return err("Unexpected deserialization error")
ok(RestPublishedSignedBlockContents(
kind: ConsensusFork.Altair, altairData: blck))
of ConsensusFork.Bellatrix:
let blck =
try:
RestJson.decode(body.data, bellatrix.SignedBeaconBlock,
requireAllFields = true,
allowUnknownFields = true)
except SerializationError as exc:
debug "Failed to deserialize REST JSON data",
err = exc.formatMsg("<data>"),
data = string.fromBytes(body.data)
return err("Unable to deserialize data")
except CatchableError:
return err("Unexpected deserialization error")
ok(RestPublishedSignedBlockContents(
kind: ConsensusFork.Bellatrix, bellatrixData: blck))
of ConsensusFork.Capella:
let blck =
try:
RestJson.decode(body.data, capella.SignedBeaconBlock,
requireAllFields = true,
allowUnknownFields = true)
except SerializationError as exc:
debug "Failed to deserialize REST JSON data",
err = exc.formatMsg("<data>"),
data = string.fromBytes(body.data)
return err("Unable to deserialize data")
except CatchableError:
return err("Unexpected deserialization error")
ok(RestPublishedSignedBlockContents(
kind: ConsensusFork.Capella, capellaData: blck))
of ConsensusFork.Deneb:
let blckContents =
try:
RestJson.decode(body.data, DenebSignedBlockContents,
requireAllFields = true,
allowUnknownFields = true)
except SerializationError as exc:
debug "Failed to deserialize REST JSON data",
err = exc.formatMsg("<data>"),
data = string.fromBytes(body.data)
return err("Unable to deserialize data")
except CatchableError:
return err("Unexpected deserialization error")
ok(RestPublishedSignedBlockContents(
kind: ConsensusFork.Deneb, denebData: blckContents))
else:
return err("Unsupported or invalid content media type")
proc decodeBodyJsonOrSsz*[T](t: typedesc[T],
body: ContentBody): Result[T, cstring] =
if body.contentType == ApplicationJsonMediaType:

View File

@ -184,6 +184,45 @@ proc publishSszBlock*(
extraHeaders = @[("eth-consensus-version", consensus)])
return resp
proc publishBlockV2Plain(body: phase0.SignedBeaconBlock): RestPlainResponse {.
rest, endpoint: "/eth/v2/beacon/blocks",
meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlockV2
proc publishBlockV2Plain(body: altair.SignedBeaconBlock): RestPlainResponse {.
rest, endpoint: "/eth/v2/beacon/blocks",
meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlockV2
proc publishBlockV2Plain(body: bellatrix.SignedBeaconBlock): RestPlainResponse {.
rest, endpoint: "/eth/v2/beacon/blocks",
meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlockV2
proc publishBlockV2Plain(body: capella.SignedBeaconBlock): RestPlainResponse {.
rest, endpoint: "/eth/v2/beacon/blocks",
meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlockV2
proc publishBlockV2Plain(body: DenebSignedBlockContents): RestPlainResponse {.
rest, endpoint: "/eth/v2/beacon/blocks",
meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlockV2
proc publishBlockV2*(
client: RestClientRef,
blck: phase0.SignedBeaconBlock | altair.SignedBeaconBlock |
bellatrix.SignedBeaconBlock | capella.SignedBeaconBlock |
deneb.SignedBeaconBlock
): Future[RestPlainResponse] {.async} =
let
consensus = typeof(blck).toFork.toString()
resp = await client.publishBlockV2Plain(
blck, extraHeaders = @[
("eth-consensus-version", consensus),
("broadcast_validation", "gossip")])
return resp
proc publishBlindedBlock*(body: phase0.SignedBeaconBlock): RestPlainResponse {.
rest, endpoint: "/eth/v1/beacon/blinded_blocks",
meth: MethodPost.}