capella builder API support (#4643)

* capella builder API support

* use capella EPH when appropriate

* fill in ExecutionPayload.withdrawals and sanity-check builder API withdrawals root
This commit is contained in:
tersec 2023-02-21 14:21:38 +01:00 committed by GitHub
parent 68cb9fe7b1
commit 79eddcde40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 242 additions and 111 deletions

View File

@ -1,4 +1,4 @@
# Copyright (c) 2018-2022 Status Research & Development GmbH
# Copyright (c) 2018-2023 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).
@ -470,8 +470,22 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
else:
RestApiResponse.jsonError(Http500, InvalidAcceptError)
if node.currentSlot().epoch() >= node.dag.cfg.BELLATRIX_FORK_EPOCH:
let res = await makeBlindedBeaconBlockForHeadAndSlot(
static: doAssert high(ConsensusFork) == ConsensusFork.EIP4844
let currentEpoch = node.currentSlot().epoch()
if currentEpoch >= node.dag.cfg.DENEB_FORK_EPOCH:
debugRaiseAssert $eip4844ImplementationMissing & ": GET /eth/v1/validator/blinded_blocks/{slot}"
elif currentEpoch >= node.dag.cfg.CAPELLA_FORK_EPOCH:
let res = await makeBlindedBeaconBlockForHeadAndSlot[
capella_mev.BlindedBeaconBlock](
node, qrandao, proposer, qgraffiti, qhead, qslot)
if res.isErr():
return RestApiResponse.jsonError(Http400, res.error())
return responsePlain(ForkedBlindedBeaconBlock(
kind: ConsensusFork.Capella,
capellaData: res.get()))
elif currentEpoch >= node.dag.cfg.BELLATRIX_FORK_EPOCH:
let res = await makeBlindedBeaconBlockForHeadAndSlot[
bellatrix_mev.BlindedBeaconBlock](
node, qrandao, proposer, qgraffiti, qhead, qslot)
if res.isErr():
return RestApiResponse.jsonError(Http400, res.error())

View File

@ -1044,7 +1044,7 @@ proc readValue*[BlockType: ForkedBlindedBeaconBlock](
let res =
try:
RestJson.decode(string(data.get()),
BlindedBeaconBlock,
bellatrix_mev.BlindedBeaconBlock,
requireAllFields = true,
allowUnknownFields = true)
except SerializationError as exc:
@ -1056,7 +1056,7 @@ proc readValue*[BlockType: ForkedBlindedBeaconBlock](
let res =
try:
RestJson.decode(string(data.get()),
BlindedBeaconBlock,
capella_mev.BlindedBeaconBlock,
requireAllFields = true,
allowUnknownFields = true)
except SerializationError as exc:

View File

@ -149,6 +149,12 @@ proc publishBlindedBlock*(body: bellatrix_mev.SignedBlindedBeaconBlock):
meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock
proc publishBlindedBlock*(body: capella_mev.SignedBlindedBeaconBlock):
RestPlainResponse {.
rest, endpoint: "/eth/v1/beacon/blinded_blocks",
meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock
proc publishSszBlindedBlock*(
client: RestClientRef,
blck: ForkySignedBeaconBlock

View File

@ -631,7 +631,8 @@ type
GetEpochCommitteesResponse* = DataEnclosedObject[seq[RestBeaconStatesCommittees]]
GetForkScheduleResponse* = DataEnclosedObject[seq[Fork]]
GetGenesisResponse* = DataEnclosedObject[RestGenesis]
GetHeaderResponse* = DataVersionEnclosedObject[bellatrix_mev.SignedBuilderBid]
GetHeaderResponseBellatrix* = DataVersionEnclosedObject[bellatrix_mev.SignedBuilderBid]
GetHeaderResponseCapella* = DataVersionEnclosedObject[capella_mev.SignedBuilderBid]
GetNetworkIdentityResponse* = DataEnclosedObject[RestNetworkIdentity]
GetPeerCountResponse* = DataMetaEnclosedObject[RestPeerCount]
GetPeerResponse* = DataMetaEnclosedObject[RestNodePeer]

View File

@ -17,12 +17,12 @@ import
block_id, eth2_merkleization, eth2_ssz_serialization,
forks_light_client, presets],
./datatypes/[phase0, altair, bellatrix, capella, deneb],
./mev/bellatrix_mev
./mev/bellatrix_mev, ./mev/capella_mev
export
extras, block_id, phase0, altair, bellatrix, capella, deneb,
eth2_merkleization, eth2_ssz_serialization, forks_light_client,
presets, bellatrix_mev
presets, bellatrix_mev, capella_mev
# This file contains helpers for dealing with forks - we have two ways we can
# deal with forks:
@ -152,9 +152,9 @@ type
case kind*: ConsensusFork
of ConsensusFork.Phase0: phase0Data*: phase0.BeaconBlock
of ConsensusFork.Altair: altairData*: altair.BeaconBlock
of ConsensusFork.Bellatrix: bellatrixData*: BlindedBeaconBlock
of ConsensusFork.Capella: capellaData*: BlindedBeaconBlock
of ConsensusFork.EIP4844: eip4844Data*: BlindedBeaconBlock
of ConsensusFork.Bellatrix: bellatrixData*: bellatrix_mev.BlindedBeaconBlock
of ConsensusFork.Capella: capellaData*: capella_mev.BlindedBeaconBlock
of ConsensusFork.EIP4844: eip4844Data*: capella_mev.BlindedBeaconBlock
ForkedTrustedBeaconBlock* = object
case kind*: ConsensusFork
@ -182,15 +182,16 @@ type
ForkySignedBlindedBeaconBlock* =
phase0.SignedBeaconBlock |
altair.SignedBeaconBlock |
SignedBlindedBeaconBlock
bellatrix_mev.SignedBlindedBeaconBlock |
capella_mev.SignedBlindedBeaconBlock
ForkedSignedBlindedBeaconBlock* = object
case kind*: ConsensusFork
of ConsensusFork.Phase0: phase0Data*: phase0.SignedBeaconBlock
of ConsensusFork.Altair: altairData*: altair.SignedBeaconBlock
of ConsensusFork.Bellatrix: bellatrixData*: SignedBlindedBeaconBlock
of ConsensusFork.Capella: capellaData*: SignedBlindedBeaconBlock
of ConsensusFork.EIP4844: eip4844Data*: SignedBlindedBeaconBlock
of ConsensusFork.Bellatrix: bellatrixData*: bellatrix_mev.SignedBlindedBeaconBlock
of ConsensusFork.Capella: capellaData*: capella_mev.SignedBlindedBeaconBlock
of ConsensusFork.EIP4844: eip4844Data*: capella_mev.SignedBlindedBeaconBlock
ForkySigVerifiedSignedBeaconBlock* =
phase0.SigVerifiedSignedBeaconBlock |
@ -417,16 +418,17 @@ func init*(T: type ForkedSignedBlindedBeaconBlock,
signature: signature))
of ConsensusFork.Bellatrix:
T(kind: ConsensusFork.Bellatrix,
bellatrixData: SignedBlindedBeaconBlock(message: forked.bellatrixData,
signature: signature))
bellatrixData: bellatrix_mev.SignedBlindedBeaconBlock(message: forked.bellatrixData,
signature: signature))
of ConsensusFork.Capella:
T(kind: ConsensusFork.Capella,
capellaData: SignedBlindedBeaconBlock(message: forked.capellaData,
signature: signature))
capellaData: capella_mev.SignedBlindedBeaconBlock(message: forked.capellaData,
signature: signature))
of ConsensusFork.EIP4844:
discard $eip4844ImplementationMissing & "forks.nim:init(T: type ForkedSignedBlindedBeaconBlock)"
T(kind: ConsensusFork.EIP4844,
eip4844Data: SignedBlindedBeaconBlock(message: forked.eip4844Data,
signature: signature))
eip4844Data: capella_mev.SignedBlindedBeaconBlock(message: forked.eip4844Data,
signature: signature))
template init*(T: type ForkedMsgTrustedSignedBeaconBlock, blck: phase0.MsgTrustedSignedBeaconBlock): T =
T(kind: ConsensusFork.Phase0, phase0Data: blck)
@ -838,7 +840,8 @@ template withStateAndBlck*(
body
func toBeaconBlockHeader*(
blck: SomeForkyBeaconBlock | BlindedBeaconBlock): BeaconBlockHeader =
blck: SomeForkyBeaconBlock | bellatrix_mev.BlindedBeaconBlock |
capella_mev.BlindedBeaconBlock): BeaconBlockHeader =
## Reduce a given `BeaconBlock` to its `BeaconBlockHeader`.
BeaconBlockHeader(
slot: blck.slot,

View File

@ -73,7 +73,7 @@ func shortLog*(v: BlindedBeaconBlock): auto =
block_number: v.body.execution_payload_header.block_number,
# TODO checksum hex? shortlog?
fee_recipient: to0xHex(v.body.execution_payload_header.fee_recipient.data),
bls_to_execution_changes_len: v.body.bls_to_execution_changes.len()
#bls_to_execution_changes_len: v.body.bls_to_execution_changes.len(), # TODO validator_client/block_service.nim seems to want same shape of shortLogs across all block types
)
func shortLog*(v: SignedBlindedBeaconBlock): auto =

View File

@ -19,10 +19,10 @@ proc registerValidator*(body: seq[SignedValidatorRegistrationV1]
## https://github.com/ethereum/builder-specs/blob/v0.3.0/apis/builder/validators.yaml
## https://github.com/ethereum/beacon-APIs/blob/v2.3.0/apis/validator/register_validator.yaml
proc getHeader*(slot: Slot,
parent_hash: Eth2Digest,
pubkey: ValidatorPubKey
): RestResponse[GetHeaderResponse] {.
proc getHeaderBellatrix*(slot: Slot,
parent_hash: Eth2Digest,
pubkey: ValidatorPubKey
): RestResponse[GetHeaderResponseBellatrix] {.
rest, endpoint: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}",
meth: MethodGet, connection: {Dedicated, Close}.}
## https://github.com/ethereum/builder-specs/blob/v0.3.0/apis/builder/header.yaml

View File

@ -12,17 +12,16 @@ import
export chronos, client, rest_types, eth2_rest_serialization
# TODO
#proc getHeader*(slot: Slot,
# parent_hash: Eth2Digest,
# pubkey: ValidatorPubKey
# ): RestResponse[GetHeaderResponse] {.
# rest, endpoint: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}",
# meth: MethodGet, connection: {Dedicated, Close}.}
# ## https://github.com/jimmygchen/builder-specs/blob/0e15394bc239d3fee1ba9e42f4ce67ff6565537b/apis/builder/header.yaml
proc getHeaderCapella*(slot: Slot,
parent_hash: Eth2Digest,
pubkey: ValidatorPubKey
): RestResponse[GetHeaderResponseCapella] {.
rest, endpoint: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}",
meth: MethodGet, connection: {Dedicated, Close}.}
## https://github.com/ethereum/builder-specs/blob/v0.3.0/apis/builder/header.yaml
proc submitBlindedBlock*(body: capella_mev.SignedBlindedBeaconBlock
): RestResponse[SubmitBlindedBlockResponseCapella] {.
rest, endpoint: "/eth/v1/builder/blinded_blocks",
meth: MethodPost, connection: {Dedicated, Close}.}
## https://github.com/jimmygchen/builder-specs/blob/0e15394bc239d3fee1ba9e42f4ce67ff6565537b/apis/builder/blinded_blocks.yaml
## https://github.com/ethereum/builder-specs/blob/v0.3.0/apis/builder/blinded_blocks.yaml

View File

@ -17,8 +17,7 @@
## functions.
import
./datatypes/[phase0, altair, bellatrix], ./mev/bellatrix_mev, ./helpers,
./eth2_merkleization
./datatypes/[phase0, altair, bellatrix], ./helpers, ./eth2_merkleization
from ./datatypes/capella import BLSToExecutionChange, SignedBLSToExecutionChange
@ -91,7 +90,8 @@ func compute_block_signing_root*(
fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot,
blck: Eth2Digest | SomeForkyBeaconBlock | BeaconBlockHeader |
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/builder.md#signing
BlindedBeaconBlock): Eth2Digest =
bellatrix_mev.BlindedBeaconBlock | capella_mev.BlindedBeaconBlock):
Eth2Digest =
let
epoch = epoch(slot)
domain = get_domain(
@ -347,7 +347,9 @@ proc verify_contribution_and_proof_signature*(
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/builder.md#signing
func compute_builder_signing_root*(
fork: Fork, msg: BuilderBid | ValidatorRegistrationV1): Eth2Digest =
fork: Fork,
msg: bellatrix_mev.BuilderBid | capella_mev.BuilderBid |
ValidatorRegistrationV1): Eth2Digest =
# Uses genesis fork version regardless
doAssert fork.current_version == fork.previous_version
@ -362,7 +364,7 @@ proc get_builder_signature*(
blsSign(privkey, signing_root.data)
proc verify_builder_signature*(
fork: Fork, msg: BuilderBid,
fork: Fork, msg: bellatrix_mev.BuilderBid | capella_mev.BuilderBid,
pubkey: ValidatorPubKey | CookedPubKey, signature: SomeSig): bool =
let signing_root = compute_builder_signing_root(fork, msg)
blsVerify(pubkey, signing_root.data, signature)

View File

@ -36,7 +36,7 @@ macro copyFields*(
# 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"]:
"execution_payload_header", "body", "withdrawals_root"]:
# TODO use stew/assign2
result.add newAssignment(
newDotExpr(dst, ident(name)), newDotExpr(src, ident(name)))

View File

@ -16,7 +16,7 @@ import
std/[os, tables, sequtils],
# Nimble packages
stew/byteutils,
stew/[assign2, byteutils],
chronos, metrics,
chronicles, chronicles/timings,
json_serialization/std/[options, sets, net],
@ -86,7 +86,6 @@ logScope: topics = "beacval"
type
ForkedBlockResult* = Result[ForkedBeaconBlock, string]
BlindedBlockResult* = Result[bellatrix_mev.BlindedBeaconBlock, string]
SyncStatus* {.pure.} = enum
synced
@ -337,7 +336,8 @@ proc getGasLimit(node: BeaconNode,
from web3/engine_api_types import PayloadExecutionStatus
from ../spec/datatypes/capella import BeaconBlock, ExecutionPayload
from ../spec/datatypes/eip4844 import BeaconBlock, ExecutionPayload
from ../spec/datatypes/eip4844 import
BeaconBlock, ExecutionPayload, shortLog
proc getExecutionPayload[T](
node: BeaconNode, proposalState: ref ForkedHashedBeaconState,
@ -487,9 +487,12 @@ proc makeBeaconBlockForHeadAndSlot*[EP](
node: BeaconNode, randao_reveal: ValidatorSig,
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
slot: Slot,
# Thse parameters are for the builder API
execution_payload: Opt[EP],
transactions_root: Opt[Eth2Digest],
execution_payload_root: Opt[Eth2Digest]):
execution_payload_root: Opt[Eth2Digest],
withdrawals_root: Opt[Eth2Digest]):
Future[ForkedBlockResult] {.async.} =
# Advance state to the slot that we're proposing for
var cache = StateCache()
@ -510,14 +513,31 @@ proc makeBeaconBlockForHeadAndSlot*[EP](
state = maybeState.get
payloadFut =
if execution_payload.isSome:
# Builder API
# In Capella, only get withdrawals root from relay.
# The execution payload will be small enough to be safe to copy because
# it won't have transactions (it's blinded)
var modified_execution_payload = execution_payload
withState(state[]):
when stateFork >= ConsensusFork.Capella and
EP isnot bellatrix.ExecutionPayload:
let withdrawals = List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD](
get_expected_withdrawals(forkyState.data))
if withdrawals_root.isNone or
hash_tree_root(withdrawals) != withdrawals_root.get:
return err("Builder relay provided incorrect withdrawals root")
# Otherwise, the state transition function notices that there are
# too few withdrawals.
assign(modified_execution_payload.get.withdrawals, withdrawals)
let fut = newFuture[Opt[EP]]("given-payload")
fut.complete(execution_payload)
fut.complete(modified_execution_payload)
fut
elif slot.epoch < node.dag.cfg.BELLATRIX_FORK_EPOCH or not (
state[].is_merge_transition_complete or
slot.epoch >= node.mergeAtEpoch):
let fut = newFuture[Opt[EP]]("empty-payload")
# https://github.com/nim-lang/Nim/issues/19802
fut.complete(Opt.some(default(EP)))
fut
else:
@ -586,19 +606,30 @@ proc makeBeaconBlockForHeadAndSlot*[EP](
node, randao_reveal, validator_index, graffiti, head, slot,
execution_payload = Opt.none(EP),
transactions_root = Opt.none(Eth2Digest),
execution_payload_root = Opt.none(Eth2Digest))
execution_payload_root = Opt.none(Eth2Digest),
withdrawals_root = Opt.none(Eth2Digest))
proc getBlindedExecutionPayload(
proc getBlindedExecutionPayload[
EPH: bellatrix.ExecutionPayloadHeader | capella.ExecutionPayloadHeader](
node: BeaconNode, slot: Slot, executionBlockRoot: Eth2Digest,
pubkey: ValidatorPubKey):
Future[Result[bellatrix.ExecutionPayloadHeader, string]] {.async.} =
pubkey: ValidatorPubKey): Future[Result[EPH, string]] {.async.} =
if node.payloadBuilderRestClient.isNil:
return err "getBlindedExecutionPayload: nil REST client"
let blindedHeader = awaitWithTimeout(
node.payloadBuilderRestClient.getHeader(slot, executionBlockRoot, pubkey),
BUILDER_PROPOSAL_DELAY_TOLERANCE):
return err "Timeout when obtaining blinded header from builder"
when EPH is capella.ExecutionPayloadHeader:
let blindedHeader = awaitWithTimeout(
node.payloadBuilderRestClient.getHeaderCapella(
slot, executionBlockRoot, pubkey),
BUILDER_PROPOSAL_DELAY_TOLERANCE):
return err "Timeout when obtaining Capella blinded header from builder"
elif EPH is bellatrix.ExecutionPayloadHeader:
let blindedHeader = awaitWithTimeout(
node.payloadBuilderRestClient.getHeaderBellatrix(
slot, executionBlockRoot, pubkey),
BUILDER_PROPOSAL_DELAY_TOLERANCE):
return err "Timeout when obtaining Bellatrix blinded header from builder"
else:
static: doAssert false
const httpOk = 200
if blindedHeader.status != httpOk:
@ -616,35 +647,37 @@ from ./message_router_mev import
copyFields, getFieldNames, unblindAndRouteBlockMEV
func constructSignableBlindedBlock[T](
forkedBlock: ForkedBeaconBlock,
executionPayloadHeader: bellatrix.ExecutionPayloadHeader): T =
blck: bellatrix.BeaconBlock | capella.BeaconBlock,
executionPayloadHeader: bellatrix.ExecutionPayloadHeader |
capella.ExecutionPayloadHeader): T =
const
blckFields = getFieldNames(typeof(forkedBlock.bellatrixData))
blckBodyFields = getFieldNames(typeof(forkedBlock.bellatrixData.body))
blckFields = getFieldNames(typeof(blck))
blckBodyFields = getFieldNames(typeof(blck.body))
var blindedBlock: T
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/validator.md#block-proposal
copyFields(blindedBlock.message, forkedBlock.bellatrixData, blckFields)
copyFields(
blindedBlock.message.body, forkedBlock.bellatrixData.body, blckBodyFields)
blindedBlock.message.body.execution_payload_header = executionPayloadHeader
copyFields(blindedBlock.message, blck, blckFields)
copyFields(blindedBlock.message.body, blck.body, blckBodyFields)
assign(
blindedBlock.message.body.execution_payload_header, executionPayloadHeader)
blindedBlock
func constructPlainBlindedBlock[T](
forkedBlock: ForkedBeaconBlock,
executionPayloadHeader: bellatrix.ExecutionPayloadHeader): T =
func constructPlainBlindedBlock[
T: bellatrix_mev.BlindedBeaconBlock | capella_mev.BlindedBeaconBlock,
EPH: bellatrix.ExecutionPayloadHeader | capella.ExecutionPayloadHeader](
blck: ForkyBeaconBlock, executionPayloadHeader: EPH): T =
const
blckFields = getFieldNames(typeof(forkedBlock.bellatrixData))
blckBodyFields = getFieldNames(typeof(forkedBlock.bellatrixData.body))
blckFields = getFieldNames(typeof(blck))
blckBodyFields = getFieldNames(typeof(blck.body))
var blindedBlock: T
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/validator.md#block-proposal
copyFields(blindedBlock, forkedBlock.bellatrixData, blckFields)
copyFields(blindedBlock.body, forkedBlock.bellatrixData.body, blckBodyFields)
blindedBlock.body.execution_payload_header = executionPayloadHeader
copyFields(blindedBlock, blck, blckFields)
copyFields(blindedBlock.body, blck.body, blckBodyFields)
assign(blindedBlock.body.execution_payload_header, executionPayloadHeader)
blindedBlock
@ -683,40 +716,55 @@ proc blindedBlockCheckSlashingAndSign[T](
return ok blindedBlock
proc getBlindedBeaconBlock[T](
proc getBlindedBeaconBlock[
T: bellatrix_mev.SignedBlindedBeaconBlock |
capella_mev.SignedBlindedBeaconBlock](
node: BeaconNode, slot: Slot, validator: AttachedValidator,
validator_index: ValidatorIndex, forkedBlock: ForkedBeaconBlock,
executionPayloadHeader: bellatrix.ExecutionPayloadHeader):
executionPayloadHeader: bellatrix.ExecutionPayloadHeader |
capella.ExecutionPayloadHeader):
Future[Result[T, string]] {.async.} =
return await blindedBlockCheckSlashingAndSign(
node, slot, validator, validator_index, constructSignableBlindedBlock[T](
forkedBlock, executionPayloadHeader))
withBlck(forkedBlock):
when stateFork >= ConsensusFork.EIP4844:
debugRaiseAssert $eip4844ImplementationMissing & ": getBlindedBeaconBlock"
return err("getBlindedBeaconBlock: Deneb blinded block creation not implemented")
elif stateFork >= ConsensusFork.Bellatrix:
when not (
(T is bellatrix_mev.SignedBlindedBeaconBlock and
stateFork == ConsensusFork.Bellatrix) or
(T is capella_mev.SignedBlindedBeaconBlock and
stateFork == ConsensusFork.Capella)):
return err("getBlindedBeaconBlock: mismatched block/payload types")
else:
return await blindedBlockCheckSlashingAndSign(
node, slot, validator, validator_index,
constructSignableBlindedBlock[T](blck, executionPayloadHeader))
else:
return err("getBlindedBeaconBlock: attempt to construct pre-Bellatrix blinded block")
proc getBlindedBlockParts(
proc getBlindedBlockParts[
EPH: bellatrix.ExecutionPayloadHeader | capella.ExecutionPayloadHeader](
node: BeaconNode, head: BlockRef, pubkey: ValidatorPubKey,
slot: Slot, randao: ValidatorSig, validator_index: ValidatorIndex,
graffiti: GraffitiBytes):
Future[Result[(bellatrix.ExecutionPayloadHeader, ForkedBeaconBlock), string]]
graffiti: GraffitiBytes): Future[Result[(EPH, ForkedBeaconBlock), string]]
{.async.} =
let
executionBlockRoot = node.dag.loadExecutionBlockRoot(head)
executionPayloadHeader =
try:
awaitWithTimeout(
node.getBlindedExecutionPayload(
slot, executionBlockRoot, pubkey),
getBlindedExecutionPayload[EPH](
node, slot, executionBlockRoot, pubkey),
BUILDER_PROPOSAL_DELAY_TOLERANCE):
Result[bellatrix.ExecutionPayloadHeader, string].err(
"getBlindedExecutionPayload timed out")
Result[EPH, string].err("getBlindedExecutionPayload timed out")
except RestDecodingError as exc:
Result[bellatrix.ExecutionPayloadHeader, string].err(
Result[EPH, string].err(
"getBlindedExecutionPayload REST decoding error")
except CatchableError as exc:
Result[bellatrix.ExecutionPayloadHeader, string].err(
"getBlindedExecutionPayload error")
Result[EPH, string].err("getBlindedExecutionPayload error")
if executionPayloadHeader.isErr:
debug "proposeBlockMEV: getBlindedExecutionPayload failed",
debug "getBlindedBlockParts: getBlindedExecutionPayload failed",
error = executionPayloadHeader.error, slot, validator_index,
head = shortLog(head)
# Haven't committed to the MEV block, so allow EL fallback.
@ -728,17 +776,29 @@ proc getBlindedBlockParts(
# processing does not work directly using blinded blocks, fix up transactions
# root after running the state transition function on an otherwise equivalent
# non-blinded block without transactions.
var shimExecutionPayload: bellatrix.ExecutionPayload
copyFields(
shimExecutionPayload, executionPayloadHeader.get,
getFieldNames(bellatrix.ExecutionPayloadHeader))
when EPH is bellatrix.ExecutionPayloadHeader:
type EP = bellatrix.ExecutionPayload
let withdrawals_root = Opt.none Eth2Digest
elif EPH is capella.ExecutionPayloadHeader:
type EP = capella.ExecutionPayload
let withdrawals_root = Opt.some executionPayloadHeader.get.withdrawals_root
else:
static: doAssert false
let newBlock = await makeBeaconBlockForHeadAndSlot[bellatrix.ExecutionPayload](
var shimExecutionPayload: EP
copyFields(
shimExecutionPayload, executionPayloadHeader.get, getFieldNames(EPH))
# In Capella and later, this doesn't have withdrawals, which each node knows
# regardless of EL or builder API. makeBeaconBlockForHeadAndSlot fills it in
# when it detects builder API usage.
let newBlock = await makeBeaconBlockForHeadAndSlot[EP](
node, randao, validator_index, graffiti, head, slot,
execution_payload = Opt.some shimExecutionPayload,
transactions_root = Opt.some executionPayloadHeader.get.transactions_root,
execution_payload_root =
Opt.some hash_tree_root(executionPayloadHeader.get))
Opt.some hash_tree_root(executionPayloadHeader.get),
withdrawals_root = withdrawals_root)
if newBlock.isErr():
# Haven't committed to the MEV block, so allow EL fallback.
@ -748,11 +808,20 @@ proc getBlindedBlockParts(
return ok((executionPayloadHeader.get, forkedBlck))
proc proposeBlockMEV(
proc proposeBlockMEV[
SBBB: bellatrix_mev.SignedBlindedBeaconBlock |
capella_mev.SignedBlindedBeaconBlock](
node: BeaconNode, head: BlockRef, validator: AttachedValidator, slot: Slot,
randao: ValidatorSig, validator_index: ValidatorIndex):
Future[Opt[BlockRef]] {.async.} =
let blindedBlockParts = await getBlindedBlockParts(
when SBBB is bellatrix_mev.SignedBlindedBeaconBlock:
type EPH = bellatrix.ExecutionPayloadHeader
elif SBBB is capella_mev.SignedBlindedBeaconBlock:
type EPH = capella.ExecutionPayloadHeader
else:
static: doAssert false
let blindedBlockParts = await getBlindedBlockParts[EPH](
node, head, validator.pubkey, slot, randao, validator_index,
node.graffitiBytes)
if blindedBlockParts.isErr:
@ -766,12 +835,11 @@ proc proposeBlockMEV(
# This is only substantively asynchronous with a remote key signer
let blindedBlock = awaitWithTimeout(
getBlindedBeaconBlock[bellatrix_mev.SignedBlindedBeaconBlock](
getBlindedBeaconBlock[SBBB](
node, slot, validator, validator_index, forkedBlck,
executionPayloadHeader),
500.milliseconds):
Result[bellatrix_mev.SignedBlindedBeaconBlock, string].err(
"getBlindedBlock timed out")
Result[SBBB, string].err("getBlindedBlock timed out")
if blindedBlock.isErr:
info "proposeBlockMEV: getBlindedBeaconBlock failed",
@ -800,19 +868,27 @@ proc proposeBlockMEV(
unblindedBlockRef.error
else:
"Unblinded block failed either to validate or integrate into validated store"
warn "proposeBlockMEV: blinded block not successfully unblinded and proposed",
warn "proposeBlockMEV: blinded block either not successfully unblinded or not successfully proposed",
head = shortLog(head), slot, validator_index,
validator = shortLog(validator),
err = errMsg, blindedBlck = shortLog(blindedBlock.get)
Opt.some head
proc makeBlindedBeaconBlockForHeadAndSlot*(
proc makeBlindedBeaconBlockForHeadAndSlot*[
BBB: bellatrix_mev.BlindedBeaconBlock | capella_mev.BlindedBeaconBlock](
node: BeaconNode, randao_reveal: ValidatorSig,
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
slot: Slot): Future[BlindedBlockResult] {.async.} =
slot: Slot): Future[Result[BBB, string]] {.async.} =
## Requests a beacon node to produce a valid blinded block, which can then be
## signed by a validator. A blinded block is a block with only a transactions
## root, rather than a full transactions list.
when BBB is bellatrix_mev.BlindedBeaconBlock:
type EPH = bellatrix.ExecutionPayloadHeader
elif BBB is capella_mev.BlindedBeaconBlock:
type EPH = capella.ExecutionPayloadHeader
else:
static: doAssert false
let
pubkey =
# Relevant state for knowledge of validators
@ -826,17 +902,27 @@ proc makeBlindedBeaconBlockForHeadAndSlot*(
forkyState.data.validators.item(validator_index).pubkey
blindedBlockParts = await getBlindedBlockParts(
blindedBlockParts = await getBlindedBlockParts[EPH](
node, head, pubkey, slot, randao_reveal, validator_index, graffiti)
if blindedBlockParts.isErr:
# Don't try EL fallback -- VC specifically requested a blinded block
return err("Unable to create blinded block")
let (executionPayloadHeader, forkedBlck) = blindedBlockParts.get
return ok constructPlainBlindedBlock[bellatrix_mev.BlindedBeaconBlock](
forkedBlck, executionPayloadHeader)
from ../spec/datatypes/eip4844 import shortLog
withBlck(forkedBlck):
when stateFork >= ConsensusFork.EIP4844:
debugRaiseAssert $eip4844ImplementationMissing & ": makeBlindedBeaconBlockForHeadAndSlot"
elif stateFork >= ConsensusFork.Bellatrix:
when ((stateFork == ConsensusFork.Bellatrix and
EPH is bellatrix.ExecutionPayloadHeader) or
(stateFork == ConsensusFork.Capella and
EPH is capella.ExecutionPayloadHeader)):
return ok constructPlainBlindedBlock[BBB, EPH](
blck, executionPayloadHeader)
else:
return err("makeBlindedBeaconBlockForHeadAndSlot: mismatched block/payload types")
else:
return err("Attempt to create pre-Bellatrix blinded block")
proc proposeBlock(node: BeaconNode,
validator: AttachedValidator,
@ -866,8 +952,20 @@ proc proposeBlock(node: BeaconNode,
res.get()
if node.config.payloadBuilderEnable:
let newBlockMEV = await node.proposeBlockMEV(
head, validator, slot, randao, validator_index)
let newBlockMEV =
if slot.epoch >= node.dag.cfg.DENEB_FORK_EPOCH:
debugRaiseAssert $eip4844ImplementationMissing & ": proposeBlock"
await proposeBlockMEV[
capella_mev.SignedBlindedBeaconBlock](
node, head, validator, slot, randao, validator_index)
elif slot.epoch >= node.dag.cfg.CAPELLA_FORK_EPOCH:
await proposeBlockMEV[
capella_mev.SignedBlindedBeaconBlock](
node, head, validator, slot, randao, validator_index)
else:
await proposeBlockMEV[
bellatrix_mev.SignedBlindedBeaconBlock](
node, head, validator, slot, randao, validator_index)
if newBlockMEV.isSome:
# This might be equivalent to the `head` passed in, but it signals that

View File

@ -418,7 +418,8 @@ proc getBlockSignature*(v: AttachedValidator, fork: Fork,
genesis_validators_root: Eth2Digest, slot: Slot,
block_root: Eth2Digest,
blck: ForkedBeaconBlock | ForkedBlindedBeaconBlock |
bellatrix_mev.BlindedBeaconBlock
bellatrix_mev.BlindedBeaconBlock |
capella_mev.BlindedBeaconBlock
): Future[SignatureResult] {.async.} =
return
case v.kind
@ -456,13 +457,20 @@ proc getBlockSignature*(v: AttachedValidator, fork: Fork,
request = Web3SignerRequest.init(
fork, genesis_validators_root, web3SignerBlock)
await v.signData(request)
elif blck is BlindedBeaconBlock:
elif blck is bellatrix_mev.BlindedBeaconBlock:
let request = Web3SignerRequest.init(
fork, genesis_validators_root,
Web3SignerForkedBeaconBlock(
kind: ConsensusFork.Bellatrix,
bellatrixData: blck.toBeaconBlockHeader))
await v.signData(request)
elif blck is capella_mev.BlindedBeaconBlock:
let request = Web3SignerRequest.init(
fork, genesis_validators_root,
Web3SignerForkedBeaconBlock(
kind: ConsensusFork.Capella,
capellaData: blck.toBeaconBlockHeader))
await v.signData(request)
else:
let
web3SignerBlock =