update Deneb for latest builder-specs flow (#5598)

The `BlobSidecar` construction has been moved to the relay and is no
longer done by the BN / VC in blinded flow. Builder bid contents have
been shrinked from full `BlindedBlobBundle` to `blob_kzg_commitments`.

- https://github.com/ethereum/builder-specs/pull/90
- https://github.com/ethereum/beacon-APIs/pull/369
This commit is contained in:
Etan Kissling 2023-11-15 16:20:13 -08:00 committed by GitHub
parent ec6780ed6f
commit 98e969084d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 169 additions and 353 deletions

View File

@ -1006,78 +1006,56 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
(currentEpochFork.toString != version):
return RestApiResponse.jsonError(Http400, BlockIncorrectFork)
case currentEpochFork
of ConsensusFork.Deneb:
let
restBlockContents = decodeBodyJsonOrSsz(deneb_mev.SignedBlindedBeaconBlockContents,
body).valueOr:
return RestApiResponse.jsonError(error)
withConsensusFork(currentEpochFork):
when consensusFork >= ConsensusFork.Capella:
let
restBlock = decodeBodyJsonOrSsz(
consensusFork.SignedBlindedBeaconBlock, body).valueOr:
return RestApiResponse.jsonError(error)
payloadBuilderClient = node.getPayloadBuilderClient(
restBlock.message.proposer_index).valueOr:
return RestApiResponse.jsonError(
Http400, "Unable to initialize payload builder client: " & $error)
res = await node.unblindAndRouteBlockMEV(
payloadBuilderClient, restBlock)
payloadBuilderClient = node.getPayloadBuilderClient(
restBlockContents.signed_blinded_block.message.proposer_index).valueOr:
if res.isErr():
return RestApiResponse.jsonError(
Http400, "Unable to initialize payload builder client: " & $error)
res = await node.unblindAndRouteBlockMEV(
payloadBuilderClient, restBlockContents)
Http500, InternalServerError, $res.error())
if res.get().isNone():
return RestApiResponse.jsonError(Http202, BlockValidationError)
if res.isErr():
return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
elif consensusFork >= ConsensusFork.Bellatrix:
return RestApiResponse.jsonError(
Http500, InternalServerError, $res.error())
if res.get().isNone():
return RestApiResponse.jsonError(Http202, BlockValidationError)
Http400, $consensusFork & " builder API unsupported")
else:
# Pre-Bellatrix, this endpoint will accept a `SignedBeaconBlock`.
#
# This is mostly the same as /eth/v1/beacon/blocks for phase 0 and
# altair.
var
restBlock = decodeBody(
RestPublishedSignedBeaconBlock, body, version).valueOr:
return RestApiResponse.jsonError(error)
forked = ForkedSignedBeaconBlock(restBlock)
return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
of ConsensusFork.Capella:
let
restBlock =
decodeBodyJsonOrSsz(capella_mev.SignedBlindedBeaconBlock,
body).valueOr:
return RestApiResponse.jsonError(error)
if forked.kind != node.dag.cfg.consensusForkAtEpoch(
getForkedBlockField(forked, slot).epoch):
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError)
payloadBuilderClient = node.getPayloadBuilderClient(
restBlock.message.proposer_index).valueOr:
let res = withBlck(forked):
forkyBlck.root = hash_tree_root(forkyBlck.message)
await node.router.routeSignedBeaconBlock(
forkyBlck, Opt.none(seq[BlobSidecar]))
if res.isErr():
return RestApiResponse.jsonError(
Http400, "Unable to initialize payload builder client: " & $error)
res = await node.unblindAndRouteBlockMEV(
payloadBuilderClient, restBlock)
Http503, BeaconNodeInSyncError, $res.error())
elif res.get().isNone():
return RestApiResponse.jsonError(Http202, BlockValidationError)
if res.isErr():
return RestApiResponse.jsonError(
Http500, InternalServerError, $res.error())
if res.get().isNone():
return RestApiResponse.jsonError(Http202, BlockValidationError)
return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
of ConsensusFork.Bellatrix:
return RestApiResponse.jsonError(Http400,
"Bellatrix builder API unsupported")
of ConsensusFork.Altair, ConsensusFork.Phase0:
# Pre-Bellatrix, this endpoint will accept a `SignedBeaconBlock`.
#
# This is mostly the same as /eth/v1/beacon/blocks for phase 0 and
# altair.
var
restBlock = decodeBody(RestPublishedSignedBeaconBlock, body,
version).valueOr:
return RestApiResponse.jsonError(error)
forked = ForkedSignedBeaconBlock(restBlock)
if forked.kind != node.dag.cfg.consensusForkAtEpoch(
getForkedBlockField(forked, slot).epoch):
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError)
let res = withBlck(forked):
forkyBlck.root = hash_tree_root(forkyBlck.message)
await node.router.routeSignedBeaconBlock(
forkyBlck, Opt.none(seq[BlobSidecar]))
if res.isErr():
return RestApiResponse.jsonError(
Http503, BeaconNodeInSyncError, $res.error())
elif res.get().isNone():
return RestApiResponse.jsonError(Http202, BlockValidationError)
return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlock
router.api(MethodGet, "/eth/v1/beacon/blocks/{block_id}") do (

View File

@ -99,7 +99,7 @@ type
SetGasLimitRequest |
bellatrix_mev.SignedBlindedBeaconBlock |
capella_mev.SignedBlindedBeaconBlock |
deneb_mev.SignedBlindedBeaconBlockContents |
deneb_mev.SignedBlindedBeaconBlock |
SignedValidatorRegistrationV1 |
SignedVoluntaryExit |
Web3SignerRequest |

View File

@ -245,7 +245,7 @@ proc publishBlindedBlock*(body: capella_mev.SignedBlindedBeaconBlock):
meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock
proc publishBlindedBlock*(body: deneb_mev.SignedBlindedBeaconBlockContents):
proc publishBlindedBlock*(body: deneb_mev.SignedBlindedBeaconBlock):
RestPlainResponse {.
rest, endpoint: "/eth/v1/beacon/blinded_blocks",
meth: MethodPost.}

View File

@ -317,7 +317,8 @@ template kind*(
capella.TrustedBeaconBlockBody |
capella.SigVerifiedSignedBeaconBlock |
capella.MsgTrustedSignedBeaconBlock |
capella.TrustedSignedBeaconBlock]): ConsensusFork =
capella.TrustedSignedBeaconBlock |
capella_mev.SignedBlindedBeaconBlock]): ConsensusFork =
ConsensusFork.Capella
template kind*(
@ -335,7 +336,8 @@ template kind*(
deneb.TrustedBeaconBlockBody |
deneb.SigVerifiedSignedBeaconBlock |
deneb.MsgTrustedSignedBeaconBlock |
deneb.TrustedSignedBeaconBlock]): ConsensusFork =
deneb.TrustedSignedBeaconBlock |
deneb_mev.SignedBlindedBeaconBlock]): ConsensusFork =
ConsensusFork.Deneb
template BeaconState*(kind: static ConsensusFork): auto =
@ -418,6 +420,16 @@ template ExecutionPayloadForSigning*(kind: static ConsensusFork): auto =
else:
static: raiseAssert "Unreachable"
template SignedBlindedBeaconBlock*(kind: static ConsensusFork): auto =
when kind == ConsensusFork.Deneb:
typedesc[deneb_mev.SignedBlindedBeaconBlock]
elif kind == ConsensusFork.Capella:
typedesc[capella_mev.SignedBlindedBeaconBlock]
elif kind == ConsensusFork.Bellatrix:
static: raiseAssert "Unsupported"
else:
static: raiseAssert "Unreachable"
template withAll*(
x: typedesc[ConsensusFork], body: untyped): untyped =
static: doAssert ConsensusFork.high == ConsensusFork.Deneb

View File

@ -235,30 +235,6 @@ func create_blob_sidecars*(
res.add(sidecar)
res
func create_blob_sidecars*(
forkyBlck: deneb_mev.SignedBlindedBeaconBlock,
kzg_proofs: KzgProofs,
blob_roots: BlobRoots): seq[BlindedBlobSidecar] =
template kzg_commitments: untyped =
forkyBlck.message.body.blob_kzg_commitments
doAssert kzg_proofs.len == blob_roots.len
doAssert kzg_proofs.len == kzg_commitments.len
var res = newSeqOfCap[BlindedBlobSidecar](blob_roots.len)
let signedBlockHeader = forkyBlck.toSignedBeaconBlockHeader()
for i in 0 ..< blob_roots.lenu64:
var sidecar = BlindedBlobSidecar(
index: i,
blob_root: blob_roots[i],
kzg_commitment: kzg_commitments[i],
kzg_proof: kzg_proofs[i],
signed_block_header: signedBlockHeader)
forkyBlck.message.body.build_proof(
kzg_commitment_inclusion_proof_gindex(i),
sidecar.kzg_commitment_inclusion_proof).expect("Valid gindex")
res.add(sidecar)
res
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.4/specs/altair/light-client/sync-protocol.md#is_sync_committee_update
template is_sync_committee_update*(update: SomeForkyLightClientUpdate): bool =
when update is SomeForkyLightClientUpdateWithSyncCommittee:

View File

@ -13,16 +13,10 @@ from stew/byteutils import to0xHex
from ".."/datatypes/capella import SignedBLSToExecutionChange
type
# https://github.com/ethereum/builder-specs/blob/534e4f81276b8346d785ed9aba12c4c74b927ec6/specs/deneb/builder.md#blindedblobsbundle
BlindedBlobsBundle* = object
commitments*: List[KZGCommitment, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK]
proofs*: List[KZGProof, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK]
blob_roots*: List[Eth2Digest, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK]
# https://github.com/ethereum/builder-specs/blob/534e4f81276b8346d785ed9aba12c4c74b927ec6/specs/deneb/builder.md#builderbid
BuilderBid* = object
header*: deneb.ExecutionPayloadHeader # [Modified in Deneb]
blinded_blobs_bundle*: BlindedBlobsBundle # [New in Deneb]
blob_kzg_commitments*: KzgCommitments # [New in Deneb]
value*: UInt256
pubkey*: ValidatorPubKey
@ -67,30 +61,15 @@ type
message*: BlindedBeaconBlock
signature*: ValidatorSig
# https://github.com/ethereum/builder-specs/blob/534e4f81276b8346d785ed9aba12c4c74b927ec6/specs/deneb/builder.md#blindedblobsidecar
BlindedBlobSidecar* = object
index*: uint64
blob_root*: Eth2Digest
kzg_commitment*: KZGCommitment
kzg_proof*: KZGProof
signed_block_header*: SignedBeaconBlockHeader
kzg_commitment_inclusion_proof*:
array[KZG_COMMITMENT_INCLUSION_PROOF_DEPTH, Eth2Digest]
# https://github.com/ethereum/builder-specs/blob/534e4f81276b8346d785ed9aba12c4c74b927ec6/specs/deneb/builder.md#signedblindedblockcontents
SignedBlindedBeaconBlockContents* = object
signed_blinded_block*: deneb_mev.SignedBlindedBeaconBlock
blinded_blob_sidecars*: List[BlindedBlobSidecar, Limit MAX_BLOBS_PER_BLOCK]
# https://github.com/ethereum/builder-specs/blob/534e4f81276b8346d785ed9aba12c4c74b927ec6/specs/deneb/builder.md#executionpayloadandblobsbundle
ExecutionPayloadAndBlobsBundle* = object
execution_payload*: deneb.ExecutionPayload
blobs_bundle*: BlobsBundle
# Not spec, but suggested by spec
ExecutionPayloadHeaderAndBlindedBlobsBundle* = object
BlindedExecutionPayloadAndBlobsBundle* = object
execution_payload_header*: deneb.ExecutionPayloadHeader
blinded_blobs_bundle*: BlindedBlobsBundle
blob_kzg_commitments*: KzgCommitments # [New in Deneb]
func shortLog*(v: BlindedBeaconBlock): auto =
(
@ -118,10 +97,3 @@ func shortLog*(v: SignedBlindedBeaconBlock): auto =
blck: shortLog(v.message),
signature: shortLog(v.signature)
)
# needs to match SignedBlindedBeaconBlock
func shortLog*(v: SignedBlindedBeaconBlockContents): auto =
(
blck: shortLog(v.signed_blinded_block.message),
signature: shortLog(v.signed_blinded_block.signature)
)

View File

@ -20,7 +20,7 @@ proc getHeaderDeneb*(slot: Slot,
meth: MethodGet, connection: {Dedicated, Close}.}
## https://github.com/ethereum/builder-specs/blob/34509da74237942aa15a4c0ca828f67acdf77652/apis/builder/header.yaml
proc submitBlindedBlock*(body: deneb_mev.SignedBlindedBeaconBlockContents
proc submitBlindedBlock*(body: deneb_mev.SignedBlindedBeaconBlock
): RestResponse[SubmitBlindedBlockResponseDeneb] {.
rest, endpoint: "/eth/v1/builder/blinded_blocks",
meth: MethodPost, connection: {Dedicated, Close}.}

View File

@ -561,7 +561,7 @@ proc makeBeaconBlockForHeadAndSlot*(
proc getBlindedExecutionPayload[
EPH: capella.ExecutionPayloadHeader |
deneb_mev.ExecutionPayloadHeaderAndBlindedBlobsBundle](
deneb_mev.BlindedExecutionPayloadAndBlobsBundle](
node: BeaconNode, payloadBuilderClient: RestClientRef, slot: Slot,
executionBlockRoot: Eth2Digest, pubkey: ValidatorPubKey):
Future[BlindedBlockResult[EPH]] {.async.} =
@ -572,7 +572,7 @@ proc getBlindedExecutionPayload[
payloadBuilderClient.getHeaderCapella(slot, executionBlockRoot, pubkey),
BUILDER_PROPOSAL_DELAY_TOLERANCE):
return err "Timeout obtaining Capella blinded header from builder"
elif EPH is deneb_mev.ExecutionPayloadHeaderAndBlindedBlobsBundle:
elif EPH is deneb_mev.BlindedExecutionPayloadAndBlobsBundle:
let blindedHeader = awaitWithTimeout(
payloadBuilderClient.getHeaderDeneb(slot, executionBlockRoot, pubkey),
BUILDER_PROPOSAL_DELAY_TOLERANCE):
@ -594,19 +594,13 @@ proc getBlindedExecutionPayload[
return ok((
blindedBlckPart: blindedHeader.data.data.message.header,
blockValue: blindedHeader.data.data.message.value))
elif EPH is deneb_mev.ExecutionPayloadHeaderAndBlindedBlobsBundle:
template bbb: untyped = blindedHeader.data.data.message.blinded_blobs_bundle
if bbb.proofs.len != bbb.blob_roots.len or
bbb.proofs.len != bbb.commitments.len:
return err("getBlindedExecutionPayload: mismatched blob_roots, commitments, and proofs lengths: " &
$bbb.blob_roots.len & ", " & $bbb.commitments.len & ", and" & $bbb.proofs.len)
elif EPH is deneb_mev.BlindedExecutionPayloadAndBlobsBundle:
template builderBid: untyped = blindedHeader.data.data.message
return ok((
blindedBlckPart: ExecutionPayloadHeaderAndBlindedBlobsBundle(
execution_payload_header: blindedHeader.data.data.message.header,
blinded_blobs_bundle: bbb),
blockValue: blindedHeader.data.data.message.value))
blindedBlckPart: EPH(
execution_payload_header: builderBid.header,
blob_kzg_commitments: builderBid.blob_kzg_commitments),
blockValue: builderBid.value))
else:
static: doAssert false
@ -631,43 +625,27 @@ func constructSignableBlindedBlock[T: capella_mev.SignedBlindedBeaconBlock](
blindedBlock
proc constructSignableBlindedBlock[T: deneb_mev.SignedBlindedBeaconBlockContents](
proc constructSignableBlindedBlock[T: deneb_mev.SignedBlindedBeaconBlock](
blck: deneb.BeaconBlock,
executionPayloadHeaderContents:
deneb_mev.ExecutionPayloadHeaderAndBlindedBlobsBundle):
T =
blindedBundle: deneb_mev.BlindedExecutionPayloadAndBlobsBundle): T =
# Leaves signature field default, to be filled in by caller
const
blckFields = getFieldNames(typeof(blck))
blckBodyFields = getFieldNames(typeof(blck.body))
var blindedBlockContents: T
template blindedBlock: untyped = blindedBlockContents.signed_blinded_block
var blindedBlock: T
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/validator.md#block-proposal
copyFields(blindedBlock.message, blck, blckFields)
copyFields(blindedBlock.message.body, blck.body, blckBodyFields)
assign(
blindedBlock.message.body.execution_payload_header,
executionPayloadHeaderContents.execution_payload_header)
blindedBundle.execution_payload_header)
assign(
blindedBlock.message.body.blob_kzg_commitments,
blindedBundle.blob_kzg_commitments)
template bbb: untyped = executionPayloadHeaderContents.blinded_blobs_bundle
# Checked in getBlindedExecutionPayload, so it's a logic error here
doAssert bbb.proofs.len == bbb.blob_roots.len
doAssert bbb.proofs.len == bbb.commitments.len
assign(blindedBlock.message.body.blob_kzg_commitments, bbb.commitments)
let sidecars = blindedBlock.create_blob_sidecars(bbb.proofs, bbb.blob_roots)
if blindedBlockContents.blinded_blob_sidecars.setLen(bbb.proofs.len):
for i in 0 ..< sidecars.len:
assign(blindedBlockContents.blinded_blob_sidecars[i], sidecars[i])
else:
debug "constructSignableBlindedBlock: unable to set blinded blob sidecar length",
blobs_len = bbb.proofs.len
blindedBlockContents
blindedBlock
func constructPlainBlindedBlock[
T: capella_mev.BlindedBeaconBlock, EPH: capella.ExecutionPayloadHeader](
@ -685,7 +663,10 @@ func constructPlainBlindedBlock[
blindedBlock
proc blindedBlockCheckSlashingAndSign[T: capella_mev.SignedBlindedBeaconBlock](
proc blindedBlockCheckSlashingAndSign[
T:
capella_mev.SignedBlindedBeaconBlock |
deneb_mev.SignedBlindedBeaconBlock](
node: BeaconNode, slot: Slot, validator: AttachedValidator,
validator_index: ValidatorIndex, nonsignedBlindedBlock: T):
Future[Result[T, string]] {.async.} =
@ -700,45 +681,6 @@ proc blindedBlockCheckSlashingAndSign[T: capella_mev.SignedBlindedBeaconBlock](
.slashingProtection
.registerBlock(validator_index, validator.pubkey, slot, signingRoot)
if notSlashable.isErr:
warn "Slashing protection activated for MEV block",
blockRoot = shortLog(blockRoot), blck = shortLog(nonsignedBlindedBlock),
signingRoot = shortLog(signingRoot),
validator = validator.pubkey,
slot = slot,
existingProposal = notSlashable.error
return err("MEV proposal would be slashable: " & $notSlashable.error)
var blindedBlock = nonsignedBlindedBlock
blindedBlock.signature =
block:
let res = await validator.getBlockSignature(
fork, genesis_validators_root, slot, blockRoot, blindedBlock.message)
if res.isErr():
return err("Unable to sign block: " & res.error())
res.get()
return ok blindedBlock
proc blindedBlockCheckSlashingAndSign[
T: deneb_mev.SignedBlindedBeaconBlockContents](
node: BeaconNode, slot: Slot, validator: AttachedValidator,
validator_index: ValidatorIndex, nonsignedBlindedBlockContents: T):
Future[Result[T, string]] {.async.} =
template nonsignedBlindedBlock: untyped =
nonsignedBlindedBlockContents.signed_blinded_block
# Check with slashing protection before submitBlindedBlock
let
fork = node.dag.forkAtEpoch(slot.epoch)
genesis_validators_root = node.dag.genesis_validators_root
blockRoot = hash_tree_root(nonsignedBlindedBlock.message)
signingRoot = compute_block_signing_root(
fork, genesis_validators_root, slot, blockRoot)
notSlashable = node.attachedValidators
.slashingProtection
.registerBlock(validator_index, validator.pubkey, slot, signingRoot)
if notSlashable.isErr:
warn "Slashing protection activated for MEV block",
blockRoot = shortLog(blockRoot), blck = shortLog(nonsignedBlindedBlock),
@ -746,29 +688,28 @@ proc blindedBlockCheckSlashingAndSign[
slot = slot, existingProposal = notSlashable.error
return err("MEV proposal would be slashable: " & $notSlashable.error)
var blindedBlockContents = nonsignedBlindedBlockContents
blindedBlockContents.signed_blinded_block.signature = block:
var blindedBlock = nonsignedBlindedBlock
blindedBlock.signature = block:
let res = await validator.getBlockSignature(
fork, genesis_validators_root, slot, blockRoot,
blindedBlockContents.signed_blinded_block.message)
fork, genesis_validators_root, slot, blockRoot, blindedBlock.message)
if res.isErr():
return err("Unable to sign blinded block: " & res.error())
return err("Unable to sign block: " & res.error())
res.get()
return ok blindedBlockContents
return ok blindedBlock
proc getUnsignedBlindedBeaconBlock[
T: capella_mev.SignedBlindedBeaconBlock |
deneb_mev.SignedBlindedBeaconBlockContents](
deneb_mev.SignedBlindedBeaconBlock](
node: BeaconNode, slot: Slot, validator: AttachedValidator,
validator_index: ValidatorIndex, forkedBlock: ForkedBeaconBlock,
executionPayloadHeader: capella.ExecutionPayloadHeader |
deneb_mev.ExecutionPayloadHeaderAndBlindedBlobsBundle):
deneb_mev.BlindedExecutionPayloadAndBlobsBundle):
Result[T, string] =
withBlck(forkedBlock):
when consensusFork >= ConsensusFork.Capella:
when not (
(T is deneb_mev.SignedBlindedBeaconBlockContents and
(T is deneb_mev.SignedBlindedBeaconBlock and
consensusFork == ConsensusFork.Deneb) or
(T is capella_mev.SignedBlindedBeaconBlock and
consensusFork == ConsensusFork.Capella)):
@ -781,7 +722,7 @@ proc getUnsignedBlindedBeaconBlock[
proc getBlindedBlockParts[
EPH: capella.ExecutionPayloadHeader |
deneb_mev.ExecutionPayloadHeaderAndBlindedBlobsBundle](
deneb_mev.BlindedExecutionPayloadAndBlobsBundle](
node: BeaconNode, payloadBuilderClient: RestClientRef, head: BlockRef,
pubkey: ValidatorPubKey, slot: Slot, randao: ValidatorSig,
validator_index: ValidatorIndex, graffiti: GraffitiBytes):
@ -829,18 +770,18 @@ proc getBlindedBlockParts[
copyFields(
shimExecutionPayload.executionPayload,
executionPayloadHeader.get.blindedBlckPart, getFieldNames(EPH))
elif EPH is deneb_mev.ExecutionPayloadHeaderAndBlindedBlobsBundle:
elif EPH is deneb_mev.BlindedExecutionPayloadAndBlobsBundle:
type PayloadType = deneb.ExecutionPayloadForSigning
template actualEPH: untyped =
executionPayloadHeader.get.blindedBlckPart.execution_payload_header
let
withdrawals_root = Opt.some actualEPH.withdrawals_root
kzg_commitments = Opt.some(
executionPayloadHeader.get.blindedBlckPart.blinded_blobs_bundle.commitments)
executionPayloadHeader.get.blindedBlckPart.blob_kzg_commitments)
var shimExecutionPayload: PayloadType
type DenebEPH =
deneb_mev.ExecutionPayloadHeaderAndBlindedBlobsBundle.execution_payload_header
deneb_mev.BlindedExecutionPayloadAndBlobsBundle.execution_payload_header
copyFields(
shimExecutionPayload.executionPayload, actualEPH, getFieldNames(DenebEPH))
else:
@ -867,7 +808,7 @@ proc getBlindedBlockParts[
proc getBuilderBid[
SBBB: capella_mev.SignedBlindedBeaconBlock |
deneb_mev.SignedBlindedBeaconBlockContents](
deneb_mev.SignedBlindedBeaconBlock](
node: BeaconNode, payloadBuilderClient: RestClientRef, head: BlockRef,
validator: AttachedValidator, slot: Slot, randao: ValidatorSig,
validator_index: ValidatorIndex):
@ -876,8 +817,8 @@ proc getBuilderBid[
## Used by the BN's own validators, but not the REST server
when SBBB is capella_mev.SignedBlindedBeaconBlock:
type EPH = capella.ExecutionPayloadHeader
elif SBBB is deneb_mev.SignedBlindedBeaconBlockContents:
type EPH = deneb_mev.ExecutionPayloadHeaderAndBlindedBlobsBundle
elif SBBB is deneb_mev.SignedBlindedBeaconBlock:
type EPH = deneb_mev.BlindedExecutionPayloadAndBlobsBundle
else:
static: doAssert false
@ -905,7 +846,7 @@ proc getBuilderBid[
proc proposeBlockMEV(
node: BeaconNode, payloadBuilderClient: RestClientRef,
blindedBlock: capella_mev.SignedBlindedBeaconBlock |
deneb_mev.SignedBlindedBeaconBlockContents):
deneb_mev.SignedBlindedBeaconBlock):
Future[Result[BlockRef, string]] {.async.} =
let unblindedBlockRef = await node.unblindAndRouteBlockMEV(
payloadBuilderClient, blindedBlock)
@ -1225,19 +1166,17 @@ proc proposeBlock(node: BeaconNode,
type1, type2, node, validator, validator_index, head, slot, randao, fork,
genesis_validators_root, node.config.localBlockValueBoost)
return
if slot.epoch >= node.dag.cfg.DENEB_FORK_EPOCH:
return withConsensusFork(node.dag.cfg.consensusForkAtEpoch(slot.epoch)):
when consensusFork >= ConsensusFork.Capella:
proposeBlockContinuation(
deneb_mev.SignedBlindedBeaconBlockContents,
deneb.ExecutionPayloadForSigning)
elif slot.epoch >= node.dag.cfg.CAPELLA_FORK_EPOCH:
proposeBlockContinuation(
capella_mev.SignedBlindedBeaconBlock, capella.ExecutionPayloadForSigning)
consensusFork.SignedBlindedBeaconBlock,
consensusFork.ExecutionPayloadForSigning)
else:
# Bellatrix MEV is not supported; this signals that, because it triggers
# intentional SignedBlindedBeaconBlock/ExecutionPayload mismatches.
proposeBlockContinuation(
capella_mev.SignedBlindedBeaconBlock, bellatrix.ExecutionPayloadForSigning)
capella_mev.SignedBlindedBeaconBlock,
bellatrix.ExecutionPayloadForSigning)
proc handleAttestations(node: BeaconNode, head: BlockRef, slot: Slot) =
## Perform all attestations that the validators attached to this node should

View File

@ -42,19 +42,20 @@ macro copyFields*(
result.add newAssignment(
newDotExpr(dst, ident(name)), newDotExpr(src, ident(name)))
# TODO when https://github.com/nim-lang/Nim/issues/21346 and/or
# https://github.com/nim-lang/Nim/issues/21347 fixed, combine and make generic
# these two very similar versions of unblindAndRouteBlockMEV
proc unblindAndRouteBlockMEV*(
node: BeaconNode, payloadBuilderRestClient: RestClientRef,
blindedBlock: capella_mev.SignedBlindedBeaconBlock):
blindedBlock:
capella_mev.SignedBlindedBeaconBlock |
deneb_mev.SignedBlindedBeaconBlock):
Future[Result[Opt[BlockRef], string]] {.async.} =
const consensusFork = typeof(blindedBlock).kind
info "Proposing blinded Builder API block",
blindedBlock = shortLog(blindedBlock)
# By time submitBlindedBlock is called, must already have done slashing
# protection check
let unblindedPayload =
let bundle =
try:
awaitWithTimeout(
payloadBuilderRestClient.submitBlindedBlock(blindedBlock),
@ -68,132 +69,70 @@ proc unblindAndRouteBlockMEV*(
return err("exception in submitBlindedBlock: " & exc.msg)
const httpOk = 200
if unblindedPayload.status == httpOk:
if hash_tree_root(
blindedBlock.message.body.execution_payload_header) !=
hash_tree_root(unblindedPayload.data.data):
err("unblinded payload doesn't match blinded payload header: " &
$blindedBlock.message.body.execution_payload_header)
else:
# Signature provided is consistent with unblinded execution payload,
# so construct full beacon block
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/validator.md#block-proposal
var signedBlock = capella.SignedBeaconBlock(
signature: blindedBlock.signature)
copyFields(
signedBlock.message, blindedBlock.message,
getFieldNames(typeof(signedBlock.message)))
copyFields(
signedBlock.message.body, blindedBlock.message.body,
getFieldNames(typeof(signedBlock.message.body)))
signedBlock.message.body.execution_payload = unblindedPayload.data.data
signedBlock.root = hash_tree_root(signedBlock.message)
doAssert signedBlock.root == hash_tree_root(blindedBlock.message)
debug "unblindAndRouteBlockMEV: proposing unblinded block",
blck = shortLog(signedBlock)
let newBlockRef =
(await node.router.routeSignedBeaconBlock(
signedBlock, Opt.none(seq[BlobSidecar]))).valueOr:
# submitBlindedBlock has run, so don't allow fallback to run
return err("routeSignedBeaconBlock error") # Errors logged in router
if newBlockRef.isSome:
beacon_block_builder_proposed.inc()
notice "Block proposed (MEV)",
blockRoot = shortLog(signedBlock.root), blck = shortLog(signedBlock),
signature = shortLog(signedBlock.signature)
ok newBlockRef
else:
if bundle.status != httpOk:
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/validator.md#proposer-slashing
# This means if a validator publishes a signature for a
# `BlindedBeaconBlock` (via a dissemination of a
# `SignedBlindedBeaconBlock`) then the validator **MUST** not use the
# local build process as a fallback, even in the event of some failure
# with the external builder network.
err("submitBlindedBlock failed with HTTP error code" &
$unblindedPayload.status & ": " & $shortLog(blindedBlock))
return err("submitBlindedBlock failed with HTTP error code " &
$bundle.status & ": " & $shortLog(blindedBlock))
# TODO currently cannot be combined into one generic function
proc unblindAndRouteBlockMEV*(
node: BeaconNode, payloadBuilderRestClient: RestClientRef,
blindedBlockContents: deneb_mev.SignedBlindedBeaconBlockContents):
Future[Result[Opt[BlockRef], string]] {.async.} =
template blindedBlock: untyped = blindedBlockContents.signed_blinded_block
info "Proposing blinded Builder API block and blobs",
blindedBlock = shortLog(blindedBlock)
# By time submitBlindedBlock is called, must already have done slashing
# protection check
let unblindedPayload =
try:
awaitWithTimeout(
payloadBuilderRestClient.submitBlindedBlock(blindedBlockContents),
BUILDER_BLOCK_SUBMISSION_DELAY_TOLERANCE):
return err("Submitting blinded block and blobs timed out")
# From here on, including error paths, disallow local EL production by
# returning Opt.some, regardless of whether on head or newBlock.
except RestDecodingError as exc:
return err("REST decoding error submitting blinded block and blobs: " & exc.msg)
except CatchableError as exc:
return err("exception in submitBlindedBlock: " & exc.msg)
const httpOk = 200
if unblindedPayload.status == httpOk:
if hash_tree_root(
blindedBlock.message.body.execution_payload_header) !=
hash_tree_root(unblindedPayload.data.data.execution_payload):
err("unblinded payload doesn't match blinded payload header: " &
$blindedBlock.message.body.execution_payload_header)
else:
# Signature provided is consistent with unblinded execution payload,
# so construct full beacon block
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/validator.md#block-proposal
var signedBlock = deneb.SignedBeaconBlock(
signature: blindedBlock.signature)
copyFields(
signedBlock.message, blindedBlock.message,
getFieldNames(typeof(signedBlock.message)))
copyFields(
signedBlock.message.body, blindedBlock.message.body,
getFieldNames(typeof(signedBlock.message.body)))
assign(
signedBlock.message.body.execution_payload,
unblindedPayload.data.data.execution_payload)
signedBlock.root = hash_tree_root(signedBlock.message)
doAssert signedBlock.root == hash_tree_root(blindedBlock.message)
debug "unblindAndRouteBlockMEV: proposing unblinded block and blobs",
blck = shortLog(signedBlock)
let newBlockRef =
(await node.router.routeSignedBeaconBlock(
signedBlock, Opt.none(seq[BlobSidecar]))).valueOr:
# submitBlindedBlock has run, so don't allow fallback to run
return err("routeSignedBeaconBlock error") # Errors logged in router
if newBlockRef.isSome:
beacon_block_builder_proposed.inc()
notice "Block proposed (MEV)",
blockRoot = shortLog(signedBlock.root), blck = shortLog(signedBlock),
signature = shortLog(signedBlock.signature)
discard $denebImplementationMissing & ": route unblinded blobs"
ok newBlockRef
when consensusFork >= ConsensusFork.Deneb:
template execution_payload: untyped = bundle.data.data.execution_payload
else:
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/validator.md#proposer-slashing
# This means if a validator publishes a signature for a
# `BlindedBeaconBlock` (via a dissemination of a
# `SignedBlindedBeaconBlock`) then the validator **MUST** not use the
# local build process as a fallback, even in the event of some failure
# with the external builder network.
err("submitBlindedBlock failed with HTTP error code" &
$unblindedPayload.status & ": " & $shortLog(blindedBlock))
template execution_payload: untyped = bundle.data.data
if hash_tree_root(blindedBlock.message.body.execution_payload_header) !=
hash_tree_root(execution_payload):
return err("unblinded payload doesn't match blinded payload header: " &
$blindedBlock.message.body.execution_payload_header)
# Signature provided is consistent with unblinded execution payload,
# so construct full beacon block
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/validator.md#block-proposal
var signedBlock = consensusFork.SignedBeaconBlock(
signature: blindedBlock.signature)
copyFields(
signedBlock.message, blindedBlock.message,
getFieldNames(typeof(signedBlock.message)))
copyFields(
signedBlock.message.body, blindedBlock.message.body,
getFieldNames(typeof(signedBlock.message.body)))
assign(signedBlock.message.body.execution_payload, execution_payload)
signedBlock.root = hash_tree_root(signedBlock.message)
doAssert signedBlock.root == hash_tree_root(blindedBlock.message)
let blobsOpt =
when consensusFork >= ConsensusFork.Deneb:
template blobs_bundle: untyped = bundle.data.data.blobs_bundle
if blindedBlock.message.body.blob_kzg_commitments !=
bundle.data.data.blobs_bundle.commitments:
return err("unblinded blobs bundle has unexpected commitments")
let ok = verifyProofs(
asSeq blobs_bundle.blobs,
asSeq blobs_bundle.commitments,
asSeq blobs_bundle.proofs).valueOr:
return err("unblinded blobs bundle fails verification")
if not ok:
return err("unblinded blobs bundle is invalid")
Opt.some(signedBlock.create_blob_sidecars(
blobs_bundle.proofs, blobs_bundle.blobs))
else:
Opt.none(seq[BlobSidecar])
debug "unblindAndRouteBlockMEV: proposing unblinded block",
blck = shortLog(signedBlock)
let newBlockRef =
(await node.router.routeSignedBeaconBlock(signedBlock, blobsOpt)).valueOr:
# submitBlindedBlock has run, so don't allow fallback to run
return err("routeSignedBeaconBlock error") # Errors logged in router
if newBlockRef.isSome:
beacon_block_builder_proposed.inc()
notice "Block proposed (MEV)",
blockRoot = shortLog(signedBlock.root), blck = shortLog(signedBlock),
signature = shortLog(signedBlock.signature)
ok newBlockRef