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 # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * 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). # * 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: else:
RestApiResponse.jsonError(Http500, InvalidAcceptError) RestApiResponse.jsonError(Http500, InvalidAcceptError)
if node.currentSlot().epoch() >= node.dag.cfg.BELLATRIX_FORK_EPOCH: static: doAssert high(ConsensusFork) == ConsensusFork.EIP4844
let res = await makeBlindedBeaconBlockForHeadAndSlot( 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) node, qrandao, proposer, qgraffiti, qhead, qslot)
if res.isErr(): if res.isErr():
return RestApiResponse.jsonError(Http400, res.error()) return RestApiResponse.jsonError(Http400, res.error())

View File

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

View File

@ -149,6 +149,12 @@ proc publishBlindedBlock*(body: bellatrix_mev.SignedBlindedBeaconBlock):
meth: MethodPost.} meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock ## 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*( proc publishSszBlindedBlock*(
client: RestClientRef, client: RestClientRef,
blck: ForkySignedBeaconBlock blck: ForkySignedBeaconBlock

View File

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

View File

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

View File

@ -73,7 +73,7 @@ func shortLog*(v: BlindedBeaconBlock): auto =
block_number: v.body.execution_payload_header.block_number, block_number: v.body.execution_payload_header.block_number,
# TODO checksum hex? shortlog? # TODO checksum hex? shortlog?
fee_recipient: to0xHex(v.body.execution_payload_header.fee_recipient.data), 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 = 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/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 ## https://github.com/ethereum/beacon-APIs/blob/v2.3.0/apis/validator/register_validator.yaml
proc getHeader*(slot: Slot, proc getHeaderBellatrix*(slot: Slot,
parent_hash: Eth2Digest, parent_hash: Eth2Digest,
pubkey: ValidatorPubKey pubkey: ValidatorPubKey
): RestResponse[GetHeaderResponse] {. ): RestResponse[GetHeaderResponseBellatrix] {.
rest, endpoint: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}", rest, endpoint: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}",
meth: MethodGet, connection: {Dedicated, Close}.} meth: MethodGet, connection: {Dedicated, Close}.}
## https://github.com/ethereum/builder-specs/blob/v0.3.0/apis/builder/header.yaml ## 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 export chronos, client, rest_types, eth2_rest_serialization
# TODO proc getHeaderCapella*(slot: Slot,
#proc getHeader*(slot: Slot, parent_hash: Eth2Digest,
# parent_hash: Eth2Digest, pubkey: ValidatorPubKey
# pubkey: ValidatorPubKey ): RestResponse[GetHeaderResponseCapella] {.
# ): RestResponse[GetHeaderResponse] {. rest, endpoint: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}",
# rest, endpoint: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}", meth: MethodGet, connection: {Dedicated, Close}.}
# meth: MethodGet, connection: {Dedicated, Close}.} ## https://github.com/ethereum/builder-specs/blob/v0.3.0/apis/builder/header.yaml
# ## https://github.com/jimmygchen/builder-specs/blob/0e15394bc239d3fee1ba9e42f4ce67ff6565537b/apis/builder/header.yaml
proc submitBlindedBlock*(body: capella_mev.SignedBlindedBeaconBlock proc submitBlindedBlock*(body: capella_mev.SignedBlindedBeaconBlock
): RestResponse[SubmitBlindedBlockResponseCapella] {. ): RestResponse[SubmitBlindedBlockResponseCapella] {.
rest, endpoint: "/eth/v1/builder/blinded_blocks", rest, endpoint: "/eth/v1/builder/blinded_blocks",
meth: MethodPost, connection: {Dedicated, Close}.} 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. ## functions.
import import
./datatypes/[phase0, altair, bellatrix], ./mev/bellatrix_mev, ./helpers, ./datatypes/[phase0, altair, bellatrix], ./helpers, ./eth2_merkleization
./eth2_merkleization
from ./datatypes/capella import BLSToExecutionChange, SignedBLSToExecutionChange from ./datatypes/capella import BLSToExecutionChange, SignedBLSToExecutionChange
@ -91,7 +90,8 @@ func compute_block_signing_root*(
fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot, fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot,
blck: Eth2Digest | SomeForkyBeaconBlock | BeaconBlockHeader | blck: Eth2Digest | SomeForkyBeaconBlock | BeaconBlockHeader |
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/builder.md#signing # 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 let
epoch = epoch(slot) epoch = epoch(slot)
domain = get_domain( 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 # https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/builder.md#signing
func compute_builder_signing_root*( 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 # Uses genesis fork version regardless
doAssert fork.current_version == fork.previous_version doAssert fork.current_version == fork.previous_version
@ -362,7 +364,7 @@ proc get_builder_signature*(
blsSign(privkey, signing_root.data) blsSign(privkey, signing_root.data)
proc verify_builder_signature*( proc verify_builder_signature*(
fork: Fork, msg: BuilderBid, fork: Fork, msg: bellatrix_mev.BuilderBid | capella_mev.BuilderBid,
pubkey: ValidatorPubKey | CookedPubKey, signature: SomeSig): bool = pubkey: ValidatorPubKey | CookedPubKey, signature: SomeSig): bool =
let signing_root = compute_builder_signing_root(fork, msg) let signing_root = compute_builder_signing_root(fork, msg)
blsVerify(pubkey, signing_root.data, signature) 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 # These fields are the ones which vary between the blinded and
# unblinded objects, and can't simply be copied. # unblinded objects, and can't simply be copied.
"transactions_root", "execution_payload", "transactions_root", "execution_payload",
"execution_payload_header", "body"]: "execution_payload_header", "body", "withdrawals_root"]:
# TODO use stew/assign2 # TODO use stew/assign2
result.add newAssignment( result.add newAssignment(
newDotExpr(dst, ident(name)), newDotExpr(src, ident(name))) newDotExpr(dst, ident(name)), newDotExpr(src, ident(name)))

View File

@ -16,7 +16,7 @@ import
std/[os, tables, sequtils], std/[os, tables, sequtils],
# Nimble packages # Nimble packages
stew/byteutils, stew/[assign2, byteutils],
chronos, metrics, chronos, metrics,
chronicles, chronicles/timings, chronicles, chronicles/timings,
json_serialization/std/[options, sets, net], json_serialization/std/[options, sets, net],
@ -86,7 +86,6 @@ logScope: topics = "beacval"
type type
ForkedBlockResult* = Result[ForkedBeaconBlock, string] ForkedBlockResult* = Result[ForkedBeaconBlock, string]
BlindedBlockResult* = Result[bellatrix_mev.BlindedBeaconBlock, string]
SyncStatus* {.pure.} = enum SyncStatus* {.pure.} = enum
synced synced
@ -337,7 +336,8 @@ proc getGasLimit(node: BeaconNode,
from web3/engine_api_types import PayloadExecutionStatus from web3/engine_api_types import PayloadExecutionStatus
from ../spec/datatypes/capella import BeaconBlock, ExecutionPayload 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]( proc getExecutionPayload[T](
node: BeaconNode, proposalState: ref ForkedHashedBeaconState, node: BeaconNode, proposalState: ref ForkedHashedBeaconState,
@ -487,9 +487,12 @@ proc makeBeaconBlockForHeadAndSlot*[EP](
node: BeaconNode, randao_reveal: ValidatorSig, node: BeaconNode, randao_reveal: ValidatorSig,
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef, validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
slot: Slot, slot: Slot,
# Thse parameters are for the builder API
execution_payload: Opt[EP], execution_payload: Opt[EP],
transactions_root: Opt[Eth2Digest], transactions_root: Opt[Eth2Digest],
execution_payload_root: Opt[Eth2Digest]): execution_payload_root: Opt[Eth2Digest],
withdrawals_root: Opt[Eth2Digest]):
Future[ForkedBlockResult] {.async.} = Future[ForkedBlockResult] {.async.} =
# Advance state to the slot that we're proposing for # Advance state to the slot that we're proposing for
var cache = StateCache() var cache = StateCache()
@ -510,14 +513,31 @@ proc makeBeaconBlockForHeadAndSlot*[EP](
state = maybeState.get state = maybeState.get
payloadFut = payloadFut =
if execution_payload.isSome: 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") let fut = newFuture[Opt[EP]]("given-payload")
fut.complete(execution_payload) fut.complete(modified_execution_payload)
fut fut
elif slot.epoch < node.dag.cfg.BELLATRIX_FORK_EPOCH or not ( elif slot.epoch < node.dag.cfg.BELLATRIX_FORK_EPOCH or not (
state[].is_merge_transition_complete or state[].is_merge_transition_complete or
slot.epoch >= node.mergeAtEpoch): slot.epoch >= node.mergeAtEpoch):
let fut = newFuture[Opt[EP]]("empty-payload") let fut = newFuture[Opt[EP]]("empty-payload")
# https://github.com/nim-lang/Nim/issues/19802
fut.complete(Opt.some(default(EP))) fut.complete(Opt.some(default(EP)))
fut fut
else: else:
@ -586,19 +606,30 @@ proc makeBeaconBlockForHeadAndSlot*[EP](
node, randao_reveal, validator_index, graffiti, head, slot, node, randao_reveal, validator_index, graffiti, head, slot,
execution_payload = Opt.none(EP), execution_payload = Opt.none(EP),
transactions_root = Opt.none(Eth2Digest), 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, node: BeaconNode, slot: Slot, executionBlockRoot: Eth2Digest,
pubkey: ValidatorPubKey): pubkey: ValidatorPubKey): Future[Result[EPH, string]] {.async.} =
Future[Result[bellatrix.ExecutionPayloadHeader, string]] {.async.} =
if node.payloadBuilderRestClient.isNil: if node.payloadBuilderRestClient.isNil:
return err "getBlindedExecutionPayload: nil REST client" return err "getBlindedExecutionPayload: nil REST client"
let blindedHeader = awaitWithTimeout( when EPH is capella.ExecutionPayloadHeader:
node.payloadBuilderRestClient.getHeader(slot, executionBlockRoot, pubkey), let blindedHeader = awaitWithTimeout(
BUILDER_PROPOSAL_DELAY_TOLERANCE): node.payloadBuilderRestClient.getHeaderCapella(
return err "Timeout when obtaining blinded header from builder" 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 const httpOk = 200
if blindedHeader.status != httpOk: if blindedHeader.status != httpOk:
@ -616,35 +647,37 @@ from ./message_router_mev import
copyFields, getFieldNames, unblindAndRouteBlockMEV copyFields, getFieldNames, unblindAndRouteBlockMEV
func constructSignableBlindedBlock[T]( func constructSignableBlindedBlock[T](
forkedBlock: ForkedBeaconBlock, blck: bellatrix.BeaconBlock | capella.BeaconBlock,
executionPayloadHeader: bellatrix.ExecutionPayloadHeader): T = executionPayloadHeader: bellatrix.ExecutionPayloadHeader |
capella.ExecutionPayloadHeader): T =
const const
blckFields = getFieldNames(typeof(forkedBlock.bellatrixData)) blckFields = getFieldNames(typeof(blck))
blckBodyFields = getFieldNames(typeof(forkedBlock.bellatrixData.body)) blckBodyFields = getFieldNames(typeof(blck.body))
var blindedBlock: T var blindedBlock: T
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/validator.md#block-proposal # 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, blck, blckFields)
copyFields( copyFields(blindedBlock.message.body, blck.body, blckBodyFields)
blindedBlock.message.body, forkedBlock.bellatrixData.body, blckBodyFields) assign(
blindedBlock.message.body.execution_payload_header = executionPayloadHeader blindedBlock.message.body.execution_payload_header, executionPayloadHeader)
blindedBlock blindedBlock
func constructPlainBlindedBlock[T]( func constructPlainBlindedBlock[
forkedBlock: ForkedBeaconBlock, T: bellatrix_mev.BlindedBeaconBlock | capella_mev.BlindedBeaconBlock,
executionPayloadHeader: bellatrix.ExecutionPayloadHeader): T = EPH: bellatrix.ExecutionPayloadHeader | capella.ExecutionPayloadHeader](
blck: ForkyBeaconBlock, executionPayloadHeader: EPH): T =
const const
blckFields = getFieldNames(typeof(forkedBlock.bellatrixData)) blckFields = getFieldNames(typeof(blck))
blckBodyFields = getFieldNames(typeof(forkedBlock.bellatrixData.body)) blckBodyFields = getFieldNames(typeof(blck.body))
var blindedBlock: T var blindedBlock: T
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/validator.md#block-proposal # https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/validator.md#block-proposal
copyFields(blindedBlock, forkedBlock.bellatrixData, blckFields) copyFields(blindedBlock, blck, blckFields)
copyFields(blindedBlock.body, forkedBlock.bellatrixData.body, blckBodyFields) copyFields(blindedBlock.body, blck.body, blckBodyFields)
blindedBlock.body.execution_payload_header = executionPayloadHeader assign(blindedBlock.body.execution_payload_header, executionPayloadHeader)
blindedBlock blindedBlock
@ -683,40 +716,55 @@ proc blindedBlockCheckSlashingAndSign[T](
return ok blindedBlock return ok blindedBlock
proc getBlindedBeaconBlock[T]( proc getBlindedBeaconBlock[
T: bellatrix_mev.SignedBlindedBeaconBlock |
capella_mev.SignedBlindedBeaconBlock](
node: BeaconNode, slot: Slot, validator: AttachedValidator, node: BeaconNode, slot: Slot, validator: AttachedValidator,
validator_index: ValidatorIndex, forkedBlock: ForkedBeaconBlock, validator_index: ValidatorIndex, forkedBlock: ForkedBeaconBlock,
executionPayloadHeader: bellatrix.ExecutionPayloadHeader): executionPayloadHeader: bellatrix.ExecutionPayloadHeader |
capella.ExecutionPayloadHeader):
Future[Result[T, string]] {.async.} = Future[Result[T, string]] {.async.} =
return await blindedBlockCheckSlashingAndSign( withBlck(forkedBlock):
node, slot, validator, validator_index, constructSignableBlindedBlock[T]( when stateFork >= ConsensusFork.EIP4844:
forkedBlock, executionPayloadHeader)) 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, node: BeaconNode, head: BlockRef, pubkey: ValidatorPubKey,
slot: Slot, randao: ValidatorSig, validator_index: ValidatorIndex, slot: Slot, randao: ValidatorSig, validator_index: ValidatorIndex,
graffiti: GraffitiBytes): graffiti: GraffitiBytes): Future[Result[(EPH, ForkedBeaconBlock), string]]
Future[Result[(bellatrix.ExecutionPayloadHeader, ForkedBeaconBlock), string]]
{.async.} = {.async.} =
let let
executionBlockRoot = node.dag.loadExecutionBlockRoot(head) executionBlockRoot = node.dag.loadExecutionBlockRoot(head)
executionPayloadHeader = executionPayloadHeader =
try: try:
awaitWithTimeout( awaitWithTimeout(
node.getBlindedExecutionPayload( getBlindedExecutionPayload[EPH](
slot, executionBlockRoot, pubkey), node, slot, executionBlockRoot, pubkey),
BUILDER_PROPOSAL_DELAY_TOLERANCE): BUILDER_PROPOSAL_DELAY_TOLERANCE):
Result[bellatrix.ExecutionPayloadHeader, string].err( Result[EPH, string].err("getBlindedExecutionPayload timed out")
"getBlindedExecutionPayload timed out")
except RestDecodingError as exc: except RestDecodingError as exc:
Result[bellatrix.ExecutionPayloadHeader, string].err( Result[EPH, string].err(
"getBlindedExecutionPayload REST decoding error") "getBlindedExecutionPayload REST decoding error")
except CatchableError as exc: except CatchableError as exc:
Result[bellatrix.ExecutionPayloadHeader, string].err( Result[EPH, string].err("getBlindedExecutionPayload error")
"getBlindedExecutionPayload error")
if executionPayloadHeader.isErr: if executionPayloadHeader.isErr:
debug "proposeBlockMEV: getBlindedExecutionPayload failed", debug "getBlindedBlockParts: getBlindedExecutionPayload failed",
error = executionPayloadHeader.error, slot, validator_index, error = executionPayloadHeader.error, slot, validator_index,
head = shortLog(head) head = shortLog(head)
# Haven't committed to the MEV block, so allow EL fallback. # 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 # processing does not work directly using blinded blocks, fix up transactions
# root after running the state transition function on an otherwise equivalent # root after running the state transition function on an otherwise equivalent
# non-blinded block without transactions. # non-blinded block without transactions.
var shimExecutionPayload: bellatrix.ExecutionPayload when EPH is bellatrix.ExecutionPayloadHeader:
copyFields( type EP = bellatrix.ExecutionPayload
shimExecutionPayload, executionPayloadHeader.get, let withdrawals_root = Opt.none Eth2Digest
getFieldNames(bellatrix.ExecutionPayloadHeader)) 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, node, randao, validator_index, graffiti, head, slot,
execution_payload = Opt.some shimExecutionPayload, execution_payload = Opt.some shimExecutionPayload,
transactions_root = Opt.some executionPayloadHeader.get.transactions_root, transactions_root = Opt.some executionPayloadHeader.get.transactions_root,
execution_payload_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(): if newBlock.isErr():
# Haven't committed to the MEV block, so allow EL fallback. # Haven't committed to the MEV block, so allow EL fallback.
@ -748,11 +808,20 @@ proc getBlindedBlockParts(
return ok((executionPayloadHeader.get, forkedBlck)) return ok((executionPayloadHeader.get, forkedBlck))
proc proposeBlockMEV( proc proposeBlockMEV[
SBBB: bellatrix_mev.SignedBlindedBeaconBlock |
capella_mev.SignedBlindedBeaconBlock](
node: BeaconNode, head: BlockRef, validator: AttachedValidator, slot: Slot, node: BeaconNode, head: BlockRef, validator: AttachedValidator, slot: Slot,
randao: ValidatorSig, validator_index: ValidatorIndex): randao: ValidatorSig, validator_index: ValidatorIndex):
Future[Opt[BlockRef]] {.async.} = 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, head, validator.pubkey, slot, randao, validator_index,
node.graffitiBytes) node.graffitiBytes)
if blindedBlockParts.isErr: if blindedBlockParts.isErr:
@ -766,12 +835,11 @@ proc proposeBlockMEV(
# This is only substantively asynchronous with a remote key signer # This is only substantively asynchronous with a remote key signer
let blindedBlock = awaitWithTimeout( let blindedBlock = awaitWithTimeout(
getBlindedBeaconBlock[bellatrix_mev.SignedBlindedBeaconBlock]( getBlindedBeaconBlock[SBBB](
node, slot, validator, validator_index, forkedBlck, node, slot, validator, validator_index, forkedBlck,
executionPayloadHeader), executionPayloadHeader),
500.milliseconds): 500.milliseconds):
Result[bellatrix_mev.SignedBlindedBeaconBlock, string].err( Result[SBBB, string].err("getBlindedBlock timed out")
"getBlindedBlock timed out")
if blindedBlock.isErr: if blindedBlock.isErr:
info "proposeBlockMEV: getBlindedBeaconBlock failed", info "proposeBlockMEV: getBlindedBeaconBlock failed",
@ -800,19 +868,27 @@ proc proposeBlockMEV(
unblindedBlockRef.error unblindedBlockRef.error
else: else:
"Unblinded block failed either to validate or integrate into validated store" "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, head = shortLog(head), slot, validator_index,
validator = shortLog(validator), validator = shortLog(validator),
err = errMsg, blindedBlck = shortLog(blindedBlock.get) err = errMsg, blindedBlck = shortLog(blindedBlock.get)
Opt.some head Opt.some head
proc makeBlindedBeaconBlockForHeadAndSlot*( proc makeBlindedBeaconBlockForHeadAndSlot*[
BBB: bellatrix_mev.BlindedBeaconBlock | capella_mev.BlindedBeaconBlock](
node: BeaconNode, randao_reveal: ValidatorSig, node: BeaconNode, randao_reveal: ValidatorSig,
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef, 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 ## 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 ## signed by a validator. A blinded block is a block with only a transactions
## root, rather than a full transactions list. ## 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 let
pubkey = pubkey =
# Relevant state for knowledge of validators # Relevant state for knowledge of validators
@ -826,17 +902,27 @@ proc makeBlindedBeaconBlockForHeadAndSlot*(
forkyState.data.validators.item(validator_index).pubkey forkyState.data.validators.item(validator_index).pubkey
blindedBlockParts = await getBlindedBlockParts( blindedBlockParts = await getBlindedBlockParts[EPH](
node, head, pubkey, slot, randao_reveal, validator_index, graffiti) node, head, pubkey, slot, randao_reveal, validator_index, graffiti)
if blindedBlockParts.isErr: if blindedBlockParts.isErr:
# Don't try EL fallback -- VC specifically requested a blinded block # Don't try EL fallback -- VC specifically requested a blinded block
return err("Unable to create blinded block") return err("Unable to create blinded block")
let (executionPayloadHeader, forkedBlck) = blindedBlockParts.get let (executionPayloadHeader, forkedBlck) = blindedBlockParts.get
return ok constructPlainBlindedBlock[bellatrix_mev.BlindedBeaconBlock]( withBlck(forkedBlck):
forkedBlck, executionPayloadHeader) when stateFork >= ConsensusFork.EIP4844:
debugRaiseAssert $eip4844ImplementationMissing & ": makeBlindedBeaconBlockForHeadAndSlot"
from ../spec/datatypes/eip4844 import shortLog 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, proc proposeBlock(node: BeaconNode,
validator: AttachedValidator, validator: AttachedValidator,
@ -866,8 +952,20 @@ proc proposeBlock(node: BeaconNode,
res.get() res.get()
if node.config.payloadBuilderEnable: if node.config.payloadBuilderEnable:
let newBlockMEV = await node.proposeBlockMEV( let newBlockMEV =
head, validator, slot, randao, validator_index) 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: if newBlockMEV.isSome:
# This might be equivalent to the `head` passed in, but it signals that # 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, genesis_validators_root: Eth2Digest, slot: Slot,
block_root: Eth2Digest, block_root: Eth2Digest,
blck: ForkedBeaconBlock | ForkedBlindedBeaconBlock | blck: ForkedBeaconBlock | ForkedBlindedBeaconBlock |
bellatrix_mev.BlindedBeaconBlock bellatrix_mev.BlindedBeaconBlock |
capella_mev.BlindedBeaconBlock
): Future[SignatureResult] {.async.} = ): Future[SignatureResult] {.async.} =
return return
case v.kind case v.kind
@ -456,13 +457,20 @@ proc getBlockSignature*(v: AttachedValidator, fork: Fork,
request = Web3SignerRequest.init( request = Web3SignerRequest.init(
fork, genesis_validators_root, web3SignerBlock) fork, genesis_validators_root, web3SignerBlock)
await v.signData(request) await v.signData(request)
elif blck is BlindedBeaconBlock: elif blck is bellatrix_mev.BlindedBeaconBlock:
let request = Web3SignerRequest.init( let request = Web3SignerRequest.init(
fork, genesis_validators_root, fork, genesis_validators_root,
Web3SignerForkedBeaconBlock( Web3SignerForkedBeaconBlock(
kind: ConsensusFork.Bellatrix, kind: ConsensusFork.Bellatrix,
bellatrixData: blck.toBeaconBlockHeader)) bellatrixData: blck.toBeaconBlockHeader))
await v.signData(request) 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: else:
let let
web3SignerBlock = web3SignerBlock =