From 128834a8eb449982470fc2955194809d8419edbd Mon Sep 17 00:00:00 2001 From: tersec Date: Wed, 24 Jan 2024 23:27:22 +0000 Subject: [PATCH] use `RestPlainResponse` to improve builder API rerror reporting (#5819) --- .../spec/mev/rest_capella_mev_calls.nim | 4 +- .../spec/mev/rest_deneb_mev_calls.nim | 4 +- beacon_chain/validators/beacon_validators.nim | 51 +++++++++++++------ .../validators/message_router_mev.nim | 35 +++++++++---- 4 files changed, 66 insertions(+), 28 deletions(-) diff --git a/beacon_chain/spec/mev/rest_capella_mev_calls.nim b/beacon_chain/spec/mev/rest_capella_mev_calls.nim index c25eeb08e..167bd5b1c 100644 --- a/beacon_chain/spec/mev/rest_capella_mev_calls.nim +++ b/beacon_chain/spec/mev/rest_capella_mev_calls.nim @@ -22,13 +22,13 @@ proc registerValidator*(body: seq[SignedValidatorRegistrationV1] proc getHeaderCapella*(slot: Slot, parent_hash: Eth2Digest, pubkey: ValidatorPubKey - ): RestResponse[GetHeaderResponseCapella] {. + ): RestPlainResponse {. rest, endpoint: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}", meth: MethodGet, connection: {Dedicated, Close}.} ## https://github.com/ethereum/builder-specs/blob/v0.4.0/apis/builder/header.yaml proc submitBlindedBlock*(body: capella_mev.SignedBlindedBeaconBlock - ): RestResponse[SubmitBlindedBlockResponseCapella] {. + ): RestPlainResponse {. rest, endpoint: "/eth/v1/builder/blinded_blocks", meth: MethodPost, connection: {Dedicated, Close}.} ## https://github.com/ethereum/builder-specs/blob/v0.4.0/apis/builder/blinded_blocks.yaml diff --git a/beacon_chain/spec/mev/rest_deneb_mev_calls.nim b/beacon_chain/spec/mev/rest_deneb_mev_calls.nim index c0b2aee58..a83ef24c5 100644 --- a/beacon_chain/spec/mev/rest_deneb_mev_calls.nim +++ b/beacon_chain/spec/mev/rest_deneb_mev_calls.nim @@ -15,13 +15,13 @@ export chronos, client, rest_types, eth2_rest_serialization proc getHeaderDeneb*(slot: Slot, parent_hash: Eth2Digest, pubkey: ValidatorPubKey - ): RestResponse[GetHeaderResponseDeneb] {. + ): RestPlainResponse {. rest, endpoint: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}", meth: MethodGet, connection: {Dedicated, Close}.} ## https://github.com/ethereum/builder-specs/blob/v0.4.0/apis/builder/header.yaml proc submitBlindedBlock*(body: deneb_mev.SignedBlindedBeaconBlock - ): RestResponse[SubmitBlindedBlockResponseDeneb] {. + ): RestPlainResponse {. rest, endpoint: "/eth/v1/builder/blinded_blocks", meth: MethodPost, connection: {Dedicated, Close}.} ## https://github.com/ethereum/builder-specs/blob/v0.4.0/apis/builder/blinded_blocks.yaml diff --git a/beacon_chain/validators/beacon_validators.nim b/beacon_chain/validators/beacon_validators.nim index 355d20fbc..0bc219d56 100644 --- a/beacon_chain/validators/beacon_validators.nim +++ b/beacon_chain/validators/beacon_validators.nim @@ -568,34 +568,55 @@ proc getBlindedExecutionPayload[ # Not ideal to use `when` where instead of splitting into separate functions, # but Nim doesn't overload on generic EPH type parameter. when EPH is capella.ExecutionPayloadHeader: - let blindedHeader = awaitWithTimeout( - payloadBuilderClient.getHeaderCapella(slot, executionBlockRoot, pubkey), - BUILDER_PROPOSAL_DELAY_TOLERANCE): - return err "Timeout obtaining Capella blinded header from builder" + let + response = awaitWithTimeout( + payloadBuilderClient.getHeaderCapella( + slot, executionBlockRoot, pubkey), + BUILDER_PROPOSAL_DELAY_TOLERANCE): + return err "Timeout obtaining Capella blinded header from builder" + + res = decodeBytes( + GetHeaderResponseCapella, response.data, response.contentType) + + blindedHeader = res.valueOr: + return err( + "Unable to decode Capella blinded header: " & $res.error & + " with HTTP status " & $response.status & ", Content-Type " & + $response.contentType & " and content " & $response.data) elif EPH is deneb_mev.BlindedExecutionPayloadAndBlobsBundle: - let blindedHeader = awaitWithTimeout( - payloadBuilderClient.getHeaderDeneb(slot, executionBlockRoot, pubkey), - BUILDER_PROPOSAL_DELAY_TOLERANCE): - return err "Timeout obtaining Deneb blinded header and blob bundle from builder" + let + response = awaitWithTimeout( + payloadBuilderClient.getHeaderDeneb( + slot, executionBlockRoot, pubkey), + BUILDER_PROPOSAL_DELAY_TOLERANCE): + return err "Timeout obtaining Deneb blinded header from builder" + + res = decodeBytes( + GetHeaderResponseDeneb, response.data, response.contentType) + + blindedHeader = res.valueOr: + return err( + "Unable to decode Deneb blinded header: " & $res.error & + " with HTTP status " & $response.status & ", Content-Type " & + $response.contentType & " and content " & $response.data) else: static: doAssert false const httpOk = 200 - if blindedHeader.status != httpOk: + if response.status != httpOk: return err "getBlindedExecutionPayload: non-200 HTTP response" else: if not verify_builder_signature( - node.dag.cfg.genesisFork, blindedHeader.data.data.message, - blindedHeader.data.data.message.pubkey, - blindedHeader.data.data.signature): + node.dag.cfg.genesisFork, blindedHeader.data.message, + blindedHeader.data.message.pubkey, blindedHeader.data.signature): return err "getBlindedExecutionPayload: signature verification failed" when EPH is capella.ExecutionPayloadHeader: return ok(( - blindedBlckPart: blindedHeader.data.data.message.header, - blockValue: blindedHeader.data.data.message.value)) + blindedBlckPart: blindedHeader.data.message.header, + blockValue: blindedHeader.data.message.value)) elif EPH is deneb_mev.BlindedExecutionPayloadAndBlobsBundle: - template builderBid: untyped = blindedHeader.data.data.message + template builderBid: untyped = blindedHeader.data.message return ok(( blindedBlckPart: EPH( execution_payload_header: builderBid.header, diff --git a/beacon_chain/validators/message_router_mev.nim b/beacon_chain/validators/message_router_mev.nim index bad4c3f10..56e1b358d 100644 --- a/beacon_chain/validators/message_router_mev.nim +++ b/beacon_chain/validators/message_router_mev.nim @@ -55,7 +55,7 @@ proc unblindAndRouteBlockMEV*( # By time submitBlindedBlock is called, must already have done slashing # protection check - let bundle = + let response = try: awaitWithTimeout( payloadBuilderRestClient.submitBlindedBlock(blindedBlock), @@ -63,13 +63,11 @@ proc unblindAndRouteBlockMEV*( return err("Submitting blinded block 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: " & exc.msg) except CatchableError as exc: return err("exception in submitBlindedBlock: " & exc.msg) const httpOk = 200 - if bundle.status != httpOk: + if response.status != httpOk: # https://github.com/ethereum/builder-specs/blob/v0.4.0/specs/bellatrix/validator.md#proposer-slashing # This means if a validator publishes a signature for a # `BlindedBeaconBlock` (via a dissemination of a @@ -77,12 +75,31 @@ proc unblindAndRouteBlockMEV*( # local build process as a fallback, even in the event of some failure # with the external builder network. return err("submitBlindedBlock failed with HTTP error code " & - $bundle.status & ": " & $shortLog(blindedBlock)) + $response.status & ": " & $shortLog(blindedBlock)) when consensusFork >= ConsensusFork.Deneb: - template execution_payload: untyped = bundle.data.data.execution_payload + let + res = decodeBytes( + SubmitBlindedBlockResponseDeneb, response.data, response.contentType) + + bundle = res.valueOr: + return err("Could not decode Deneb blinded block: " & $res.error & + " with HTTP status " & $response.status & ", Content-Type " & + $response.contentType & " and content " & $response.data) + + template execution_payload: untyped = bundle.data.execution_payload else: - template execution_payload: untyped = bundle.data.data + let + res = decodeBytes( + SubmitBlindedBlockResponseCapella, response.data, response.contentType) + + bundle = res.valueOr: + return err("Could not decode Capella blinded block: " & $res.error & + " with HTTP status " & $response.status & ", Content-Type " & + $response.contentType & " and content " & $response.data) + + template execution_payload: untyped = bundle.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: " & @@ -105,9 +122,9 @@ proc unblindAndRouteBlockMEV*( let blobsOpt = when consensusFork >= ConsensusFork.Deneb: - template blobs_bundle: untyped = bundle.data.data.blobs_bundle + template blobs_bundle: untyped = bundle.data.blobs_bundle if blindedBlock.message.body.blob_kzg_commitments != - bundle.data.data.blobs_bundle.commitments: + bundle.data.blobs_bundle.commitments: return err("unblinded blobs bundle has unexpected commitments") let ok = verifyProofs( asSeq blobs_bundle.blobs,