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

implement /eth/v1/beacon/blinded_blocks BN endpoint #4286

Merged
merged 3 commits into from
Nov 8, 2022
Merged
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
72 changes: 72 additions & 0 deletions beacon_chain/rpc/rest_beacon_api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import
../consensus_object_pools/[blockchain_dag, exit_pool, spec_cache],
../spec/[eth2_merkleization, forks, network, validator],
../spec/datatypes/[phase0, altair],
../validators/message_router_mev,
./state_ttl_cache

export rest_utils
Expand Down Expand Up @@ -795,6 +796,77 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =

return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)

# https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock
# https://github.com/ethereum/beacon-APIs/blob/v2.3.0/apis/beacon/blocks/blinded_blocks.yaml
router.api(MethodPost, "/eth/v1/beacon/blinded_blocks") do (
contentBody: Option[ContentBody]) -> RestApiResponse:
## Instructs the beacon node to use the components of the
## `SignedBlindedBeaconBlock` to construct and publish a
## `SignedBeaconBlock` by swapping out the transactions_root for the
## corresponding full list of transactions. The beacon node should
## broadcast a newly constructed `SignedBeaconBlock` to the beacon network,
## to be included in the beacon chain. The beacon node is not required to
## validate the signed `BeaconBlock`, and a successful response (20X) only
## indicates that the broadcast has been successful.
if contentBody.isNone():
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)

let
currentEpochFork =
node.dag.cfg.stateForkAtEpoch(node.currentSlot().epoch())
version = request.headers.getString("eth-consensus-version")
body = contentBody.get()

if body.contentType == OctetStreamMediaType and
currentEpochFork.toString != version:
return RestApiResponse.jsonError(Http400, BlockIncorrectFork)

case currentEpochFork
of BeaconStateFork.Capella:
return RestApiResponse.jsonError(Http500, $capellaImplementationMissing)
of BeaconStateFork.Bellatrix:
let res =
block:
var restBlock = decodeBodyJsonOrSsz(SignedBlindedBeaconBlock, body).valueOr:
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError,
$error)
await node.unblindAndRouteBlockMEV(restBlock)

if res.get().isErr():
return RestApiResponse.jsonError(
Http503, BeaconNodeInSyncError, $res.error())
if res.get().isNone():
return RestApiResponse.jsonError(Http202, BlockValidationError)

return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
of BeaconStateFork.Altair, BeaconStateFork.Phase0:
# Pre-Bellatrix, this endpoint will accept a `SignedBeaconBlock`.
#
# This is mostly the same as /eth/v1/beacon/blocks for phase 0 and
# altair.
var
restBlock = decodeBody(RestPublishedSignedBeaconBlock, body,
version).valueOr:
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError,
$error)
forked = ForkedSignedBeaconBlock(restBlock)

if forked.kind != node.dag.cfg.blockForkAtEpoch(
getForkedBlockField(forked, slot).epoch):
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError)

let res = withBlck(forked):
blck.root = hash_tree_root(blck.message)
await node.router.routeSignedBeaconBlock(blck)

if res.isErr():
return RestApiResponse.jsonError(
Http503, BeaconNodeInSyncError, $res.error())
elif res.get().isNone():
return RestApiResponse.jsonError(Http202, BlockValidationError)

return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)

# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlock
router.api(MethodGet, "/eth/v1/beacon/blocks/{block_id}") do (
block_id: BlockIdent) -> RestApiResponse:
Expand Down
2 changes: 2 additions & 0 deletions beacon_chain/rpc/rest_constants.nim
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,5 @@ const
DeprecatedRemovalValidatorBlocksV1* =
"v1/validator/blocks/{slot} endpoint was deprecated and replaced by v2, see " &
"https://github.com/ethereum/beacon-APIs/pull/220"
BlockIncorrectFork* =
"Block has incorrect fork"
28 changes: 28 additions & 0 deletions beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2446,6 +2446,34 @@ proc decodeBody*[T](t: typedesc[T],
return err("Unexpected deserialization error")
ok(data)

proc decodeBodyJsonOrSsz*[T](t: typedesc[T],
body: ContentBody): Result[T, cstring] =
if body.contentType == ApplicationJsonMediaType:
let data =
try:
RestJson.decode(body.data, T,
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(data)
elif body.contentType == OctetStreamMediaType:
let blck =
try:
SSZ.decode(body.data, T)
except SerializationError:
return err("Unable to deserialize data")
except CatchableError:
return err("Unexpected deserialization error")
ok(blck)
else:
return err("Unsupported content type")

proc encodeBytes*[T: EncodeTypes](value: T,
contentType: string): RestResult[seq[byte]] =
case contentType
Expand Down
116 changes: 116 additions & 0 deletions beacon_chain/validators/message_router_mev.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# beacon_chain
# Copyright (c) 2022 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).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}

import std/macros
import metrics
import ../beacon_node

from eth/async_utils import awaitWithTimeout
from ../spec/datatypes/bellatrix import SignedBeaconBlock
from ../spec/mev/rest_bellatrix_mev_calls import submitBlindedBlock

const
BUILDER_BLOCK_SUBMISSION_DELAY_TOLERANCE = 4.seconds

declareCounter beacon_block_builder_proposed,
"Number of beacon chain blocks produced using an external block builder"

func getFieldNames*(x: typedesc[auto]): seq[string] {.compileTime.} =
var res: seq[string]
for name, _ in fieldPairs(default(x)):
res.add name
res

macro copyFields*(
dst: untyped, src: untyped, fieldNames: static[seq[string]]): untyped =
result = newStmtList()
for name in fieldNames:
if name notin [
# These fields are the ones which vary between the blinded and
# unblinded objects, and can't simply be copied.
"transactions_root", "execution_payload",
"execution_payload_header", "body"]:
# TODO use stew/assign2
result.add newAssignment(
arnetheduck marked this conversation as resolved.
Show resolved Hide resolved
newDotExpr(dst, ident(name)), newDotExpr(src, ident(name)))

proc unblindAndRouteBlockMEV*(
node: BeaconNode, blindedBlock: SignedBlindedBeaconBlock):
Future[Result[Opt[BlockRef], string]] {.async.} =
# By time submitBlindedBlock is called, must already have done slashing
# protection check
let unblindedPayload =
try:
awaitWithTimeout(
node.payloadBuilderRestClient.submitBlindedBlock(blindedBlock),
BUILDER_BLOCK_SUBMISSION_DELAY_TOLERANCE):
return err("Submitting blinded block timed out")
# From here on, including error paths, disallow local EL production by
# returning Opt.some, regardless of whether on head or newBlock.
except RestDecodingError as exc:
return err("REST decoding error submitting blinded block: " & exc.msg)
except CatchableError as exc:
return err("exception in submitBlindedBlock: " & exc.msg)

const httpOk = 200
if unblindedPayload.status == httpOk:
if hash_tree_root(
blindedBlock.message.body.execution_payload_header) !=
hash_tree_root(unblindedPayload.data.data):
debug "unblindAndRouteBlockMEV: unblinded payload doesn't match blinded payload",
blindedPayload =
blindedBlock.message.body.execution_payload_header
else:
# Signature provided is consistent with unblinded execution payload,
# so construct full beacon block
# https://github.com/ethereum/builder-specs/blob/v0.2.0/specs/validator.md#block-proposal
var signedBlock = bellatrix.SignedBeaconBlock(
signature: blindedBlock.signature)
copyFields(
signedBlock.message, blindedBlock.message,
getFieldNames(typeof(signedBlock.message)))
copyFields(
signedBlock.message.body, blindedBlock.message.body,
getFieldNames(typeof(signedBlock.message.body)))
signedBlock.message.body.execution_payload = unblindedPayload.data.data

if signedBlock.root != hash_tree_root(blindedBlock.message):
return err("Unblinded block doesn't match blinded block SSZ root")

signedBlock.root = hash_tree_root(signedBlock.message)

debug "unblindAndRouteBlockMEV: proposing unblinded block",
blck = shortLog(signedBlock)

let newBlockRef =
(await node.router.routeSignedBeaconBlock(signedBlock)).valueOr:
# submitBlindedBlock has run, so don't allow fallback to run
return err("routeSignedBeaconBlock error") # Errors logged in router

if newBlockRef.isSome:
beacon_block_builder_proposed.inc()
notice "Block proposed (MEV)",
blockRoot = shortLog(signedBlock.root), blck = shortLog(signedBlock),
signature = shortLog(signedBlock.signature)

return ok newBlockRef
else:
debug "unblindAndRouteBlockMEV: submitBlindedBlock failed",
blindedBlock, payloadStatus = unblindedPayload.status

# https://github.com/ethereum/builder-specs/blob/v0.2.0/specs/validator.md#proposer-slashing
# This means if a validator publishes a signature for a
# `BlindedBeaconBlock` (via a dissemination of a
# `SignedBlindedBeaconBlock`) then the validator **MUST** not use the
# local build process as a fallback, even in the event of some failure
# with the external buildernetwork.
return err("unblindAndRouteBlockMEV error")
Loading