2022-11-08 18:08:43 +00:00
|
|
|
# beacon_chain
|
2024-01-06 14:26:56 +00:00
|
|
|
# Copyright (c) 2022-2024 Status Research & Development GmbH
|
2022-11-08 18:08:43 +00:00
|
|
|
# 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.
|
|
|
|
|
2023-01-20 14:14:37 +00:00
|
|
|
{.push raises: [].}
|
2022-11-08 18:08:43 +00:00
|
|
|
|
|
|
|
import std/macros
|
|
|
|
import metrics
|
2023-10-12 03:40:15 +00:00
|
|
|
import stew/assign2
|
2022-11-08 18:08:43 +00:00
|
|
|
import ../beacon_node
|
|
|
|
|
|
|
|
from eth/async_utils import awaitWithTimeout
|
|
|
|
from ../spec/datatypes/bellatrix import SignedBeaconBlock
|
2023-02-06 18:07:30 +00:00
|
|
|
from ../spec/mev/rest_capella_mev_calls import submitBlindedBlock
|
2023-10-12 03:40:15 +00:00
|
|
|
from ../spec/mev/rest_deneb_mev_calls import submitBlindedBlock
|
2022-11-08 18:08:43 +00:00
|
|
|
|
|
|
|
const
|
2023-10-11 11:48:35 +00:00
|
|
|
BUILDER_BLOCK_SUBMISSION_DELAY_TOLERANCE = 5.seconds
|
2022-11-08 18:08:43 +00:00
|
|
|
|
|
|
|
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",
|
2023-02-21 13:21:38 +00:00
|
|
|
"execution_payload_header", "body", "withdrawals_root"]:
|
2022-11-08 18:08:43 +00:00
|
|
|
# TODO use stew/assign2
|
|
|
|
result.add newAssignment(
|
|
|
|
newDotExpr(dst, ident(name)), newDotExpr(src, ident(name)))
|
|
|
|
|
2023-02-14 10:49:48 +00:00
|
|
|
proc unblindAndRouteBlockMEV*(
|
2023-05-25 15:38:56 +00:00
|
|
|
node: BeaconNode, payloadBuilderRestClient: RestClientRef,
|
2023-11-16 00:20:13 +00:00
|
|
|
blindedBlock:
|
|
|
|
capella_mev.SignedBlindedBeaconBlock |
|
|
|
|
deneb_mev.SignedBlindedBeaconBlock):
|
2023-02-14 10:49:48 +00:00
|
|
|
Future[Result[Opt[BlockRef], string]] {.async.} =
|
2023-11-16 00:20:13 +00:00
|
|
|
const consensusFork = typeof(blindedBlock).kind
|
|
|
|
|
2023-09-15 09:27:38 +00:00
|
|
|
info "Proposing blinded Builder API block",
|
|
|
|
blindedBlock = shortLog(blindedBlock)
|
|
|
|
|
2023-02-14 10:49:48 +00:00
|
|
|
# By time submitBlindedBlock is called, must already have done slashing
|
|
|
|
# protection check
|
2024-01-24 23:27:22 +00:00
|
|
|
let response =
|
2023-02-14 10:49:48 +00:00
|
|
|
try:
|
|
|
|
awaitWithTimeout(
|
2023-05-25 15:38:56 +00:00
|
|
|
payloadBuilderRestClient.submitBlindedBlock(blindedBlock),
|
2023-02-14 10:49:48 +00:00
|
|
|
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 CatchableError as exc:
|
|
|
|
return err("exception in submitBlindedBlock: " & exc.msg)
|
|
|
|
|
|
|
|
const httpOk = 200
|
2024-01-24 23:27:22 +00:00
|
|
|
if response.status != httpOk:
|
2024-01-22 07:36:46 +00:00
|
|
|
# https://github.com/ethereum/builder-specs/blob/v0.4.0/specs/bellatrix/validator.md#proposer-slashing
|
2023-10-31 10:15:38 +00:00
|
|
|
# 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 builder network.
|
2023-11-16 00:20:13 +00:00
|
|
|
return err("submitBlindedBlock failed with HTTP error code " &
|
2024-01-24 23:27:22 +00:00
|
|
|
$response.status & ": " & $shortLog(blindedBlock))
|
2022-11-08 18:08:43 +00:00
|
|
|
|
2023-11-16 00:20:13 +00:00
|
|
|
when consensusFork >= ConsensusFork.Deneb:
|
2024-01-24 23:27:22 +00:00
|
|
|
let
|
|
|
|
res = decodeBytes(
|
|
|
|
SubmitBlindedBlockResponseDeneb, response.data, response.contentType)
|
|
|
|
|
|
|
|
bundle = res.valueOr:
|
|
|
|
return err("Could not decode Deneb blinded block: " & $res.error &
|
|
|
|
" with HTTP status " & $response.status & ", Content-Type " &
|
|
|
|
$response.contentType & " and content " & $response.data)
|
|
|
|
|
|
|
|
template execution_payload: untyped = bundle.data.execution_payload
|
2023-11-16 00:20:13 +00:00
|
|
|
else:
|
2024-01-24 23:27:22 +00:00
|
|
|
let
|
|
|
|
res = decodeBytes(
|
|
|
|
SubmitBlindedBlockResponseCapella, response.data, response.contentType)
|
|
|
|
|
|
|
|
bundle = res.valueOr:
|
|
|
|
return err("Could not decode Capella blinded block: " & $res.error &
|
|
|
|
" with HTTP status " & $response.status & ", Content-Type " &
|
|
|
|
$response.contentType & " and content " & $response.data)
|
|
|
|
|
|
|
|
template execution_payload: untyped = bundle.data
|
|
|
|
|
2023-11-16 00:20:13 +00:00
|
|
|
if hash_tree_root(blindedBlock.message.body.execution_payload_header) !=
|
|
|
|
hash_tree_root(execution_payload):
|
|
|
|
return err("unblinded payload doesn't match blinded payload header: " &
|
|
|
|
$blindedBlock.message.body.execution_payload_header)
|
|
|
|
|
|
|
|
# Signature provided is consistent with unblinded execution payload,
|
|
|
|
# so construct full beacon block
|
2024-01-22 07:36:46 +00:00
|
|
|
# https://github.com/ethereum/builder-specs/blob/v0.4.0/specs/bellatrix/validator.md#block-proposal
|
2023-11-16 00:20:13 +00:00
|
|
|
var signedBlock = consensusFork.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)))
|
|
|
|
assign(signedBlock.message.body.execution_payload, execution_payload)
|
|
|
|
signedBlock.root = hash_tree_root(signedBlock.message)
|
|
|
|
doAssert signedBlock.root == hash_tree_root(blindedBlock.message)
|
|
|
|
|
|
|
|
let blobsOpt =
|
|
|
|
when consensusFork >= ConsensusFork.Deneb:
|
2024-01-24 23:27:22 +00:00
|
|
|
template blobs_bundle: untyped = bundle.data.blobs_bundle
|
2023-11-16 00:20:13 +00:00
|
|
|
if blindedBlock.message.body.blob_kzg_commitments !=
|
2024-01-24 23:27:22 +00:00
|
|
|
bundle.data.blobs_bundle.commitments:
|
2023-11-16 00:20:13 +00:00
|
|
|
return err("unblinded blobs bundle has unexpected commitments")
|
|
|
|
let ok = verifyProofs(
|
|
|
|
asSeq blobs_bundle.blobs,
|
|
|
|
asSeq blobs_bundle.commitments,
|
|
|
|
asSeq blobs_bundle.proofs).valueOr:
|
|
|
|
return err("unblinded blobs bundle fails verification")
|
|
|
|
if not ok:
|
|
|
|
return err("unblinded blobs bundle is invalid")
|
|
|
|
Opt.some(signedBlock.create_blob_sidecars(
|
|
|
|
blobs_bundle.proofs, blobs_bundle.blobs))
|
|
|
|
else:
|
|
|
|
Opt.none(seq[BlobSidecar])
|
2023-10-12 03:40:15 +00:00
|
|
|
|
2023-11-16 00:20:13 +00:00
|
|
|
debug "unblindAndRouteBlockMEV: proposing unblinded block",
|
|
|
|
blck = shortLog(signedBlock)
|
2023-10-12 03:40:15 +00:00
|
|
|
|
2023-11-16 00:20:13 +00:00
|
|
|
let newBlockRef =
|
|
|
|
(await node.router.routeSignedBeaconBlock(signedBlock, blobsOpt)).valueOr:
|
|
|
|
# submitBlindedBlock has run, so don't allow fallback to run
|
|
|
|
return err("routeSignedBeaconBlock error") # Errors logged in router
|
2023-10-12 03:40:15 +00:00
|
|
|
|
2023-11-16 00:20:13 +00:00
|
|
|
if newBlockRef.isSome:
|
|
|
|
beacon_block_builder_proposed.inc()
|
|
|
|
notice "Block proposed (MEV)",
|
|
|
|
blockRoot = shortLog(signedBlock.root), blck = shortLog(signedBlock),
|
|
|
|
signature = shortLog(signedBlock.signature)
|
|
|
|
|
|
|
|
ok newBlockRef
|