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:
parent
9ceed40090
commit
9efd26c2e9
|
@ -894,8 +894,68 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
|
|
||||||
return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
|
return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
|
||||||
|
|
||||||
# TODO
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlockV2
|
||||||
# Add POST /eth/v2/beacon/blocks
|
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://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock
|
||||||
# https://github.com/ethereum/beacon-APIs/blob/v2.4.0/apis/beacon/blocks/blinded_blocks.yaml
|
# https://github.com/ethereum/beacon-APIs/blob/v2.4.0/apis/beacon/blocks/blinded_blocks.yaml
|
||||||
|
|
|
@ -3027,7 +3027,7 @@ proc decodeBody*(
|
||||||
t: typedesc[RestPublishedSignedBlockContents],
|
t: typedesc[RestPublishedSignedBlockContents],
|
||||||
body: ContentBody,
|
body: ContentBody,
|
||||||
version: string
|
version: string
|
||||||
): Result[RestPublishedSignedBlockContents, cstring] =
|
): Result[RestPublishedSignedBlockContents, string] =
|
||||||
if body.contentType == ApplicationJsonMediaType:
|
if body.contentType == ApplicationJsonMediaType:
|
||||||
let data =
|
let data =
|
||||||
try:
|
try:
|
||||||
|
@ -3043,7 +3043,9 @@ proc decodeBody*(
|
||||||
return err("Unexpected deserialization error")
|
return err("Unexpected deserialization error")
|
||||||
ok(data)
|
ok(data)
|
||||||
elif body.contentType == OctetStreamMediaType:
|
elif body.contentType == OctetStreamMediaType:
|
||||||
let consensusFork = ? ConsensusFork.decodeString(version)
|
let consensusFork =
|
||||||
|
decodeEthConsensusVersion(version).valueOr:
|
||||||
|
return err("Invalid or Unsupported consensus version")
|
||||||
case consensusFork
|
case consensusFork
|
||||||
of ConsensusFork.Phase0:
|
of ConsensusFork.Phase0:
|
||||||
let blck =
|
let blck =
|
||||||
|
@ -3116,6 +3118,98 @@ proc decodeBody*[T](t: typedesc[T],
|
||||||
return err("Unexpected deserialization error")
|
return err("Unexpected deserialization error")
|
||||||
ok(data)
|
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],
|
proc decodeBodyJsonOrSsz*[T](t: typedesc[T],
|
||||||
body: ContentBody): Result[T, cstring] =
|
body: ContentBody): Result[T, cstring] =
|
||||||
if body.contentType == ApplicationJsonMediaType:
|
if body.contentType == ApplicationJsonMediaType:
|
||||||
|
|
|
@ -184,6 +184,45 @@ proc publishSszBlock*(
|
||||||
extraHeaders = @[("eth-consensus-version", consensus)])
|
extraHeaders = @[("eth-consensus-version", consensus)])
|
||||||
return resp
|
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 {.
|
proc publishBlindedBlock*(body: phase0.SignedBeaconBlock): RestPlainResponse {.
|
||||||
rest, endpoint: "/eth/v1/beacon/blinded_blocks",
|
rest, endpoint: "/eth/v1/beacon/blinded_blocks",
|
||||||
meth: MethodPost.}
|
meth: MethodPost.}
|
||||||
|
|
Loading…
Reference in New Issue