refactor payload builder REST client usage (#4973)

* refactor payload builder REST client usage

* change HTTP response code
This commit is contained in:
tersec 2023-05-25 15:38:56 +00:00 committed by GitHub
parent 8833acbe23
commit d1941b670a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 62 deletions

View File

@ -70,7 +70,6 @@ type
lightClientPool*: ref LightClientPool
validatorChangePool*: ref ValidatorChangePool
elManager*: ELManager
payloadBuilderRestClient*: RestClientRef
restServer*: RestServerRef
keymanagerHost*: ref KeymanagerHost
keymanagerServer*: RestServerRef
@ -115,3 +114,14 @@ template rng*(node: BeaconNode): ref HmacDrbgContext =
proc currentSlot*(node: BeaconNode): Slot =
node.beaconClock.now.slotOrZero
proc getPayloadBuilderClient*(node: BeaconNode): RestResult[RestClientRef] =
if node.config.payloadBuilderEnable:
# Logging done in caller
let res = RestClientRef.new(node.config.payloadBuilderUrl)
if res.isOk and res.get.isNil:
err "Got nil payload builder REST client reference"
else:
res
else:
err "Payload builder globally disabled"

View File

@ -696,16 +696,7 @@ proc init*(T: type BeaconNode,
else:
nil
let payloadBuilderRestClient =
if config.payloadBuilderEnable:
RestClientRef.new(config.payloadBuilderUrl).valueOr:
warn "Payload builder REST client setup failed",
payloadBuilderUrl = config.payloadBuilderUrl
nil
else:
nil
if config.payloadBuilderEnable and payloadBuilderRestClient != nil:
if config.payloadBuilderEnable:
info "Using external payload builder",
payloadBuilderUrl = config.payloadBuilderUrl
@ -719,7 +710,6 @@ proc init*(T: type BeaconNode,
config: config,
attachedValidators: validatorPool,
elManager: elManager,
payloadBuilderRestClient: payloadBuilderRestClient,
restServer: restServer,
keymanagerHost: keymanagerHost,
keymanagerServer: keymanagerInitResult.server,

View File

@ -886,6 +886,10 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
currentEpochFork.toString != version:
return RestApiResponse.jsonError(Http400, BlockIncorrectFork)
let payloadBuilderClient = node.getPayloadBuilderClient().valueOr:
return RestApiResponse.jsonError(
Http400, "Unable to initialize payload builder client: " & $error)
case currentEpochFork
of ConsensusFork.Deneb:
return RestApiResponse.jsonError(Http500, $denebImplementationMissing)
@ -896,7 +900,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
capella_mev.SignedBlindedBeaconBlock, body).valueOr:
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError,
$error)
await node.unblindAndRouteBlockMEV(restBlock)
await node.unblindAndRouteBlockMEV(payloadBuilderClient, restBlock)
if res.isErr():
return RestApiResponse.jsonError(
@ -912,7 +916,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
bellatrix_mev.SignedBlindedBeaconBlock, body).valueOr:
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError,
$error)
await node.unblindAndRouteBlockMEV(restBlock)
await node.unblindAndRouteBlockMEV(payloadBuilderClient, restBlock)
if res.isErr():
return RestApiResponse.jsonError(

View File

@ -508,7 +508,12 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
else:
RestApiResponse.jsonError(Http500, InvalidAcceptError)
let contextFork = node.dag.cfg.consensusForkAtEpoch(node.currentSlot.epoch)
let
payloadBuilderClient = node.getPayloadBuilderClient().valueOr:
return RestApiResponse.jsonError(
Http500, "Unable to initialize payload builder client: " & $error)
contextFork = node.dag.cfg.consensusForkAtEpoch(node.currentSlot.epoch)
case contextFork
of ConsensusFork.Deneb:
# TODO
@ -518,14 +523,14 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
of ConsensusFork.Capella:
let res = await makeBlindedBeaconBlockForHeadAndSlot[
capella_mev.BlindedBeaconBlock](
node, qrandao, proposer, qgraffiti, qhead, qslot)
node, payloadBuilderClient, qrandao, proposer, qgraffiti, qhead, qslot)
if res.isErr():
return RestApiResponse.jsonError(Http400, res.error())
return responseVersioned(res.get().blindedBlckPart, contextFork)
of ConsensusFork.Bellatrix:
let res = await makeBlindedBeaconBlockForHeadAndSlot[
bellatrix_mev.BlindedBeaconBlock](
node, qrandao, proposer, qgraffiti, qhead, qslot)
node, payloadBuilderClient, qrandao, proposer, qgraffiti, qhead, qslot)
if res.isErr():
return RestApiResponse.jsonError(Http400, res.error())
return responseVersioned(res.get().blindedBlckPart, contextFork)

View File

@ -45,17 +45,15 @@ macro copyFields*(
# https://github.com/nim-lang/Nim/issues/21347 fixed, combine and make generic
# these two very similar versions of unblindAndRouteBlockMEV
proc unblindAndRouteBlockMEV*(
node: BeaconNode, blindedBlock: bellatrix_mev.SignedBlindedBeaconBlock):
node: BeaconNode, payloadBuilderRestClient: RestClientRef,
blindedBlock: bellatrix_mev.SignedBlindedBeaconBlock):
Future[Result[Opt[BlockRef], string]] {.async.} =
# By time submitBlindedBlock is called, must already have done slashing
# protection check
if node.payloadBuilderRestClient.isNil:
return err "unblindAndRouteBlockMEV: nil REST client"
let unblindedPayload =
try:
awaitWithTimeout(
node.payloadBuilderRestClient.submitBlindedBlock(blindedBlock),
payloadBuilderRestClient.submitBlindedBlock(blindedBlock),
BUILDER_BLOCK_SUBMISSION_DELAY_TOLERANCE):
return err("Submitting blinded block timed out")
# From here on, including error paths, disallow local EL production by
@ -122,17 +120,15 @@ proc unblindAndRouteBlockMEV*(
# Only difference is `var signedBlock = capella.SignedBeaconBlock` instead of
# `var signedBlock = bellatrix.SignedBeaconBlock`
proc unblindAndRouteBlockMEV*(
node: BeaconNode, blindedBlock: capella_mev.SignedBlindedBeaconBlock):
node: BeaconNode, payloadBuilderRestClient: RestClientRef,
blindedBlock: capella_mev.SignedBlindedBeaconBlock):
Future[Result[Opt[BlockRef], string]] {.async.} =
# By time submitBlindedBlock is called, must already have done slashing
# protection check
if node.payloadBuilderRestClient.isNil:
return err "unblindAndRouteBlockMEV: nil REST client"
let unblindedPayload =
try:
awaitWithTimeout(
node.payloadBuilderRestClient.submitBlindedBlock(blindedBlock),
payloadBuilderRestClient.submitBlindedBlock(blindedBlock),
BUILDER_BLOCK_SUBMISSION_DELAY_TOLERANCE):
return err("Submitting blinded block timed out")
# From here on, including error paths, disallow local EL production by

View File

@ -453,23 +453,19 @@ proc makeBeaconBlockForHeadAndSlot*(
proc getBlindedExecutionPayload[
EPH: bellatrix.ExecutionPayloadHeader | capella.ExecutionPayloadHeader](
node: BeaconNode, slot: Slot, executionBlockRoot: Eth2Digest,
pubkey: ValidatorPubKey): Future[BlindedBlockResult[EPH]] {.async.} =
if node.payloadBuilderRestClient.isNil:
return err "getBlindedExecutionPayload: nil REST client"
node: BeaconNode, payloadBuilderClient: RestClientRef, slot: Slot,
executionBlockRoot: Eth2Digest, pubkey: ValidatorPubKey):
Future[BlindedBlockResult[EPH]] {.async.} =
when EPH is capella.ExecutionPayloadHeader:
let blindedHeader = awaitWithTimeout(
node.payloadBuilderRestClient.getHeaderCapella(
slot, executionBlockRoot, pubkey),
payloadBuilderClient.getHeaderCapella(slot, executionBlockRoot, pubkey),
BUILDER_PROPOSAL_DELAY_TOLERANCE):
return err "Timeout when obtaining Capella blinded header from builder"
return err "Timeout obtaining Capella blinded header from builder"
elif EPH is bellatrix.ExecutionPayloadHeader:
let blindedHeader = awaitWithTimeout(
node.payloadBuilderRestClient.getHeaderBellatrix(
slot, executionBlockRoot, pubkey),
payloadBuilderClient.getHeaderBellatrix(slot, executionBlockRoot, pubkey),
BUILDER_PROPOSAL_DELAY_TOLERANCE):
return err "Timeout when obtaining Bellatrix blinded header from builder"
return err "Timeout obtaining Bellatrix blinded header from builder"
else:
static: doAssert false
@ -584,17 +580,17 @@ proc getUnsignedBlindedBeaconBlock[
return err("getUnsignedBlindedBeaconBlock: attempt to construct pre-Bellatrix blinded block")
proc getBlindedBlockParts[EPH: ForkyExecutionPayloadHeader](
node: BeaconNode, head: BlockRef, pubkey: ValidatorPubKey,
slot: Slot, randao: ValidatorSig, validator_index: ValidatorIndex,
graffiti: GraffitiBytes): Future[Result[(EPH, UInt256, ForkedBeaconBlock), string]]
{.async.} =
node: BeaconNode, payloadBuilderClient: RestClientRef, head: BlockRef,
pubkey: ValidatorPubKey, slot: Slot, randao: ValidatorSig,
validator_index: ValidatorIndex, graffiti: GraffitiBytes):
Future[Result[(EPH, UInt256, ForkedBeaconBlock), string]] {.async.} =
let
executionBlockRoot = node.dag.loadExecutionBlockHash(head)
executionPayloadHeader =
try:
awaitWithTimeout(
getBlindedExecutionPayload[EPH](
node, slot, executionBlockRoot, pubkey),
node, payloadBuilderClient, slot, executionBlockRoot, pubkey),
BUILDER_PROPOSAL_DELAY_TOLERANCE):
BlindedBlockResult[EPH].err("getBlindedExecutionPayload timed out")
except RestDecodingError as exc:
@ -661,8 +657,9 @@ proc getBlindedBlockParts[EPH: ForkyExecutionPayloadHeader](
proc getBuilderBid[
SBBB: bellatrix_mev.SignedBlindedBeaconBlock |
capella_mev.SignedBlindedBeaconBlock](
node: BeaconNode, head: BlockRef, validator: AttachedValidator, slot: Slot,
randao: ValidatorSig, validator_index: ValidatorIndex):
node: BeaconNode, payloadBuilderClient: RestClientRef, head: BlockRef,
validator: AttachedValidator, slot: Slot, randao: ValidatorSig,
validator_index: ValidatorIndex):
Future[BlindedBlockResult[SBBB]] {.async.} =
## Returns the unsigned blinded block obtained from the Builder API.
## Used by the BN's own validators, but not the REST server
@ -674,8 +671,8 @@ proc getBuilderBid[
static: doAssert false
let blindedBlockParts = await getBlindedBlockParts[EPH](
node, head, validator.pubkey, slot, randao, validator_index,
node.graffitiBytes)
node, payloadBuilderClient, head, validator.pubkey, slot, randao,
validator_index, node.graffitiBytes)
if blindedBlockParts.isErr:
# Not signed yet, fine to try to fall back on EL
beacon_block_builder_missed_with_fallback.inc()
@ -694,9 +691,11 @@ proc getBuilderBid[
return ok (unsignedBlindedBlock.get, bidValue)
proc proposeBlockMEV(node: BeaconNode, blindedBlock: auto):
proc proposeBlockMEV(
node: BeaconNode, payloadBuilderClient: RestClientRef, blindedBlock: auto):
Future[Result[BlockRef, string]] {.async.} =
let unblindedBlockRef = await node.unblindAndRouteBlockMEV(blindedBlock)
let unblindedBlockRef = await node.unblindAndRouteBlockMEV(
payloadBuilderClient, blindedBlock)
return if unblindedBlockRef.isOk and unblindedBlockRef.get.isSome:
beacon_blocks_proposed.inc()
ok(unblindedBlockRef.get.get)
@ -727,9 +726,10 @@ func isEFMainnet(cfg: RuntimeConfig): bool =
proc makeBlindedBeaconBlockForHeadAndSlot*[
BBB: bellatrix_mev.BlindedBeaconBlock | capella_mev.BlindedBeaconBlock](
node: BeaconNode, randao_reveal: ValidatorSig,
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
slot: Slot): Future[BlindedBlockResult[BBB]] {.async.} =
node: BeaconNode, payloadBuilderClient: RestClientRef,
randao_reveal: ValidatorSig, validator_index: ValidatorIndex,
graffiti: GraffitiBytes, head: BlockRef, slot: Slot):
Future[BlindedBlockResult[BBB]] {.async.} =
## Requests a beacon node to produce a valid blinded block, which can then be
## signed by a validator. A blinded block is a block with only a transactions
## root, rather than a full transactions list.
@ -762,7 +762,8 @@ proc makeBlindedBeaconBlockForHeadAndSlot*[
forkyState.data.validators.item(validator_index).pubkey
blindedBlockParts = await getBlindedBlockParts[EPH](
node, head, pubkey, slot, randao_reveal, validator_index, graffiti)
node, payloadBuilderClient, head, pubkey, slot, randao_reveal,
validator_index, graffiti)
if blindedBlockParts.isErr:
# Don't try EL fallback -- VC specifically requested a blinded block
return err("Unable to create blinded block")
@ -790,8 +791,17 @@ proc proposeBlockAux(
genesis_validators_root: Eth2Digest,
localBlockValueBoost: uint8): Future[BlockRef] {.async.} =
# Collect bids
var payloadBuilderClient: RestClientRef
let payloadBuilderClientMaybe = node.getPayloadBuilderClient()
if payloadBuilderClientMaybe.isErr:
warn "Unable to initialize payload builder client while proposing block",
err = payloadBuilderClientMaybe.error
else:
payloadBuilderClient = payloadBuilderClientMaybe.get
let usePayloadBuilder =
if node.config.payloadBuilderEnable:
if node.config.payloadBuilderEnable and payloadBuilderClientMaybe.isOk:
withState(node.dag.headState):
# Head slot, not proposal slot, matters here
# TODO it might make some sense to allow use of builder API if local
@ -806,7 +816,9 @@ proc proposeBlockAux(
let
payloadBuilderBidFut =
if usePayloadBuilder:
getBuilderBid[SBBB](node, head, validator, slot, randao, validator_index)
getBuilderBid[SBBB](
node, payloadBuilderClient, head, validator, slot, randao,
validator_index)
else:
let fut = newFuture[BlindedBlockResult[SBBB]]("builder-bid")
fut.complete(BlindedBlockResult[SBBB].err(
@ -885,7 +897,8 @@ proc proposeBlockAux(
return head
# Before proposeBlockMEV, can fall back to EL; after, cannot without
# risking slashing.
maybeUnblindedBlock = await proposeBlockMEV(node, blindedBlock)
maybeUnblindedBlock = await proposeBlockMEV(
node, payloadBuilderClient, blindedBlock)
return maybeUnblindedBlock.valueOr:
warn "Blinded block proposal incomplete",
@ -1383,14 +1396,15 @@ proc registerValidators*(node: BeaconNode, epoch: Epoch) {.async.} =
if (not node.config.payloadBuilderEnable) or
node.currentSlot.epoch < node.dag.cfg.BELLATRIX_FORK_EPOCH:
return
elif node.config.payloadBuilderEnable and
node.payloadBuilderRestClient.isNil:
warn "registerValidators: node.config.payloadBuilderEnable and node.payloadBuilderRestClient.isNil"
return
const HttpOk = 200
let restBuilderStatus = awaitWithTimeout(node.payloadBuilderRestClient.checkBuilderStatus(),
let payloadBuilderClient = node.getPayloadBuilderClient().valueOr:
debug "Unable to initialize payload builder client while registering validators",
err = error
return
let restBuilderStatus = awaitWithTimeout(payloadBuilderClient.checkBuilderStatus(),
BUILDER_STATUS_DELAY_TOLERANCE):
debug "Timeout when obtaining builder status"
return
@ -1494,7 +1508,7 @@ proc registerValidators*(node: BeaconNode, epoch: Epoch) {.async.} =
for chunkIdx in 0 ..< validatorRegistrations.len:
let registerValidatorResult =
awaitWithTimeout(
node.payloadBuilderRestClient.registerValidator(
payloadBuilderClient.registerValidator(
validatorRegistrations[chunkIdx]),
BUILDER_VALIDATOR_REGISTRATION_DELAY_TOLERANCE):
error "Timeout when registering validator with builder"