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): (currentEpochFork.toString != version):
return RestApiResponse.jsonError(Http400, BlockIncorrectFork) return RestApiResponse.jsonError(Http400, BlockIncorrectFork)
case currentEpochFork withConsensusFork(currentEpochFork):
of ConsensusFork.Deneb: when consensusFork >= ConsensusFork.Capella:
let let
restBlockContents = decodeBodyJsonOrSsz(deneb_mev.SignedBlindedBeaconBlockContents, restBlock = decodeBodyJsonOrSsz(
body).valueOr: consensusFork.SignedBlindedBeaconBlock, body).valueOr:
return RestApiResponse.jsonError(error) 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( if res.isErr():
restBlockContents.signed_blinded_block.message.proposer_index).valueOr:
return RestApiResponse.jsonError( return RestApiResponse.jsonError(
Http400, "Unable to initialize payload builder client: " & $error) Http500, InternalServerError, $res.error())
res = await node.unblindAndRouteBlockMEV( if res.get().isNone():
payloadBuilderClient, restBlockContents) return RestApiResponse.jsonError(Http202, BlockValidationError)
if res.isErr(): return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
elif consensusFork >= ConsensusFork.Bellatrix:
return RestApiResponse.jsonError( return RestApiResponse.jsonError(
Http500, InternalServerError, $res.error()) Http400, $consensusFork & " builder API unsupported")
if res.get().isNone(): else:
return RestApiResponse.jsonError(Http202, BlockValidationError) # 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) if forked.kind != node.dag.cfg.consensusForkAtEpoch(
of ConsensusFork.Capella: getForkedBlockField(forked, slot).epoch):
let return RestApiResponse.jsonError(Http400, InvalidBlockObjectError)
restBlock =
decodeBodyJsonOrSsz(capella_mev.SignedBlindedBeaconBlock,
body).valueOr:
return RestApiResponse.jsonError(error)
payloadBuilderClient = node.getPayloadBuilderClient( let res = withBlck(forked):
restBlock.message.proposer_index).valueOr: forkyBlck.root = hash_tree_root(forkyBlck.message)
await node.router.routeSignedBeaconBlock(
forkyBlck, Opt.none(seq[BlobSidecar]))
if res.isErr():
return RestApiResponse.jsonError( return RestApiResponse.jsonError(
Http400, "Unable to initialize payload builder client: " & $error) Http503, BeaconNodeInSyncError, $res.error())
res = await node.unblindAndRouteBlockMEV( elif res.get().isNone():
payloadBuilderClient, restBlock) return RestApiResponse.jsonError(Http202, BlockValidationError)
if res.isErr(): return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
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)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlock # https://ethereum.github.io/beacon-APIs/#/Beacon/getBlock
router.api(MethodGet, "/eth/v1/beacon/blocks/{block_id}") do ( router.api(MethodGet, "/eth/v1/beacon/blocks/{block_id}") do (

View File

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

View File

@ -245,7 +245,7 @@ proc publishBlindedBlock*(body: capella_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: deneb_mev.SignedBlindedBeaconBlockContents): proc publishBlindedBlock*(body: deneb_mev.SignedBlindedBeaconBlock):
RestPlainResponse {. RestPlainResponse {.
rest, endpoint: "/eth/v1/beacon/blinded_blocks", rest, endpoint: "/eth/v1/beacon/blinded_blocks",
meth: MethodPost.} meth: MethodPost.}

View File

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

View File

@ -235,30 +235,6 @@ func create_blob_sidecars*(
res.add(sidecar) res.add(sidecar)
res 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 # 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 = template is_sync_committee_update*(update: SomeForkyLightClientUpdate): bool =
when update is SomeForkyLightClientUpdateWithSyncCommittee: when update is SomeForkyLightClientUpdateWithSyncCommittee:

View File

@ -13,16 +13,10 @@ from stew/byteutils import to0xHex
from ".."/datatypes/capella import SignedBLSToExecutionChange from ".."/datatypes/capella import SignedBLSToExecutionChange
type 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 # https://github.com/ethereum/builder-specs/blob/534e4f81276b8346d785ed9aba12c4c74b927ec6/specs/deneb/builder.md#builderbid
BuilderBid* = object BuilderBid* = object
header*: deneb.ExecutionPayloadHeader # [Modified in Deneb] header*: deneb.ExecutionPayloadHeader # [Modified in Deneb]
blinded_blobs_bundle*: BlindedBlobsBundle # [New in Deneb] blob_kzg_commitments*: KzgCommitments # [New in Deneb]
value*: UInt256 value*: UInt256
pubkey*: ValidatorPubKey pubkey*: ValidatorPubKey
@ -67,30 +61,15 @@ type
message*: BlindedBeaconBlock message*: BlindedBeaconBlock
signature*: ValidatorSig 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 # https://github.com/ethereum/builder-specs/blob/534e4f81276b8346d785ed9aba12c4c74b927ec6/specs/deneb/builder.md#executionpayloadandblobsbundle
ExecutionPayloadAndBlobsBundle* = object ExecutionPayloadAndBlobsBundle* = object
execution_payload*: deneb.ExecutionPayload execution_payload*: deneb.ExecutionPayload
blobs_bundle*: BlobsBundle blobs_bundle*: BlobsBundle
# Not spec, but suggested by spec # Not spec, but suggested by spec
ExecutionPayloadHeaderAndBlindedBlobsBundle* = object BlindedExecutionPayloadAndBlobsBundle* = object
execution_payload_header*: deneb.ExecutionPayloadHeader execution_payload_header*: deneb.ExecutionPayloadHeader
blinded_blobs_bundle*: BlindedBlobsBundle blob_kzg_commitments*: KzgCommitments # [New in Deneb]
func shortLog*(v: BlindedBeaconBlock): auto = func shortLog*(v: BlindedBeaconBlock): auto =
( (
@ -118,10 +97,3 @@ func shortLog*(v: SignedBlindedBeaconBlock): auto =
blck: shortLog(v.message), blck: shortLog(v.message),
signature: shortLog(v.signature) 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}.} meth: MethodGet, connection: {Dedicated, Close}.}
## https://github.com/ethereum/builder-specs/blob/34509da74237942aa15a4c0ca828f67acdf77652/apis/builder/header.yaml ## 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] {. ): RestResponse[SubmitBlindedBlockResponseDeneb] {.
rest, endpoint: "/eth/v1/builder/blinded_blocks", rest, endpoint: "/eth/v1/builder/blinded_blocks",
meth: MethodPost, connection: {Dedicated, Close}.} meth: MethodPost, connection: {Dedicated, Close}.}

View File

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

View File

@ -42,19 +42,20 @@ macro copyFields*(
result.add newAssignment( result.add newAssignment(
newDotExpr(dst, ident(name)), newDotExpr(src, ident(name))) 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*( proc unblindAndRouteBlockMEV*(
node: BeaconNode, payloadBuilderRestClient: RestClientRef, node: BeaconNode, payloadBuilderRestClient: RestClientRef,
blindedBlock: capella_mev.SignedBlindedBeaconBlock): blindedBlock:
capella_mev.SignedBlindedBeaconBlock |
deneb_mev.SignedBlindedBeaconBlock):
Future[Result[Opt[BlockRef], string]] {.async.} = Future[Result[Opt[BlockRef], string]] {.async.} =
const consensusFork = typeof(blindedBlock).kind
info "Proposing blinded Builder API block", info "Proposing blinded Builder API block",
blindedBlock = shortLog(blindedBlock) blindedBlock = shortLog(blindedBlock)
# By time submitBlindedBlock is called, must already have done slashing # By time submitBlindedBlock is called, must already have done slashing
# protection check # protection check
let unblindedPayload = let bundle =
try: try:
awaitWithTimeout( awaitWithTimeout(
payloadBuilderRestClient.submitBlindedBlock(blindedBlock), payloadBuilderRestClient.submitBlindedBlock(blindedBlock),
@ -68,132 +69,70 @@ proc unblindAndRouteBlockMEV*(
return err("exception in submitBlindedBlock: " & exc.msg) return err("exception in submitBlindedBlock: " & exc.msg)
const httpOk = 200 const httpOk = 200
if unblindedPayload.status == httpOk: if bundle.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:
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/validator.md#proposer-slashing # 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 # This means if a validator publishes a signature for a
# `BlindedBeaconBlock` (via a dissemination of a # `BlindedBeaconBlock` (via a dissemination of a
# `SignedBlindedBeaconBlock`) then the validator **MUST** not use the # `SignedBlindedBeaconBlock`) then the validator **MUST** not use the
# local build process as a fallback, even in the event of some failure # local build process as a fallback, even in the event of some failure
# with the external builder network. # with the external builder network.
err("submitBlindedBlock failed with HTTP error code" & return err("submitBlindedBlock failed with HTTP error code " &
$unblindedPayload.status & ": " & $shortLog(blindedBlock)) $bundle.status & ": " & $shortLog(blindedBlock))
# TODO currently cannot be combined into one generic function when consensusFork >= ConsensusFork.Deneb:
proc unblindAndRouteBlockMEV*( template execution_payload: untyped = bundle.data.data.execution_payload
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
else: else:
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/validator.md#proposer-slashing template execution_payload: untyped = bundle.data.data
# This means if a validator publishes a signature for a if hash_tree_root(blindedBlock.message.body.execution_payload_header) !=
# `BlindedBeaconBlock` (via a dissemination of a hash_tree_root(execution_payload):
# `SignedBlindedBeaconBlock`) then the validator **MUST** not use the return err("unblinded payload doesn't match blinded payload header: " &
# local build process as a fallback, even in the event of some failure $blindedBlock.message.body.execution_payload_header)
# with the external builder network.
err("submitBlindedBlock failed with HTTP error code" & # Signature provided is consistent with unblinded execution payload,
$unblindedPayload.status & ": " & $shortLog(blindedBlock)) # 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