From 8fa6064b9a15e495bd27e702e60e3fbf88a0c4c9 Mon Sep 17 00:00:00 2001 From: Eugene Kabanov Date: Thu, 24 Nov 2022 11:14:05 +0200 Subject: [PATCH] VC: blinded block publishing support (#4332) * Add blind REST API declarations and implementations. * shortLog is still not stable. * Fix shortLog issues. * Enable disabled logging statements. * Address review comments. * Avoid templates suffering from double evaluation of their params * Address review comments. * Fix compilation issue. Co-authored-by: Zahary Karadjov --- .../eth2_apis/eth2_rest_serialization.nim | 75 +++- .../spec/eth2_apis/rest_beacon_calls.nim | 28 ++ beacon_chain/spec/eth2_apis/rest_types.nim | 1 + .../spec/eth2_apis/rest_validator_calls.nim | 7 + beacon_chain/spec/forks.nim | 52 ++- beacon_chain/spec/mev/bellatrix_mev.nim | 13 + beacon_chain/validator_client/api.nim | 194 ++++++++++ .../validator_client/block_service.nim | 346 +++++++++++++----- beacon_chain/validators/validator_pool.nim | 28 +- 9 files changed, 629 insertions(+), 115 deletions(-) diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index 8378f07d8..a65ebec11 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -123,6 +123,7 @@ type ListFeeRecipientResponse | PrepareBeaconProposer | ProduceBlockResponseV2 | + ProduceBlindedBlockResponse | RestIndexedErrorMessage | RestErrorMessage | RestValidator | @@ -886,29 +887,31 @@ template unrecognizedFieldWarning = ## ForkedBeaconBlock template prepareForkedBlockReading( reader: var JsonReader[RestJson], value: untyped, - version: var Option[BeaconBlockFork], data: var Option[JsonString]) = + version: var Option[BeaconBlockFork], + data: var Option[JsonString], + blockTypeName: cstring) = for fieldName {.inject.} in readObjectFields(reader): case fieldName of "version": if version.isSome(): reader.raiseUnexpectedField("Multiple version fields found", - "ForkedBeaconBlock") - let vres = reader.readValue(string) + blockTypeName) + let vres = reader.readValue(string).toLowerAscii() case vres - of "PHASE0", "phase0": + of "phase0": version = some(BeaconBlockFork.Phase0) - of "ALTAIR", "altair": + of "altair": version = some(BeaconBlockFork.Altair) - of "BELLATRIX", "bellatrix": + of "bellatrix": version = some(BeaconBlockFork.Bellatrix) - of "CAPELLA", "capella": + of "capella": version = some(BeaconBlockFork.Bellatrix) else: reader.raiseUnexpectedValue("Incorrect version field value") of "block", "block_header", "data": if data.isSome(): reader.raiseUnexpectedField("Multiple block or block_header fields found", - "ForkedBeaconBlock") + blockTypeName) data = some(reader.readValue(JsonString)) else: unrecognizedFieldWarning() @@ -925,7 +928,7 @@ proc readValue*[BlockType: ForkedBeaconBlock]( version: Option[BeaconBlockFork] data: Option[JsonString] - prepareForkedBlockReading(reader, value, version, data) + prepareForkedBlockReading(reader, value, version, data, "ForkedBeaconBlock") case version.get(): of BeaconBlockFork.Phase0: @@ -967,6 +970,57 @@ proc readValue*[BlockType: ForkedBeaconBlock]( of BeaconBlockFork.Capella: reader.raiseUnexpectedValue($capellaImplementationMissing) +proc readValue*[BlockType: ForkedBlindedBeaconBlock]( + reader: var JsonReader[RestJson], + value: var BlockType + ) {.raises: [IOError, SerializationError, Defect].} = + var + version: Option[BeaconBlockFork] + data: Option[JsonString] + + prepareForkedBlockReading(reader, value, version, data, + "ForkedBlindedBeaconBlock") + + case version.get(): + of BeaconBlockFork.Phase0: + let res = + try: + RestJson.decode(string(data.get()), + phase0.BeaconBlock, + requireAllFields = true, + allowUnknownFields = true) + except SerializationError as exc: + reader.raiseUnexpectedValue("Incorrect phase0 block format, [" & + exc.formatMsg("BlindedBlock") & "]") + value = ForkedBlindedBeaconBlock(kind: BeaconBlockFork.Phase0, + phase0Data: res) + of BeaconBlockFork.Altair: + let res = + try: + RestJson.decode(string(data.get()), + altair.BeaconBlock, + requireAllFields = true, + allowUnknownFields = true) + except SerializationError as exc: + reader.raiseUnexpectedValue("Incorrect altair block format, [" & + exc.formatMsg("BlindedBlock") & "]") + value = ForkedBlindedBeaconBlock(kind: BeaconBlockFork.Altair, + altairData: res) + of BeaconBlockFork.Bellatrix: + let res = + try: + RestJson.decode(string(data.get()), + BlindedBeaconBlock, + requireAllFields = true, + allowUnknownFields = true) + except SerializationError as exc: + reader.raiseUnexpectedValue("Incorrect bellatrix block format, [" & + exc.formatMsg("BlindedBlock") & "]") + value = ForkedBlindedBeaconBlock(kind: BeaconBlockFork.Bellatrix, + bellatrixData: res) + of BeaconBlockFork.Capella: + reader.raiseUnexpectedValue($capellaImplementationMissing) + proc readValue*[BlockType: Web3SignerForkedBeaconBlock]( reader: var JsonReader[RestJson], value: var BlockType) {.raises: [IOError, SerializationError, Defect].} = @@ -974,7 +1028,8 @@ proc readValue*[BlockType: Web3SignerForkedBeaconBlock]( version: Option[BeaconBlockFork] data: Option[JsonString] - prepareForkedBlockReading(reader, value, version, data) + prepareForkedBlockReading(reader, value, version, data, + "Web3SignerForkedBeaconBlock") case version.get(): of BeaconBlockFork.Phase0: diff --git a/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim b/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim index 2fa19255a..5e78a63b4 100644 --- a/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim @@ -13,6 +13,7 @@ import chronos, presto/client, chronicles, ".."/".."/validators/slashing_protection_common, ".."/datatypes/[phase0, altair, bellatrix], + ".."/mev/bellatrix_mev, ".."/[helpers, forks, keystore, eth2_ssz_serialization], "."/[rest_types, rest_common, eth2_rest_serialization] @@ -135,6 +136,33 @@ proc publishSszBlock*( extraHeaders = @[("eth-consensus-version", consensus)]) return resp +proc publishBlindedBlock*(body: phase0.SignedBeaconBlock): RestPlainResponse {. + rest, endpoint: "/eth/v1/beacon/blinded_blocks", + meth: MethodPost.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock + +proc publishBlindedBlock*(body: altair.SignedBeaconBlock): RestPlainResponse {. + rest, endpoint: "/eth/v1/beacon/blinded_blocks", + meth: MethodPost.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock + +proc publishBlindedBlock*(body: SignedBlindedBeaconBlock): RestPlainResponse {. + rest, endpoint: "/eth/v1/beacon/blinded_blocks", + meth: MethodPost.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock + +proc publishSszBlindedBlock*( + client: RestClientRef, + blck: ForkySignedBeaconBlock + ): Future[RestPlainResponse] {.async.} = + ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock + let + consensus = typeof(blck).toFork.toString() + resp = await client.publishBlindedBlock( + blck, restContentType = $OctetStreamMediaType, + extraHeaders = @[("eth-consensus-version", consensus)]) + return resp + proc getBlockV2Plain*(block_id: BlockIdent): RestPlainResponse {. rest, endpoint: "/eth/v2/beacon/blocks/{block_id}", accept: preferSSZ, diff --git a/beacon_chain/spec/eth2_apis/rest_types.nim b/beacon_chain/spec/eth2_apis/rest_types.nim index 43d3a6829..d56838926 100644 --- a/beacon_chain/spec/eth2_apis/rest_types.nim +++ b/beacon_chain/spec/eth2_apis/rest_types.nim @@ -604,6 +604,7 @@ type ProduceAttestationDataResponse* = DataEnclosedObject[AttestationData] ProduceBlockResponse* = DataEnclosedObject[phase0.BeaconBlock] ProduceBlockResponseV2* = ForkedBeaconBlock + ProduceBlindedBlockResponse* = ForkedBlindedBeaconBlock ProduceSyncCommitteeContributionResponse* = DataEnclosedObject[SyncCommitteeContribution] SubmitBlindedBlockResponse* = DataEnclosedObject[bellatrix.ExecutionPayload] GetValidatorsActivityResponse* = DataEnclosedObject[seq[RestActivityItem]] diff --git a/beacon_chain/spec/eth2_apis/rest_validator_calls.nim b/beacon_chain/spec/eth2_apis/rest_validator_calls.nim index c971da7cf..b48afba5d 100644 --- a/beacon_chain/spec/eth2_apis/rest_validator_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_validator_calls.nim @@ -41,6 +41,13 @@ proc produceBlockV2*(slot: Slot, randao_reveal: ValidatorSig, meth: MethodGet.} ## https://ethereum.github.io/beacon-APIs/#/Validator/produceBlockV2 +proc produceBlindedBlock*(slot: Slot, randao_reveal: ValidatorSig, + graffiti: GraffitiBytes + ): RestResponse[ProduceBlindedBlockResponse] {. + rest, endpoint: "/eth/v1/validator/blinded_blocks/{slot}", + meth: MethodGet.} + ## https://ethereum.github.io/beacon-APIs/#/Validator/produceBlindedBlock + proc produceAttestationData*(slot: Slot, committee_index: CommitteeIndex ): RestResponse[ProduceAttestationDataResponse] {. diff --git a/beacon_chain/spec/forks.nim b/beacon_chain/spec/forks.nim index fd2555382..14acaa062 100644 --- a/beacon_chain/spec/forks.nim +++ b/beacon_chain/spec/forks.nim @@ -22,7 +22,7 @@ import # it sequentially export extras, block_id, phase0, altair, bellatrix, eth2_merkleization, - eth2_ssz_serialization, presets + eth2_ssz_serialization, presets, bellatrix_mev # This file contains helpers for dealing with forks - we have two ways we can # deal with forks: @@ -159,6 +159,18 @@ type of BeaconBlockFork.Bellatrix: bellatrixData*: bellatrix.SignedBeaconBlock of BeaconBlockFork.Capella: capellaData*: capella.SignedBeaconBlock + ForkySignedBlindedBeaconBlock* = + phase0.SignedBeaconBlock | + altair.SignedBeaconBlock | + SignedBlindedBeaconBlock + + ForkedSignedBlindedBeaconBlock* = object + case kind*: BeaconBlockFork + of BeaconBlockFork.Phase0: phase0Data*: phase0.SignedBeaconBlock + of BeaconBlockFork.Altair: altairData*: altair.SignedBeaconBlock + of BeaconBlockFork.Bellatrix: bellatrixData*: SignedBlindedBeaconBlock + of BeaconBlockFork.Capella: capellaData*: SignedBlindedBeaconBlock + ForkySigVerifiedSignedBeaconBlock* = phase0.SigVerifiedSignedBeaconBlock | altair.SigVerifiedSignedBeaconBlock | @@ -265,8 +277,8 @@ template init*(T: type ForkedSignedBeaconBlock, blck: bellatrix.SignedBeaconBloc template init*(T: type ForkedSignedBeaconBlock, blck: capella.SignedBeaconBlock): T = T(kind: BeaconBlockFork.Capella, capellaData: blck) -template init*(T: type ForkedSignedBeaconBlock, forked: ForkedBeaconBlock, - blockRoot: Eth2Digest, signature: ValidatorSig): T = +func init*(T: type ForkedSignedBeaconBlock, forked: ForkedBeaconBlock, + blockRoot: Eth2Digest, signature: ValidatorSig): T = case forked.kind of BeaconBlockFork.Phase0: T(kind: BeaconBlockFork.Phase0, @@ -289,6 +301,29 @@ template init*(T: type ForkedSignedBeaconBlock, forked: ForkedBeaconBlock, root: blockRoot, signature: signature)) +func init*(T: type ForkedSignedBlindedBeaconBlock, + forked: ForkedBlindedBeaconBlock, blockRoot: Eth2Digest, + signature: ValidatorSig): T = + case forked.kind + of BeaconBlockFork.Phase0: + T(kind: BeaconBlockFork.Phase0, + phase0Data: phase0.SignedBeaconBlock(message: forked.phase0Data, + root: blockRoot, + signature: signature)) + of BeaconBlockFork.Altair: + T(kind: BeaconBlockFork.Altair, + altairData: altair.SignedBeaconBlock(message: forked.altairData, + root: blockRoot, + signature: signature)) + of BeaconBlockFork.Bellatrix: + T(kind: BeaconBlockFork.Bellatrix, + bellatrixData: SignedBlindedBeaconBlock(message: forked.bellatrixData, + signature: signature)) + of BeaconBlockFork.Capella: + T(kind: BeaconBlockFork.Capella, + capellaData: SignedBlindedBeaconBlock(message: forked.capellaData, + signature: signature)) + template init*(T: type ForkedMsgTrustedSignedBeaconBlock, blck: phase0.MsgTrustedSignedBeaconBlock): T = T(kind: BeaconBlockFork.Phase0, phase0Data: blck) template init*(T: type ForkedMsgTrustedSignedBeaconBlock, blck: altair.MsgTrustedSignedBeaconBlock): T = @@ -536,7 +571,8 @@ template asTrusted*( template withBlck*( x: ForkedBeaconBlock | Web3SignerForkedBeaconBlock | ForkedSignedBeaconBlock | ForkedMsgTrustedSignedBeaconBlock | - ForkedTrustedSignedBeaconBlock, + ForkedTrustedSignedBeaconBlock | ForkedBlindedBeaconBlock | + ForkedSignedBlindedBeaconBlock, body: untyped): untyped = case x.kind of BeaconBlockFork.Phase0: @@ -576,7 +612,8 @@ template getForkedBlockField*( of BeaconBlockFork.Capella: unsafeAddr x.capellaData.message.y)[] template signature*(x: ForkedSignedBeaconBlock | - ForkedMsgTrustedSignedBeaconBlock): ValidatorSig = + ForkedMsgTrustedSignedBeaconBlock | + ForkedSignedBlindedBeaconBlock): ValidatorSig = withBlck(x): blck.signature template signature*(x: ForkedTrustedSignedBeaconBlock): TrustedSig = @@ -592,12 +629,13 @@ template slot*(x: ForkedSignedBeaconBlock | ForkedTrustedSignedBeaconBlock): Slot = withBlck(x): blck.message.slot -template shortLog*(x: ForkedBeaconBlock): auto = +template shortLog*(x: ForkedBeaconBlock | ForkedBlindedBeaconBlock): auto = withBlck(x): shortLog(blck) template shortLog*(x: ForkedSignedBeaconBlock | ForkedMsgTrustedSignedBeaconBlock | - ForkedTrustedSignedBeaconBlock): auto = + ForkedTrustedSignedBeaconBlock | + ForkedSignedBlindedBeaconBlock): auto = withBlck(x): shortLog(blck) chronicles.formatIt ForkedBeaconBlock: it.shortLog diff --git a/beacon_chain/spec/mev/bellatrix_mev.nim b/beacon_chain/spec/mev/bellatrix_mev.nim index 3ab585fc8..2642cab57 100644 --- a/beacon_chain/spec/mev/bellatrix_mev.nim +++ b/beacon_chain/spec/mev/bellatrix_mev.nim @@ -6,6 +6,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import ".."/datatypes/[altair, bellatrix] +from stew/byteutils import to0xHex when (NimMajor, NimMinor) < (1, 4): {.push raises: [Defect].} @@ -76,6 +77,18 @@ func shortLog*(v: BlindedBeaconBlock): auto = proposer_index: v.proposer_index, parent_root: shortLog(v.parent_root), state_root: shortLog(v.state_root), + eth1data: v.body.eth1_data, + graffiti: $v.body.graffiti, + proposer_slashings_len: v.body.proposer_slashings.len(), + attester_slashings_len: v.body.attester_slashings.len(), + attestations_len: v.body.attestations.len(), + deposits_len: v.body.deposits.len(), + voluntary_exits_len: v.body.voluntary_exits.len(), + sync_committee_participants: + countOnes(v.body.sync_aggregate.sync_committee_bits), + block_number: v.body.execution_payload_header.block_number, + # TODO checksum hex? shortlog? + fee_recipient: to0xHex(v.body.execution_payload_header.fee_recipient.data), ) func shortLog*(v: SignedBlindedBeaconBlock): auto = diff --git a/beacon_chain/validator_client/api.nim b/beacon_chain/validator_client/api.nim index 5546dc7b2..71e8ebc34 100644 --- a/beacon_chain/validator_client/api.nim +++ b/beacon_chain/validator_client/api.nim @@ -1772,6 +1772,200 @@ proc publishBlock*( raise newException(ValidatorApiError, ErrorMessage) +proc produceBlindedBlock*( + vc: ValidatorClientRef, + slot: Slot, + randao_reveal: ValidatorSig, + graffiti: GraffitiBytes, + strategy: ApiStrategyKind + ): Future[ProduceBlindedBlockResponse] {.async.} = + logScope: + request = "produceBlindedBlock" + strategy = $strategy + + const ErrorMessage = "Unable to retrieve block data" + + case strategy + of ApiStrategyKind.First, ApiStrategyKind.Best: + let res = vc.firstSuccessParallel( + RestResponse[ProduceBlindedBlockResponse], + SlotDuration, {BeaconNodeRole.BlockProposalData}, + produceBlindedBlock(it, slot, randao_reveal, graffiti)): + if apiResponse.isErr(): + debug ErrorMessage, endpoint = node, error = apiResponse.error() + RestBeaconNodeStatus.Offline + else: + let response = apiResponse.get() + case response.status: + of 200: + trace ResponseSuccess, endpoint = node + RestBeaconNodeStatus.Online + of 400: + debug ResponseInvalidError, response_code = response.status, + endpoint = node + RestBeaconNodeStatus.Incompatible + of 500: + debug ResponseInternalError, response_code = response.status, + endpoint = node + RestBeaconNodeStatus.Offline + of 503: + debug ResponseNoSyncError, response_code = response.status, + endpoint = node + RestBeaconNodeStatus.NotSynced + else: + debug ResponseUnexpectedError, response_code = response.status, + endpoint = node + RestBeaconNodeStatus.Offline + if res.isErr(): + raise newException(ValidatorApiError, res.error()) + return res.get().data + + of ApiStrategyKind.Priority: + vc.firstSuccessSequential( + RestResponse[ProduceBlindedBlockResponse], + SlotDuration, {BeaconNodeRole.BlockProposalData}, + produceBlindedBlock(it, slot, randao_reveal, graffiti)): + if apiResponse.isErr(): + debug ErrorMessage, endpoint = node, error = apiResponse.error() + RestBeaconNodeStatus.Offline + else: + let response = apiResponse.get() + case response.status: + of 200: + trace ResponseSuccess, endpoint = node + return response.data + of 400: + debug ResponseInvalidError, response_code = response.status, + endpoint = node + RestBeaconNodeStatus.Incompatible + of 500: + debug ResponseInternalError, response_code = response.status, + endpoint = node + RestBeaconNodeStatus.Offline + of 503: + debug ResponseNoSyncError, response_code = response.status, + endpoint = node + RestBeaconNodeStatus.NotSynced + else: + debug ResponseUnexpectedError, response_code = response.status, + endpoint = node + RestBeaconNodeStatus.Offline + + raise newException(ValidatorApiError, ErrorMessage) + +proc publishBlindedBlock*( + vc: ValidatorClientRef, + data: ForkedSignedBlindedBeaconBlock, + strategy: ApiStrategyKind + ): Future[bool] {.async.} = + logScope: + request = "publishBlindedBlock" + strategy = $strategy + + const + BlockPublished = "Block was successfully published" + BlockBroadcasted = "Block not passed validation, but still published" + ErrorMessage = "Unable to publish block" + + case strategy + of ApiStrategyKind.First, ApiStrategyKind.Best: + let res = block: + vc.firstSuccessParallel(RestPlainResponse, SlotDuration, + {BeaconNodeRole.BlockProposalPublish}): + case data.kind + of BeaconBlockFork.Phase0: + publishBlindedBlock(it, data.phase0Data) + of BeaconBlockFork.Altair: + publishBlindedBlock(it, data.altairData) + of BeaconBlockFork.Bellatrix: + publishBlindedBlock(it, data.bellatrixData) + of BeaconBlockFork.Capella: + raiseAssert $capellaImplementationMissing + do: + if apiResponse.isErr(): + debug ErrorMessage, endpoint = node, error = apiResponse.error() + RestBeaconNodeStatus.Offline + else: + let response = apiResponse.get() + case response.status: + of 200: + trace BlockPublished, endpoint = node + RestBeaconNodeStatus.Online + of 202: + debug BlockBroadcasted, endpoint = node + RestBeaconNodeStatus.Online + of 400: + debug ResponseInvalidError, response_code = response.status, + endpoint = node, + response_error = response.getErrorMessage() + RestBeaconNodeStatus.Incompatible + of 500: + debug ResponseInternalError, response_code = response.status, + endpoint = node, + response_error = response.getErrorMessage() + RestBeaconNodeStatus.Offline + of 503: + debug ResponseNoSyncError, response_code = response.status, + endpoint = node, + response_error = response.getErrorMessage() + RestBeaconNodeStatus.NotSynced + else: + debug ResponseUnexpectedError, response_code = response.status, + endpoint = node, + response_error = response.getErrorMessage() + RestBeaconNodeStatus.Offline + if res.isErr(): + raise newException(ValidatorApiError, res.error()) + return true + + of ApiStrategyKind.Priority: + vc.firstSuccessSequential(RestPlainResponse, SlotDuration, + {BeaconNodeRole.BlockProposalPublish}): + case data.kind + of BeaconBlockFork.Phase0: + publishBlindedBlock(it, data.phase0Data) + of BeaconBlockFork.Altair: + publishBlindedBlock(it, data.altairData) + of BeaconBlockFork.Bellatrix: + publishBlindedBlock(it, data.bellatrixData) + of BeaconBlockFork.Capella: + raiseAssert $capellaImplementationMissing + do: + if apiResponse.isErr(): + debug ErrorMessage, endpoint = node, error = apiResponse.error() + RestBeaconNodeStatus.Offline + else: + let response = apiResponse.get() + case response.status: + of 200: + trace BlockPublished, endpoint = node + return true + of 202: + debug BlockBroadcasted, endpoint = node + return true + of 400: + debug ResponseInvalidError, response_code = response.status, + endpoint = node, + response_error = response.getErrorMessage() + RestBeaconNodeStatus.Incompatible + of 500: + debug ResponseInternalError, response_code = response.status, + endpoint = node, + response_error = response.getErrorMessage() + RestBeaconNodeStatus.Offline + of 503: + debug ResponseNoSyncError, response_code = response.status, + endpoint = node, + response_error = response.getErrorMessage() + RestBeaconNodeStatus.NotSynced + else: + debug ResponseUnexpectedError, response_code = response.status, + endpoint = node, + response_error = response.getErrorMessage() + RestBeaconNodeStatus.Offline + + raise newException(ValidatorApiError, ErrorMessage) + proc prepareBeaconCommitteeSubnet*( vc: ValidatorClientRef, data: seq[RestCommitteeSubscription], diff --git a/beacon_chain/validator_client/block_service.nim b/beacon_chain/validator_client/block_service.nim index 8bac01b40..b311b2c50 100644 --- a/beacon_chain/validator_client/block_service.nim +++ b/beacon_chain/validator_client/block_service.nim @@ -13,6 +13,82 @@ import logScope: service = "block_service" +type + PreparedBeaconBlock = object + blockRoot*: Eth2Digest + data*: ForkedBeaconBlock + + PreparedBlindedBeaconBlock = object + blockRoot*: Eth2Digest + data*: ForkedBlindedBeaconBlock + +proc produceBlock( + vc: ValidatorClientRef, + currentSlot, slot: Slot, + randao_reveal: ValidatorSig, + graffiti: GraffitiBytes, + validator: AttachedValidator + ): Future[Opt[PreparedBeaconBlock]] {.async.} = + logScope: + slot = slot + wall_slot = currentSlot + validator = shortLog(validator) + let + beaconBlock = + try: + await vc.produceBlockV2(slot, randao_reveal, graffiti, + ApiStrategyKind.Best) + except ValidatorApiError: + error "Unable to retrieve block data" + return Opt.none(PreparedBeaconBlock) + except CancelledError as exc: + error "Block data production has been interrupted" + raise exc + except CatchableError as exc: + error "An unexpected error occurred while getting block data", + error_name = exc.name, error_msg = exc.msg + return Opt.none(PreparedBeaconBlock) + blockRoot = withBlck(beaconBlock): hash_tree_root(blck) + + return Opt.some(PreparedBeaconBlock(blockRoot: blockRoot, data: beaconBlock)) + +proc produceBlindedBlock( + vc: ValidatorClientRef, + currentSlot, slot: Slot, + randao_reveal: ValidatorSig, + graffiti: GraffitiBytes, + validator: AttachedValidator + ): Future[Opt[PreparedBlindedBeaconBlock]] {.async.} = + logScope: + slot = slot + wall_slot = currentSlot + validator = shortLog(validator) + let + beaconBlock = + try: + await vc.produceBlindedBlock(slot, randao_reveal, graffiti, + ApiStrategyKind.Best) + except ValidatorApiError: + error "Unable to retrieve blinded block data" + return Opt.none(PreparedBlindedBeaconBlock) + except CancelledError as exc: + error "Blinded block data production has been interrupted" + raise exc + except CatchableError as exc: + error "An unexpected error occurred while getting blinded block data", + error_name = exc.name, error_msg = exc.msg + return Opt.none(PreparedBlindedBeaconBlock) + blockRoot = withBlck(beaconBlock): hash_tree_root(blck) + + return Opt.some( + PreparedBlindedBeaconBlock(blockRoot: blockRoot, data: beaconBlock)) + +proc lazyWait[T](fut: Future[T]) {.async.} = + try: + discard await fut + except CatchableError: + discard + proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot, validator: AttachedValidator) {.async.} = let @@ -25,123 +101,204 @@ proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot, fork = vc.forkAtEpoch(slot.epoch) vindex = validator.index.get() + logScope: + validator = shortLog(validator) + validator_index = vindex + slot = slot + wall_slot = currentSlot + if not(vc.doppelgangerCheck(validator)): - info "Block has not been produced (doppelganger check still active)", - slot = slot, validator = shortLog(validator), - validator_index = vindex + info "Block has not been produced (doppelganger check still active)" return - debug "Publishing block", validator = shortLog(validator), - delay = vc.getDelay(slot.block_deadline()), - wall_slot = currentSlot, + debug "Publishing block", delay = vc.getDelay(slot.block_deadline()), genesis_root = genesisRoot, - graffiti = graffiti, fork = fork, slot = slot, - wall_slot = currentSlot + graffiti = graffiti, fork = fork let randaoReveal = try: let res = await validator.getEpochSignature(fork, genesisRoot, slot.epoch) if res.isErr(): - error "Unable to generate randao reveal usint remote signer", - validator = shortLog(validator), error_msg = res.error() + error "Unable to generate randao reveal using remote signer", + error_msg = res.error() return res.get() except CancelledError as exc: - error "Randao reveal processing was interrupted" + error "Randao reveal production has been interrupted" raise exc except CatchableError as exc: error "An unexpected error occurred while receiving randao data", - err_name = exc.name, err_msg = exc.msg + error_name = exc.name, error_msg = exc.msg return - let beaconBlock = - try: - await vc.produceBlockV2(slot, randaoReveal, graffiti, - ApiStrategyKind.Best) - except ValidatorApiError: - error "Unable to retrieve block data", slot = slot, - wall_slot = currentSlot, validator = shortLog(validator) - return - except CancelledError as exc: - error "Producing block processing was interrupted" - raise exc - except CatchableError as exc: - error "An unexpected error occurred while getting block data", - err_name = exc.name, err_msg = exc.msg - return + var beaconBlocks = + block: + let blindedBlockFut = + if vc.config.payloadBuilderEnable: + vc.produceBlindedBlock(currentSlot, slot, randaoReveal, graffiti, + validator) + else: + nil + let normalBlockFut = vc.produceBlock(currentSlot, slot, randaoReveal, + graffiti, validator) + let blindedBlock = + if isNil(blindedBlockFut): + Opt.none(PreparedBlindedBeaconBlock) + else: + try: + await blindedBlockFut + except CancelledError as exc: + if not(normalBlockFut.finished()): + await normalBlockFut.cancelAndWait() + raise exc + except CatchableError as exc: + # This should not be happened, because all the exceptions handled. + Opt.none(PreparedBlindedBeaconBlock) - let blockRoot = withBlck(beaconBlock): hash_tree_root(blck) - # TODO: signingRoot is recomputed in getBlockSignature just after - let signingRoot = compute_block_signing_root(fork, genesisRoot, slot, - blockRoot) - let notSlashable = vc.attachedValidators[] - .slashingProtection - .registerBlock(vindex, validator.pubkey, slot, signingRoot) + let normalBlock = + if blindedBlock.isNone(): + try: + await normalBlockFut + except CancelledError as exc: + raise exc + except CatchableError as exc: + # This should not be happened, because all the exceptions handled. + Opt.none(PreparedBeaconBlock) + else: + if not(normalBlockFut.finished()): + asyncSpawn lazyWait(normalBlockFut) + Opt.none(PreparedBeaconBlock) - if notSlashable.isOk(): - let signature = - try: - let res = await validator.getBlockSignature(fork, genesisRoot, - slot, blockRoot, - beaconBlock) - if res.isErr(): - error "Unable to sign block proposal using remote signer", - validator = shortLog(validator), error_msg = res.error() - return - res.get() - except CancelledError as exc: - debug "Block signature processing was interrupted" - raise exc - except CatchableError as exc: - error "An unexpected error occurred while signing block", - err_name = exc.name, err_msg = exc.msg + if blindedBlock.isNone() and normalBlock.isNone(): return - debug "Sending block", - blockRoot = shortLog(blockRoot), blck = shortLog(beaconBlock), - signature = shortLog(signature), validator = shortLog(validator) + (blindedBlock: blindedBlock, normalBlock: normalBlock) - let res = - try: - let signedBlock = ForkedSignedBeaconBlock.init(beaconBlock, blockRoot, - signature) - await vc.publishBlock(signedBlock, ApiStrategyKind.First) - except ValidatorApiError: - error "Unable to publish block", - blockRoot = shortLog(blockRoot), - blck = shortLog(beaconBlock), - signature = shortLog(signature), - validator = shortLog(validator), - validator_index = validator.index.get(), - wall_slot = currentSlot - return - except CancelledError as exc: - debug "Publishing block processing was interrupted" - raise exc - except CatchableError as exc: - error "An unexpected error occurred while publishing block", - err_name = exc.name, err_msg = exc.msg - return - if res: - let delay = vc.getDelay(slot.block_deadline()) - beacon_blocks_sent.inc() - beacon_blocks_sent_delay.observe(delay.toFloatSeconds()) - notice "Block published", blockRoot = shortLog(blockRoot), - blck = shortLog(beaconBlock), signature = shortLog(signature), - validator = shortLog(validator) + if beaconBlocks.blindedBlock.isSome(): + let + preparedBlock = beaconBlocks.blindedBlock.get() + signingRoot = compute_block_signing_root(fork, genesisRoot, slot, + preparedBlock.blockRoot) + notSlashable = vc.attachedValidators[] + .slashingProtection + .registerBlock(vindex, validator.pubkey, slot, signingRoot) + + logScope: + blck = shortLog(preparedBlock.data) + block_root = shortLog(preparedBlock.blockRoot) + signing_root = shortLog(signingRoot) + + if notSlashable.isOk(): + let + signature = + try: + let res = await validator.getBlockSignature(fork, genesisRoot, + slot, + preparedBlock.blockRoot, + preparedBlock.data) + if res.isErr(): + error "Unable to sign blinded block proposal using remote signer", + error_msg = res.error() + return + res.get() + except CancelledError as exc: + debug "Blinded block signature process has been interrupted" + raise exc + except CatchableError as exc: + error "An unexpected error occurred while signing blinded block", + error_name = exc.name, error_msg = exc.msg + return + + logScope: + signature = shortLog(signature) + + let + signedBlock = ForkedSignedBlindedBeaconBlock.init(preparedBlock.data, + preparedBlock.blockRoot, signature) + res = + try: + debug "Sending blinded block" + await vc.publishBlindedBlock(signedBlock, ApiStrategyKind.First) + except ValidatorApiError: + error "Unable to publish blinded block" + return + except CancelledError as exc: + debug "Blinded block publication has been interrupted" + raise exc + except CatchableError as exc: + error "An unexpected error occurred while publishing blinded block", + error_name = exc.name, error_msg = exc.msg + return + + if res: + let delay = vc.getDelay(slot.block_deadline()) + beacon_blocks_sent.inc() + beacon_blocks_sent_delay.observe(delay.toFloatSeconds()) + notice "Blinded block published", delay = delay + else: + warn "Blinded block was not accepted by beacon node" else: - warn "Block was not accepted by beacon node", - blockRoot = shortLog(blockRoot), - blck = shortLog(beaconBlock), - signature = shortLog(signature), - validator = shortLog(validator), - wall_slot = currentSlot + warn "Slashing protection activated for blinded block proposal" else: - warn "Slashing protection activated for block proposal", - blockRoot = shortLog(blockRoot), blck = shortLog(beaconBlock), - signingRoot = shortLog(signingRoot), - validator = shortLog(validator), - wall_slot = currentSlot, - existingProposal = notSlashable.error + let + preparedBlock = beaconBlocks.normalBlock.get() + signingRoot = compute_block_signing_root(fork, genesisRoot, slot, + preparedBlock.blockRoot) + notSlashable = vc.attachedValidators[] + .slashingProtection + .registerBlock(vindex, validator.pubkey, slot, signingRoot) + + logScope: + blck = shortLog(preparedBlock.data) + block_root = shortLog(preparedBlock.blockRoot) + signing_root = shortLog(signingRoot) + + if notSlashable.isOk(): + let + signature = + try: + let res = await validator.getBlockSignature(fork, + genesisRoot, slot, + preparedBlock.blockRoot, + preparedBlock.data) + if res.isErr(): + error "Unable to sign block proposal using remote signer", + error_msg = res.error() + return + res.get() + except CancelledError as exc: + debug "Block signature process has been interrupted" + raise exc + except CatchableError as exc: + error "An unexpected error occurred while signing block", + error_name = exc.name, error_msg = exc.msg + return + signedBlock = ForkedSignedBeaconBlock.init(preparedBlock.data, + preparedBlock.blockRoot, + signature) + res = + try: + debug "Sending block" + await vc.publishBlock(signedBlock, ApiStrategyKind.First) + except ValidatorApiError: + error "Unable to publish block" + return + except CancelledError as exc: + debug "Block publication has been interrupted" + raise exc + except CatchableError as exc: + error "An unexpected error occurred while publishing block", + error_name = exc.name, error_msg = exc.msg + return + + if res: + let delay = vc.getDelay(slot.block_deadline()) + beacon_blocks_sent.inc() + beacon_blocks_sent_delay.observe(delay.toFloatSeconds()) + notice "Block published", delay = delay + else: + warn "Block was not accepted by beacon node" + else: + warn "Slashing protection activated for block proposal" proc proposeBlock(vc: ValidatorClientRef, slot: Slot, proposerKey: ValidatorPubKey) {.async.} = @@ -291,4 +448,3 @@ proc waitForBlockPublished*(vc: ValidatorClientRef, slot: Slot) {.async.} = pending.add(future.cancelAndWait()) await allFutures(pending) raise exc - diff --git a/beacon_chain/validators/validator_pool.nim b/beacon_chain/validators/validator_pool.nim index d703aa64c..5948e03a4 100644 --- a/beacon_chain/validators/validator_pool.nim +++ b/beacon_chain/validators/validator_pool.nim @@ -326,7 +326,8 @@ proc signData(v: AttachedValidator, proc getBlockSignature*(v: AttachedValidator, fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot, block_root: Eth2Digest, - blck: ForkedBeaconBlock | BlindedBeaconBlock + blck: ForkedBeaconBlock | ForkedBlindedBeaconBlock | + BlindedBeaconBlock ): Future[SignatureResult] {.async.} = return case v.kind @@ -336,7 +337,29 @@ proc getBlockSignature*(v: AttachedValidator, fork: Fork, fork, genesis_validators_root, slot, block_root, v.data.privateKey).toValidatorSig()) of ValidatorKind.Remote: - when blck is BlindedBeaconBlock: + when blck is ForkedBlindedBeaconBlock: + let + web3SignerBlock = + case blck.kind + of BeaconBlockFork.Phase0: + Web3SignerForkedBeaconBlock( + kind: BeaconBlockFork.Phase0, + phase0Data: blck.phase0Data) + of BeaconBlockFork.Altair: + Web3SignerForkedBeaconBlock( + kind: BeaconBlockFork.Altair, + altairData: blck.altairData) + of BeaconBlockFork.Bellatrix: + Web3SignerForkedBeaconBlock( + kind: BeaconBlockFork.Bellatrix, + bellatrixData: blck.bellatrixData.toBeaconBlockHeader) + of BeaconBlockFork.Capella: + raiseAssert $capellaImplementationMissing + + request = Web3SignerRequest.init( + fork, genesis_validators_root, web3SignerBlock) + await v.signData(request) + elif blck is BlindedBeaconBlock: let request = Web3SignerRequest.init( fork, genesis_validators_root, Web3SignerForkedBeaconBlock( @@ -519,4 +542,3 @@ proc getBuilderSignature*(v: AttachedValidator, fork: Fork, let request = Web3SignerRequest.init( fork, ZERO_HASH, validatorRegistration) await v.signData(request) -