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:
parent
68cb9fe7b1
commit
79eddcde40
|
@ -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())
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 =
|
||||
|
|
Loading…
Reference in New Issue