diff --git a/beacon_chain/beacon_node.nim b/beacon_chain/beacon_node.nim index f40848386..61d5b023c 100644 --- a/beacon_chain/beacon_node.nim +++ b/beacon_chain/beacon_node.nim @@ -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" diff --git a/beacon_chain/nimbus_beacon_node.nim b/beacon_chain/nimbus_beacon_node.nim index 88cd8c841..6aabf8220 100644 --- a/beacon_chain/nimbus_beacon_node.nim +++ b/beacon_chain/nimbus_beacon_node.nim @@ -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, diff --git a/beacon_chain/rpc/rest_beacon_api.nim b/beacon_chain/rpc/rest_beacon_api.nim index 82c840e0e..a74c1d73e 100644 --- a/beacon_chain/rpc/rest_beacon_api.nim +++ b/beacon_chain/rpc/rest_beacon_api.nim @@ -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( diff --git a/beacon_chain/rpc/rest_validator_api.nim b/beacon_chain/rpc/rest_validator_api.nim index 5e057d7ac..660744ad7 100644 --- a/beacon_chain/rpc/rest_validator_api.nim +++ b/beacon_chain/rpc/rest_validator_api.nim @@ -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) diff --git a/beacon_chain/validators/message_router_mev.nim b/beacon_chain/validators/message_router_mev.nim index b645cc8fb..e410d03d0 100644 --- a/beacon_chain/validators/message_router_mev.nim +++ b/beacon_chain/validators/message_router_mev.nim @@ -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 diff --git a/beacon_chain/validators/validator_duties.nim b/beacon_chain/validators/validator_duties.nim index 76c318adb..3c84a78bf 100644 --- a/beacon_chain/validators/validator_duties.nim +++ b/beacon_chain/validators/validator_duties.nim @@ -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"