From e9ebb4610310b20d4dc68b3852579daddb07a7c0 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 13 Nov 2023 11:36:19 -0800 Subject: [PATCH] update Deneb for latest builder-specs flow 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 --- beacon_chain/rpc/rest_beacon_api.nim | 106 ++++------ .../eth2_apis/eth2_rest_serialization.nim | 2 +- .../spec/eth2_apis/rest_beacon_calls.nim | 2 +- beacon_chain/spec/forks.nim | 16 +- beacon_chain/spec/helpers.nim | 24 --- beacon_chain/spec/mev/deneb_mev.nim | 34 +--- .../spec/mev/rest_deneb_mev_calls.nim | 2 +- beacon_chain/validators/beacon_validators.nim | 145 ++++---------- .../validators/message_router_mev.nim | 187 ++++++------------ 9 files changed, 167 insertions(+), 351 deletions(-) diff --git a/beacon_chain/rpc/rest_beacon_api.nim b/beacon_chain/rpc/rest_beacon_api.nim index 944002f511..bbed3dea6e 100644 --- a/beacon_chain/rpc/rest_beacon_api.nim +++ b/beacon_chain/rpc/rest_beacon_api.nim @@ -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) - - return RestApiResponse.jsonMsgResponse(BlockValidationSuccess) - of ConsensusFork.Capella: - let - restBlock = - decodeBodyJsonOrSsz(capella_mev.SignedBlindedBeaconBlock, - body).valueOr: - return RestApiResponse.jsonError(error) + 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) - 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) + if forked.kind != node.dag.cfg.consensusForkAtEpoch( + getForkedBlockField(forked, slot).epoch): + return RestApiResponse.jsonError(Http400, InvalidBlockObjectError) - if res.isErr(): - return RestApiResponse.jsonError( - Http500, InternalServerError, $res.error()) - if res.get().isNone(): - return RestApiResponse.jsonError(Http202, BlockValidationError) + let res = withBlck(forked): + forkyBlck.root = hash_tree_root(forkyBlck.message) + await node.router.routeSignedBeaconBlock( + forkyBlck, Opt.none(seq[BlobSidecar])) - 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) + 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 ( diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index 42b030d805..9fdf7ce8bf 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -99,7 +99,7 @@ type SetGasLimitRequest | bellatrix_mev.SignedBlindedBeaconBlock | capella_mev.SignedBlindedBeaconBlock | - deneb_mev.SignedBlindedBeaconBlockContents | + deneb_mev.SignedBlindedBeaconBlock | SignedValidatorRegistrationV1 | SignedVoluntaryExit | Web3SignerRequest | diff --git a/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim b/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim index 07f692e22c..915f530a23 100644 --- a/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim @@ -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.} diff --git a/beacon_chain/spec/forks.nim b/beacon_chain/spec/forks.nim index 0090223583..418ae67ee3 100644 --- a/beacon_chain/spec/forks.nim +++ b/beacon_chain/spec/forks.nim @@ -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 diff --git a/beacon_chain/spec/helpers.nim b/beacon_chain/spec/helpers.nim index 774e6a3ea1..3b1df40e16 100644 --- a/beacon_chain/spec/helpers.nim +++ b/beacon_chain/spec/helpers.nim @@ -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: diff --git a/beacon_chain/spec/mev/deneb_mev.nim b/beacon_chain/spec/mev/deneb_mev.nim index f64f87860c..6a91236592 100644 --- a/beacon_chain/spec/mev/deneb_mev.nim +++ b/beacon_chain/spec/mev/deneb_mev.nim @@ -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) - ) diff --git a/beacon_chain/spec/mev/rest_deneb_mev_calls.nim b/beacon_chain/spec/mev/rest_deneb_mev_calls.nim index b1a20500c6..6f981d7169 100644 --- a/beacon_chain/spec/mev/rest_deneb_mev_calls.nim +++ b/beacon_chain/spec/mev/rest_deneb_mev_calls.nim @@ -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}.} diff --git a/beacon_chain/validators/beacon_validators.nim b/beacon_chain/validators/beacon_validators.nim index 6d7c28aa0e..df1c744f2e 100644 --- a/beacon_chain/validators/beacon_validators.nim +++ b/beacon_chain/validators/beacon_validators.nim @@ -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) - - 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 + blindedBundle.execution_payload_header) + assign( + blindedBlock.message.body.blob_kzg_commitments, + blindedBundle.blob_kzg_commitments) - blindedBlockContents + blindedBlock func constructPlainBlindedBlock[ T: capella_mev.BlindedBeaconBlock, EPH: capella.ExecutionPayloadHeader]( @@ -685,49 +663,13 @@ func constructPlainBlindedBlock[ blindedBlock -proc blindedBlockCheckSlashingAndSign[T: capella_mev.SignedBlindedBeaconBlock]( - node: BeaconNode, slot: Slot, validator: AttachedValidator, - validator_index: ValidatorIndex, nonsignedBlindedBlock: T): - Future[Result[T, string]] {.async.} = - # 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), - 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]( + T: + capella_mev.SignedBlindedBeaconBlock | + deneb_mev.SignedBlindedBeaconBlock]( node: BeaconNode, slot: Slot, validator: AttachedValidator, - validator_index: ValidatorIndex, nonsignedBlindedBlockContents: T): + validator_index: ValidatorIndex, nonsignedBlindedBlock: 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) @@ -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: - proposeBlockContinuation( - deneb_mev.SignedBlindedBeaconBlockContents, - deneb.ExecutionPayloadForSigning) - elif slot.epoch >= node.dag.cfg.CAPELLA_FORK_EPOCH: + return withConsensusFork(node.dag.cfg.consensusForkAtEpoch(slot.epoch)): + when consensusFork >= ConsensusFork.Capella: 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 diff --git a/beacon_chain/validators/message_router_mev.nim b/beacon_chain/validators/message_router_mev.nim index b04872626c..a7df35522e 100644 --- a/beacon_chain/validators/message_router_mev.nim +++ b/beacon_chain/validators/message_router_mev.nim @@ -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 + when consensusFork >= ConsensusFork.Deneb: + template execution_payload: untyped = bundle.data.data.execution_payload + else: + 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]) - info "Proposing blinded Builder API block and blobs", - blindedBlock = shortLog(blindedBlock) + debug "unblindAndRouteBlockMEV: proposing unblinded block", + blck = shortLog(signedBlock) - # 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) + 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 - 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: - # 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)) + 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