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:
parent
ec6780ed6f
commit
98e969084d
|
@ -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 (
|
||||
|
|
|
@ -99,7 +99,7 @@ type
|
|||
SetGasLimitRequest |
|
||||
bellatrix_mev.SignedBlindedBeaconBlock |
|
||||
capella_mev.SignedBlindedBeaconBlock |
|
||||
deneb_mev.SignedBlindedBeaconBlockContents |
|
||||
deneb_mev.SignedBlindedBeaconBlock |
|
||||
SignedValidatorRegistrationV1 |
|
||||
SignedVoluntaryExit |
|
||||
Web3SignerRequest |
|
||||
|
|
|
@ -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.}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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}.}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue