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):
|
(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 (
|
||||||
|
|
|
@ -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 |
|
||||||
|
|
|
@ -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.}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
|
||||||
)
|
|
||||||
|
|
|
@ -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}.}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue