diff --git a/Makefile b/Makefile index 6d3508f4bc..74a170a865 100644 --- a/Makefile +++ b/Makefile @@ -236,6 +236,7 @@ local-testnet-minimal: --remote-validators-count 512 \ --signer-type $(SIGNER_TYPE) \ --deneb-fork-epoch 0 \ + --electra-fork-epoch 5 \ --stop-at-epoch 6 \ --disable-htop \ --enable-payload-builder \ @@ -265,6 +266,7 @@ local-testnet-mainnet: --data-dir $@ \ --nodes 2 \ --deneb-fork-epoch 0 \ + --electra-fork-epoch 5 \ --stop-at-epoch 6 \ --disable-htop \ --enable-logtrace \ diff --git a/beacon_chain/deposits.nim b/beacon_chain/deposits.nim index 5eaf2973cb..bf822ee509 100644 --- a/beacon_chain/deposits.nim +++ b/beacon_chain/deposits.nim @@ -236,11 +236,21 @@ proc restValidatorExit(config: BeaconNodeConf) {.async.} = let forkConfig = response.data.data.getConsensusForkConfig() if forkConfig.isErr: raise newException(RestError, "Invalid config: " & forkConfig.error) - let capellaForkVersion = forkConfig.get.capellaVersion.valueOr: - raise newException(RestError, - ConsensusFork.Capella.forkVersionConfigKey() & " missing") + let + capellaForkVersion = + try: + forkConfig.get()[ConsensusFork.Capella].version + except KeyError: + raise newException(RestError, + ConsensusFork.Capella.forkVersionConfigKey() & " missing") + denebForkEpoch = + try: + forkConfig.get()[ConsensusFork.Deneb].epoch + except KeyError: + raise newException(RestError, + ConsensusFork.Deneb.forkEpochConfigKey() & " missing") voluntary_exit_signature_fork( - fork, capellaForkVersion, currentEpoch, forkConfig.get.denebEpoch) + fork, capellaForkVersion, currentEpoch, denebForkEpoch) else: raise newException(RestError, "Error response (" & $response.status & ")") except CatchableError as exc: diff --git a/beacon_chain/nimbus_binary_common.nim b/beacon_chain/nimbus_binary_common.nim index 6c896118b2..a5acbd1596 100644 --- a/beacon_chain/nimbus_binary_common.nim +++ b/beacon_chain/nimbus_binary_common.nim @@ -279,6 +279,11 @@ proc sleepAsync*(t: TimeDiff): Future[void] = sleepAsync(nanoseconds( if t.nanoseconds < 0: 0'i64 else: t.nanoseconds)) +proc sleepAsync2*(t: TimeDiff): Future[void] {. + async: (raises: [CancelledError], raw: true).} = + sleepAsync(nanoseconds( + if t.nanoseconds < 0: 0'i64 else: t.nanoseconds)) + proc runSlotLoop*[T](node: T, startTime: BeaconTime, slotProc: SlotStartProc[T]) {.async.} = var diff --git a/beacon_chain/nimbus_validator_client.nim b/beacon_chain/nimbus_validator_client.nim index 35a5c9f7b0..73533ba130 100644 --- a/beacon_chain/nimbus_validator_client.nim +++ b/beacon_chain/nimbus_validator_client.nim @@ -21,7 +21,8 @@ declareGauge validator_client_node_counts, "Number of connected beacon nodes and their status", labels = ["status"] -proc initGenesis(vc: ValidatorClientRef): Future[RestGenesis] {.async.} = +proc initGenesis(vc: ValidatorClientRef): Future[RestGenesis] {. + async: (raises: [CancelledError]).} = info "Initializing genesis", nodes_count = len(vc.beaconNodes) var nodes = vc.beaconNodes while true: @@ -61,7 +62,7 @@ proc initGenesis(vc: ValidatorClientRef): Future[RestGenesis] {.async.} = for i in 0 ..< len(pendingRequests): let fut = pendingRequests[i] if fut.completed(): - let resp = fut.read() + let resp = fut.value if resp.status == 200: debug "Received genesis information", endpoint = nodes[i], genesis_time = resp.data.data.genesis_time, @@ -73,10 +74,10 @@ proc initGenesis(vc: ValidatorClientRef): Future[RestGenesis] {.async.} = response_code = resp.status bres.add(nodes[i]) elif fut.failed(): - let error = fut.readError() + let error = fut.error debug "Could not obtain genesis information from beacon node", endpoint = nodes[i], error_name = error.name, - error_msg = error.msg + reason = error.msg bres.add(nodes[i]) else: debug "Interrupted while requesting information from beacon node", @@ -105,14 +106,19 @@ proc initGenesis(vc: ValidatorClientRef): Future[RestGenesis] {.async.} = dec(counter) return melem -proc addValidatorsFromWeb3Signer(vc: ValidatorClientRef, web3signerUrl: Web3SignerUrl) {.async.} = +proc addValidatorsFromWeb3Signer( + vc: ValidatorClientRef, + web3signerUrl: Web3SignerUrl +) {.async: (raises: [CancelledError]).} = let res = await queryValidatorsSource(web3signerUrl) if res.isOk(): let dynamicKeystores = res.get() for keystore in dynamicKeystores: vc.addValidator(keystore) -proc initValidators(vc: ValidatorClientRef): Future[bool] {.async.} = +proc initValidators( + vc: ValidatorClientRef +): Future[bool] {.async: (raises: [CancelledError]).} = info "Loading validators", validatorsDir = vc.config.validatorsDir() for keystore in listLoadableKeystores(vc.config, vc.keystoreCache): vc.addValidator(keystore) @@ -129,7 +135,10 @@ proc initValidators(vc: ValidatorClientRef): Future[bool] {.async.} = true -proc initClock(vc: ValidatorClientRef): Future[BeaconClock] {.async.} = +proc initClock( + vc: ValidatorClientRef +): Future[BeaconClock] {. + async: (raises: [CancelledError, ValidatorClientError]).} = # This procedure performs initialization of BeaconClock using current genesis # information. It also performs waiting for genesis. let @@ -150,9 +159,11 @@ proc initClock(vc: ValidatorClientRef): Future[BeaconClock] {.async.} = info "Initializing beacon clock", genesis_time = vc.beaconGenesis.genesis_time, current_slot = currentSlot, current_epoch = currentEpoch - return res + res -proc initMetrics(vc: ValidatorClientRef): Future[bool] {.async.} = +proc initMetrics( + vc: ValidatorClientRef +): Future[bool] {.async: (raises: [CancelledError]).} = if vc.config.metricsEnabled: let metricsAddress = vc.config.metricsAddress @@ -174,9 +185,9 @@ proc initMetrics(vc: ValidatorClientRef): Future[bool] {.async.} = error "Could not start metrics HTTP server", url = url, error_msg = exc.msg, error_name = exc.name return false - return true + true -proc shutdownMetrics(vc: ValidatorClientRef) {.async.} = +proc shutdownMetrics(vc: ValidatorClientRef) {.async: (raises: []).} = if vc.config.metricsEnabled: if vc.metricsServer.isSome(): info "Shutting down metrics HTTP server" @@ -186,7 +197,8 @@ proc shutdownSlashingProtection(vc: ValidatorClientRef) = info "Closing slashing protection", path = vc.config.validatorsDir() vc.attachedValidators[].slashingProtection.close() -proc runVCSlotLoop(vc: ValidatorClientRef) {.async.} = +proc runVCSlotLoop( + vc: ValidatorClientRef) {.async: (raises: [CancelledError]).} = var startTime = vc.beaconClock.now() curSlot = startTime.slotOrZero() @@ -255,9 +267,11 @@ proc runVCSlotLoop(vc: ValidatorClientRef) {.async.} = node_status = $vc.beaconNodes[0].status, delay = shortLog(delay) -proc new*(T: type ValidatorClientRef, - config: ValidatorClientConf, - rng: ref HmacDrbgContext): ValidatorClientRef = +proc new*( + T: type ValidatorClientRef, + config: ValidatorClientConf, + rng: ref HmacDrbgContext +): ValidatorClientRef = let beaconNodes = block: var servers: seq[BeaconNodeServerRef] @@ -317,7 +331,8 @@ proc new*(T: type ValidatorClientRef, keystoreCache: KeystoreCacheRef.init() ) -proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {.async.} = +proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {. + async: (raises: [CancelledError, ValidatorClientError]).} = notice "Launching validator client", version = fullVersionStr, cmdParams = commandLineParams(), config = vc.config, @@ -361,15 +376,21 @@ proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {.async.} = keymanagerInitResult = initKeymanagerServer(vc.config, nil) func getCapellaForkVersion(): Opt[Version] = - if vc.runtimeConfig.forkConfig.isSome(): - vc.runtimeConfig.forkConfig.get().capellaVersion - else: + if vc.forkConfig.isNone(): + return Opt.none(Version) + + try: + Opt.some(vc.forkConfig.get()[ConsensusFork.Capella].version) + except KeyError: Opt.none(Version) func getDenebForkEpoch(): Opt[Epoch] = - if vc.runtimeConfig.forkConfig.isSome(): - Opt.some(vc.runtimeConfig.forkConfig.get().denebEpoch) - else: + if vc.forkConfig.isNone(): + return Opt.none(Epoch) + + try: + Opt.some(vc.forkConfig.get()[ConsensusFork.Deneb].epoch) + except KeyError: Opt.none(Epoch) proc getForkForEpoch(epoch: Epoch): Opt[Fork] = @@ -409,12 +430,6 @@ proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {.async.} = getForkForEpoch, getGenesisRoot ) - - except CatchableError as exc: - warn "Unexpected error encountered while initializing", - error_name = exc.name, error_msg = exc.msg - await vc.shutdownMetrics() - vc.shutdownSlashingProtection() except CancelledError: debug "Initialization process interrupted" await vc.shutdownMetrics() @@ -423,7 +438,9 @@ proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {.async.} = return vc -proc runPreGenesisWaitingLoop(vc: ValidatorClientRef) {.async.} = +proc runPreGenesisWaitingLoop( + vc: ValidatorClientRef +) {.async: (raises: [CancelledError]).} = var breakLoop = false while not(breakLoop): let @@ -444,15 +461,13 @@ proc runPreGenesisWaitingLoop(vc: ValidatorClientRef) {.async.} = except CancelledError as exc: debug "Pre-genesis waiting loop was interrupted" raise exc - except CatchableError as exc: - error "Pre-genesis waiting loop failed with unexpected error", - err_name = $exc.name, err_msg = $exc.msg - true if not(breakLoop): vc.preGenesisEvent.fire() -proc runGenesisWaitingLoop(vc: ValidatorClientRef) {.async.} = +proc runGenesisWaitingLoop( + vc: ValidatorClientRef +) {.async: (raises: [CancelledError]).} = var breakLoop = false while not(breakLoop): let genesisTime = vc.beaconClock.fromNow(Slot(0)) @@ -471,15 +486,13 @@ proc runGenesisWaitingLoop(vc: ValidatorClientRef) {.async.} = except CancelledError as exc: debug "Genesis waiting loop was interrupted" raise exc - except CatchableError as exc: - error "Genesis waiting loop failed with unexpected error", - err_name = $exc.name, err_msg = $exc.msg - true if not(breakLoop): vc.genesisEvent.fire() -proc asyncRun*(vc: ValidatorClientRef) {.async.} = +proc asyncRun*( + vc: ValidatorClientRef +) {.async: (raises: [ValidatorClientError]).} = vc.fallbackService.start() vc.forkService.start() vc.dutiesService.start() @@ -508,9 +521,6 @@ proc asyncRun*(vc: ValidatorClientRef) {.async.} = notice "Received shutdown event, exiting" except CancelledError: debug "Main loop interrupted" - except CatchableError as exc: - debug "Main loop failed with an error", err_name = $exc.name, - err_msg = $exc.msg await vc.shutdownMetrics() vc.shutdownSlashingProtection() @@ -539,14 +549,19 @@ proc asyncRun*(vc: ValidatorClientRef) {.async.} = pending.add(vc.syncCommitteeService.stop()) if not isNil(vc.keymanagerServer): pending.add(vc.keymanagerServer.stop()) - await allFutures(pending) + await noCancel allFutures(pending) template runWithSignals(vc: ValidatorClientRef, body: untyped): bool = let future = body - discard await race(future, vc.sigintHandleFut, vc.sigtermHandleFut) + + try: + discard await race(future, vc.sigintHandleFut, vc.sigtermHandleFut) + except CancelledError: + discard + if future.finished(): if future.failed() or future.cancelled(): - let exc = future.readError() + let exc = future.error error "Validator client initialization failed", err_name = $exc.name, err_msg = $exc.msg var pending: seq[Future[void]] @@ -554,7 +569,7 @@ template runWithSignals(vc: ValidatorClientRef, body: untyped): bool = pending.add(cancelAndWait(vc.sigintHandleFut)) if not(vc.sigtermHandleFut.finished()): pending.add(cancelAndWait(vc.sigtermHandleFut)) - await allFutures(pending) + await noCancel allFutures(pending) false else: true @@ -566,11 +581,13 @@ template runWithSignals(vc: ValidatorClientRef, body: untyped): bool = pending.add(cancelAndWait(vc.sigintHandleFut)) if not(vc.sigtermHandleFut.finished()): pending.add(cancelAndWait(vc.sigtermHandleFut)) - await allFutures(pending) + await noCancel allFutures(pending) false -proc runValidatorClient*(config: ValidatorClientConf, - rng: ref HmacDrbgContext) {.async.} = +proc runValidatorClient*( + config: ValidatorClientConf, + rng: ref HmacDrbgContext +) {.async: (raises: []).} = let vc = ValidatorClientRef.new(config, rng) if not vc.runWithSignals(asyncInit vc): return diff --git a/beacon_chain/rpc/rest_validator_api.nim b/beacon_chain/rpc/rest_validator_api.nim index a75a99938b..7763f0509a 100644 --- a/beacon_chain/rpc/rest_validator_api.nim +++ b/beacon_chain/rpc/rest_validator_api.nim @@ -840,21 +840,27 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http400, InvalidAttestationDataRootValueError, $res.error()) res.get() - let phase0_attestations = - node.attestationPool[].getPhase0AggregatedAttestation(qslot, root) - if phase0_attestations.isSome(): - return RestApiResponse.jsonResponse(phase0_attestations.get()) - - let electra_attestations = - node.attestationPool[].getElectraAggregatedAttestation(qslot, - root, - committee_index) - - if electra_attestations.isSome(): - return RestApiResponse.jsonResponse(electra_attestations.get()) - - RestApiResponse.jsonError(Http400, UnableToGetAggregatedAttestationError) + let + qfork = node.dag.cfg.consensusForkAtEpoch(qslot.epoch) + forked = + if qfork >= ConsensusFork.Electra: + let electra_attestation = + node.attestationPool[].getElectraAggregatedAttestation( + qslot, root, committee_index).valueOr: + return RestApiResponse.jsonError(Http404, + UnableToGetAggregatedAttestationError) + ForkedAttestation.init(electra_attestation, qfork) + else: + let phase0_attestation = + node.attestationPool[].getPhase0AggregatedAttestation( + qslot, root).valueOr: + return RestApiResponse.jsonError(Http404, + UnableToGetAggregatedAttestationError) + ForkedAttestation.init(phase0_attestation, qfork) + + let headers = HttpTable.init([("eth-consensus-version", qfork.toString())]) + RestApiResponse.jsonResponsePlain(forked, headers) # https://ethereum.github.io/beacon-APIs/#/Validator/publishAggregateAndProofs router.api2(MethodPost, "/eth/v1/validator/aggregate_and_proofs") do ( diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index de19b6c921..b68bda78b8 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -361,6 +361,7 @@ type EncodeArrays* = seq[phase0.Attestation] | + seq[electra.Attestation] | seq[PrepareBeaconProposer] | seq[RemoteKeystoreInfo] | seq[RestCommitteeSubscription] | @@ -368,6 +369,7 @@ type seq[RestSyncCommitteeMessage] | seq[RestSyncCommitteeSubscription] | seq[phase0.SignedAggregateAndProof] | + seq[electra.SignedAggregateAndProof] | seq[SignedValidatorRegistrationV1] | seq[ValidatorIndex] | seq[RestBeaconCommitteeSelection] | @@ -403,7 +405,8 @@ type seq[SomeForkedLightClientObject] | RestNimbusTimestamp1 | RestNimbusTimestamp2 | - GetGraffitiResponse + GetGraffitiResponse | + GetAggregatedAttestationV2Response DecodeConsensysTypes* = ProduceBlindedBlockResponse @@ -3952,3 +3955,69 @@ proc writeValue*(writer: var JsonWriter[RestJson], writer.writeField("validator_index", value.validator_index) writer.writeField("reward", value.reward) writer.endRecord() + +## ForkedAttestation +proc readValue*(reader: var JsonReader[RestJson], + value: var ForkedAttestation) {. + raises: [IOError, SerializationError].} = + var + version: Opt[ConsensusFork] + data: Opt[JsonString] + + for fieldName {.inject.} in readObjectFields(reader): + case fieldName + of "version": + if version.isSome(): + reader.raiseUnexpectedField("Multiple version fields found", + "ForkedAttestation") + let vres = reader.readValue(string).toLowerAscii() + version = ConsensusFork.init(vres) + if version.isNone(): + reader.raiseUnexpectedValue("Incorrect version field value") + of "data": + if data.isSome(): + reader.raiseUnexpectedField( + "Multiple '" & fieldName & "' fields found", "ForkedAttestation") + data = Opt.some(reader.readValue(JsonString)) + else: + unrecognizedFieldWarning(fieldName, "ForkedAttestation") + + if version.isNone(): + reader.raiseUnexpectedValue("Field `version` is missing") + if data.isNone(): + reader.raiseUnexpectedValue("Field `data` is missing") + + withConsensusFork(version.get()): + when consensusFork < ConsensusFork.Electra: + let res = + try: + RestJson.decode(string(data.get()), + phase0.Attestation, + requireAllFields = true, + allowUnknownFields = true) + except SerializationError as exc: + reader.raiseUnexpectedValue( + "Incorrect phase0 attestation format, [" & + exc.formatMsg("ForkedAttestation") & "]") + value = ForkedAttestation.init(res, consensusFork) + else: + let res = + try: + RestJson.decode(string(data.get()), + electra.Attestation, + requireAllFields = true, + allowUnknownFields = true) + except SerializationError as exc: + reader.raiseUnexpectedValue( + "Incorrect electra attestation format, [" & + exc.formatMsg("ForkedAttestation") & "]") + value = ForkedAttestation.init(res, consensusFork) + +## ForkedAttestation +proc writeValue*(writer: var JsonWriter[RestJson], + attestation: ForkedAttestation) {.raises: [IOError].} = + writer.beginRecord() + writer.writeField("version", attestation.kind) + withAttestation(attestation): + writer.writeField("data", forkyAttestation) + writer.endRecord() diff --git a/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim b/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim index 4acb4b41a6..20122f63af 100644 --- a/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim @@ -19,6 +19,15 @@ from ".."/datatypes/capella import SignedBeaconBlock export chronos, client, rest_types, eth2_rest_serialization +type + ForkySignedBlockContents* = + phase0.SignedBeaconBlock | + altair.SignedBeaconBlock | + bellatrix.SignedBeaconBlock | + capella.SignedBeaconBlock | + DenebSignedBlockContents | + ElectraSignedBlockContents + proc getGenesis*(): RestResponse[GetGenesisResponse] {. rest, endpoint: "/eth/v1/beacon/genesis", meth: MethodGet.} @@ -97,9 +106,10 @@ proc getBlockHeaderPlain*(block_id: BlockIdent): RestPlainResponse {. ## https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeader proc getBlockHeader*( - client: RestClientRef, - block_id: BlockIdent - ): Future[Opt[GetBlockHeaderResponse]] {.async.} = + client: RestClientRef, + block_id: BlockIdent +): Future[Opt[GetBlockHeaderResponse]] {. + async: (raises: [CancelledError, RestError, RestResponseError]).} = ## https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeader let resp = await client.getBlockHeaderPlain(block_id) return @@ -165,44 +175,86 @@ proc publishSszBlock*( extraHeaders = @[("eth-consensus-version", consensus)]) return resp -proc publishBlockV2Plain(body: phase0.SignedBeaconBlock): RestPlainResponse {. - rest, endpoint: "/eth/v2/beacon/blocks", - meth: MethodPost.} +proc publishBlockV2( + broadcast_validation: Option[BroadcastValidationType], + body: phase0.SignedBeaconBlock +): RestPlainResponse {.rest, endpoint: "/eth/v2/beacon/blocks", + meth: MethodPost.} ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlockV2 -proc publishBlockV2Plain(body: altair.SignedBeaconBlock): RestPlainResponse {. - rest, endpoint: "/eth/v2/beacon/blocks", - meth: MethodPost.} +proc publishBlockV2( + broadcast_validation: Option[BroadcastValidationType], + body: altair.SignedBeaconBlock +): RestPlainResponse {.rest, endpoint: "/eth/v2/beacon/blocks", + meth: MethodPost.} ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlockV2 -proc publishBlockV2Plain(body: bellatrix.SignedBeaconBlock): RestPlainResponse {. - rest, endpoint: "/eth/v2/beacon/blocks", - meth: MethodPost.} +proc publishBlockV2( + broadcast_validation: Option[BroadcastValidationType], + body: bellatrix.SignedBeaconBlock +): RestPlainResponse {.rest, endpoint: "/eth/v2/beacon/blocks", + meth: MethodPost.} ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlockV2 -proc publishBlockV2Plain(body: capella.SignedBeaconBlock): RestPlainResponse {. - rest, endpoint: "/eth/v2/beacon/blocks", - meth: MethodPost.} +proc publishBlockV2( + broadcast_validation: Option[BroadcastValidationType], + body: capella.SignedBeaconBlock +): RestPlainResponse {.rest, endpoint: "/eth/v2/beacon/blocks", + meth: MethodPost.} ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlockV2 -proc publishBlockV2Plain(body: DenebSignedBlockContents): RestPlainResponse {. - rest, endpoint: "/eth/v2/beacon/blocks", - meth: MethodPost.} +proc publishBlockV2( + broadcast_validation: Option[BroadcastValidationType], + body: DenebSignedBlockContents +): RestPlainResponse {.rest, endpoint: "/eth/v2/beacon/blocks", + meth: MethodPost.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlockV2 + +proc publishBlockV2( + broadcast_validation: Option[BroadcastValidationType], + body: ElectraSignedBlockContents +): RestPlainResponse {.rest, endpoint: "/eth/v2/beacon/blocks", + meth: MethodPost.} ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlockV2 proc publishBlockV2*( - client: RestClientRef, - blck: phase0.SignedBeaconBlock | altair.SignedBeaconBlock | - bellatrix.SignedBeaconBlock | capella.SignedBeaconBlock | - deneb.SignedBeaconBlock - ): Future[RestPlainResponse] {.async} = - let - consensus = typeof(blck).kind.toString() - resp = await client.publishBlockV2Plain( - blck, extraHeaders = @[ - ("eth-consensus-version", consensus), - ("broadcast_validation", "gossip")]) - return resp + client: RestClientRef, + broadcast_validation: Option[BroadcastValidationType], + blck: ForkySignedBlockContents +): Future[RestPlainResponse] {. + async: (raises: [CancelledError, RestEncodingError, RestDnsResolveError, + RestCommunicationError], raw: true).} = + let consensus = + when blck is DenebSignedBlockContents: + ConsensusFork.Deneb.toString() + elif blck is ElectraSignedBlockContents: + ConsensusFork.Electra.toString() + else: + typeof(blck).kind.toString() + client.publishBlockV2( + broadcast_validation, + blck, + extraHeaders = @[("eth-consensus-version", consensus)]) + +proc publishSszBlockV2*( + client: RestClientRef, + broadcast_validation: Option[BroadcastValidationType], + blck: ForkySignedBlockContents +): Future[RestPlainResponse] {. + async: (raises: [CancelledError, RestEncodingError, RestDnsResolveError, + RestCommunicationError], raw: true).} = + let consensus = + when blck is DenebSignedBlockContents: + ConsensusFork.Deneb.toString() + elif blck is ElectraSignedBlockContents: + ConsensusFork.Electra.toString() + else: + typeof(blck).kind.toString() + client.publishBlockV2( + broadcast_validation, + blck, + restContentType = $OctetStreamMediaType, + extraHeaders = @[("eth-consensus-version", consensus)]) proc publishBlindedBlock*(body: phase0.SignedBeaconBlock): RestPlainResponse {. rest, endpoint: "/eth/v1/beacon/blinded_blocks", @@ -239,16 +291,85 @@ proc publishBlindedBlock*(body: electra_mev.SignedBlindedBeaconBlock): ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock proc publishSszBlindedBlock*( - client: RestClientRef, - blck: ForkySignedBeaconBlock - ): Future[RestPlainResponse] {.async.} = + client: RestClientRef, + blck: ForkySignedBeaconBlock +): Future[RestPlainResponse] {. + async: (raises: [CancelledError, RestEncodingError, RestDnsResolveError, + RestCommunicationError], raw: true).} = + ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock + let consensus = typeof(blck).kind.toString() + client.publishBlindedBlock( + blck, restContentType = $OctetStreamMediaType, + extraHeaders = @[("eth-consensus-version", consensus)]) + +proc publishBlindedBlockV2*( + broadcast_validation: Option[BroadcastValidationType], + body: phase0.SignedBeaconBlock +): RestPlainResponse {.rest, endpoint: "/eth/v2/beacon/blinded_blocks", + meth: MethodPost.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock + +proc publishBlindedBlockV2*( + broadcast_validation: Option[BroadcastValidationType], + body: altair.SignedBeaconBlock +): RestPlainResponse {.rest, endpoint: "/eth/v2/beacon/blinded_blocks", + meth: MethodPost.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock + +proc publishBlindedBlockV2*( + broadcast_validation: Option[BroadcastValidationType], + body: bellatrix_mev.SignedBlindedBeaconBlock +): RestPlainResponse {.rest, endpoint: "/eth/v2/beacon/blinded_blocks", + meth: MethodPost.} ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock - let - consensus = typeof(blck).kind.toString() - resp = await client.publishBlindedBlock( - blck, restContentType = $OctetStreamMediaType, - extraHeaders = @[("eth-consensus-version", consensus)]) - return resp + +proc publishBlindedBlockV2*( + broadcast_validation: Option[BroadcastValidationType], + body: capella_mev.SignedBlindedBeaconBlock +): RestPlainResponse {.rest, endpoint: "/eth/v2/beacon/blinded_blocks", + meth: MethodPost.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock + +proc publishBlindedBlockV2*( + broadcast_validation: Option[BroadcastValidationType], + body: deneb_mev.SignedBlindedBeaconBlock +): RestPlainResponse {.rest, endpoint: "/eth/v2/beacon/blinded_blocks", + meth: MethodPost.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock + +proc publishBlindedBlockV2*( + broadcast_validation: Option[BroadcastValidationType], + body: electra_mev.SignedBlindedBeaconBlock +): RestPlainResponse {.rest, endpoint: "/eth/v2/beacon/blinded_blocks", + meth: MethodPost.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlindedBlock + +proc publishBlindedBlockV2*( + client: RestClientRef, + broadcast_validation: Option[BroadcastValidationType], + blck: ForkySignedBlindedBeaconBlock +): Future[RestPlainResponse] {. + async: (raises: [CancelledError, RestEncodingError, RestDnsResolveError, + RestCommunicationError], raw: true).} = + let consensus = typeof(blck).kind.toString() + client.publishBlindedBlockV2( + broadcast_validation, + blck, + extraHeaders = @[("eth-consensus-version", consensus)]) + +proc publishSszBlindedBlockV2*( + client: RestClientRef, + broadcast_validation: Option[BroadcastValidationType], + blck: ForkySignedBlindedBeaconBlock +): Future[RestPlainResponse] {. + async: (raises: [CancelledError, RestEncodingError, RestDnsResolveError, + RestCommunicationError], raw: true).} = + let consensus = typeof(blck).kind.toString() + client.publishBlindedBlockV2( + broadcast_validation, + blck, + restContentType = $OctetStreamMediaType, + extraHeaders = @[("eth-consensus-version", consensus)]) proc getBlockV2Plain*(block_id: BlockIdent): RestPlainResponse {. rest, endpoint: "/eth/v2/beacon/blocks/{block_id}", @@ -341,13 +462,23 @@ proc submitPoolAttestations*(body: seq[phase0.Attestation]): meth: MethodPost.} ## https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolAttestations -proc submitPoolAttestationsV2*( - body: seq[phase0.Attestation] | seq[electra.Attestation]): - RestPlainResponse {. +proc submitPoolAttestationsV2Plain*( + body: seq[ForkyAttestation] +): RestPlainResponse {. rest, endpoint: "/eth/v2/beacon/pool/attestations", meth: MethodPost.} ## https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/submitPoolAttestationsV2 +proc submitPoolAttestationsV2*[T: ForkyAttestation]( + client: RestClientRef, + body: seq[T] +): Future[RestPlainResponse] {. + async: (raises: [CancelledError, RestEncodingError, RestDnsResolveError, + RestCommunicationError], raw: true).} = + let consensus = T.kind.toString() + client.submitPoolAttestationsV2Plain( + body, extraHeaders = @[("eth-consensus-version", consensus)]) + proc getPoolAttesterSlashings*(): RestResponse[GetPoolAttesterSlashingsResponse] {. rest, endpoint: "/eth/v1/beacon/pool/attester_slashings", meth: MethodGet.} diff --git a/beacon_chain/spec/eth2_apis/rest_fork_config.nim b/beacon_chain/spec/eth2_apis/rest_fork_config.nim index dcfc5d62d7..c569586214 100644 --- a/beacon_chain/spec/eth2_apis/rest_fork_config.nim +++ b/beacon_chain/spec/eth2_apis/rest_fork_config.nim @@ -8,19 +8,20 @@ {.push raises: [].} import - std/strutils, + std/[sequtils, strutils, tables], stew/[base10, byteutils], ../forks from ./rest_types import VCRuntimeConfig -export forks, rest_types +export forks, tables, rest_types -type VCForkConfig* = object - altairEpoch*: Epoch - capellaVersion*: Opt[Version] - capellaEpoch*: Epoch - denebEpoch*: Epoch +type + ForkConfigItem* = object + version*: Version + epoch*: Epoch + + VCForkConfig* = Table[ConsensusFork, ForkConfigItem] func forkVersionConfigKey*(consensusFork: ConsensusFork): string = if consensusFork > ConsensusFork.Phase0: @@ -33,65 +34,86 @@ func forkEpochConfigKey*(consensusFork: ConsensusFork): string = ($consensusFork).toUpperAscii() & "_FORK_EPOCH" func getOrDefault*(info: VCRuntimeConfig, name: string, - default: uint64): uint64 = - let numstr = info.getOrDefault(name, "missing") - if numstr == "missing": return default + defaultValue: uint64): uint64 = + let numstr = + try: + info[name] + except KeyError: + return defaultValue Base10.decode(uint64, numstr).valueOr: - return default + return defaultValue func getOrDefault*(info: VCRuntimeConfig, name: string, default: Epoch): Epoch = Epoch(info.getOrDefault(name, uint64(default))) func getForkVersion( info: VCRuntimeConfig, - consensusFork: ConsensusFork): Result[Opt[Version], string] = - let key = consensusFork.forkVersionConfigKey() - let stringValue = info.getOrDefault(key, "missing") - if stringValue == "missing": return ok Opt.none(Version) + consensusFork: ConsensusFork +): Result[Version, string] = + let + key = consensusFork.forkVersionConfigKey() + stringValue = + try: + info[key] + except KeyError: + return err("Forks configuration missing value " & $consensusFork) var value: Version try: hexToByteArrayStrict(stringValue, distinctBase(value)) except ValueError as exc: - return err(key & " is invalid: " & exc.msg) - ok Opt.some value + return err(key & " is invalid, reason " & exc.msg) + ok(value) -func getForkEpoch(info: VCRuntimeConfig, consensusFork: ConsensusFork): Epoch = +func getForkEpoch(info: VCRuntimeConfig, + consensusFork: ConsensusFork): Result[Epoch, string] = if consensusFork > ConsensusFork.Phase0: - let key = consensusFork.forkEpochConfigKey() - info.getOrDefault(key, FAR_FUTURE_EPOCH) + let + key = consensusFork.forkEpochConfigKey() + stringValue = + try: + info[key] + except KeyError: + return err("Forks configuration missing value " & $consensusFork) + numValue = Base10.decode(uint64, stringValue).valueOr: + return err(key & " is invalid, reason " & $error) + ok(Epoch(numValue)) else: - GENESIS_EPOCH + ok(GENESIS_EPOCH) + +template toString(epoch: Epoch): string = + Base10.toString(uint64(epoch)) func getConsensusForkConfig*( - info: VCRuntimeConfig): Result[VCForkConfig, string] = + info: VCRuntimeConfig +): Result[VCForkConfig, string] = ## This extracts all `_FORK_VERSION` and `_FORK_EPOCH` constants - ## that are relevant for Validator Client operation. - ## - ## Note that the fork schedule (`/eth/v1/config/fork_schedule`) cannot be used - ## because it does not indicate whether the forks refer to `ConsensusFork` or - ## to a different fork sequence from an incompatible network (e.g., devnet) - let - res = VCForkConfig( - altairEpoch: info.getForkEpoch(ConsensusFork.Altair), - capellaVersion: ? info.getForkVersion(ConsensusFork.Capella), - capellaEpoch: info.getForkEpoch(ConsensusFork.Capella), - denebEpoch: info.getForkEpoch(ConsensusFork.Deneb)) + var + config: VCForkConfig + presence: set[ConsensusFork] + for fork in ConsensusFork: + let + forkVersion = ? info.getForkVersion(fork) + forkEpoch = ? info.getForkEpoch(fork) + config[fork] = ForkConfigItem(version: forkVersion, epoch: forkEpoch) + presence.incl(fork) - if res.capellaEpoch < res.altairEpoch: + let forks = ConsensusFork.toSeq() + if len(presence) != (int(high(ConsensusFork)) + 1): + let missingForks = forks.filterIt(it notin presence) return err( - "Fork epochs are inconsistent, " & $ConsensusFork.Capella & - " is scheduled at epoch " & $res.capellaEpoch & - " which is before prior fork epoch " & $res.altairEpoch) - if res.denebEpoch < res.capellaEpoch: - return err( - "Fork epochs are inconsistent, " & $ConsensusFork.Deneb & - " is scheduled at epoch " & $res.denebEpoch & - " which is before prior fork epoch " & $res.capellaEpoch) + "Some forks missing in configuration [" & + missingForks.mapIt($it).join(", ") & "]") - if res.capellaEpoch != FAR_FUTURE_EPOCH and res.capellaVersion.isNone: - return err( - "Beacon node has scheduled " & - ConsensusFork.Capella.forkEpochConfigKey() & - " but does not report " & - ConsensusFork.Capella.forkVersionConfigKey()) - ok res + try: + for index, fork in forks.pairs(): + if index > 0: + if config[forks[index]].epoch < config[forks[index - 1]].epoch: + return err( + "Fork epochs are inconsistent, " & $forks[index] & + " is scheduled at epoch " & + config[forks[index]].epoch.toString() & + " which is before prior fork epoch " & + config[forks[index - 1]].epoch.toString()) + except KeyError: + raiseAssert "Forks configuration is missing values" + ok(config) diff --git a/beacon_chain/spec/eth2_apis/rest_nimbus_calls.nim b/beacon_chain/spec/eth2_apis/rest_nimbus_calls.nim index 7ec5f1f1d7..3de4ceded1 100644 --- a/beacon_chain/spec/eth2_apis/rest_nimbus_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_nimbus_calls.nim @@ -21,58 +21,58 @@ proc getTimesyncInifo*(body: RestNimbusTimestamp1): RestPlainResponse {. rest, endpoint: "/nimbus/v1/timesync", meth: MethodPost.} proc getTimeOffset*(client: RestClientRef, - delay: Duration): Future[int64] {.async.} = + delay: Duration): Future[int64] {. + async: (raises: [RestError, RestResponseError, CancelledError]).} = let timestamp1 = getTimestamp() data = RestNimbusTimestamp1(timestamp1: timestamp1) resp = await client.getTimesyncInifo(data) timestamp4 = getTimestamp() - return - case resp.status - of 200: - if resp.contentType.isNone() or - isWildCard(resp.contentType.get().mediaType) or - resp.contentType.get().mediaType != ApplicationJsonMediaType: - raise newException(RestError, "Missing or incorrect Content-Type") + case resp.status + of 200: + if resp.contentType.isNone() or + isWildCard(resp.contentType.get().mediaType) or + resp.contentType.get().mediaType != ApplicationJsonMediaType: + raise newException(RestError, "Missing or incorrect Content-Type") - let stamps = decodeBytes(RestNimbusTimestamp2, resp.data, - resp.contentType).valueOr: - raise newException(RestError, $error) + let stamps = decodeBytes(RestNimbusTimestamp2, resp.data, + resp.contentType).valueOr: + raise newException(RestError, $error) - trace "Time offset data", - timestamp1 = timestamp1, - timestamp2 = stamps.timestamp2, - timestamp3 = stamps.timestamp3, - timestamp4 = timestamp4, - delay14 = delay.nanoseconds, - delay23 = stamps.delay + trace "Time offset data", + timestamp1 = timestamp1, + timestamp2 = stamps.timestamp2, + timestamp3 = stamps.timestamp3, + timestamp4 = timestamp4, + delay14 = delay.nanoseconds, + delay23 = stamps.delay - # t1 - time when we sent request. - # t2 - time when remote server received request. - # t3 - time when remote server sent response. - # t4 - time when we received response. - # delay14 = validator client processing delay. - # delay23 = beacon node processing delay. - # - # Round-trip network delay `delta` = (t4 - t1) - (t3 - t2) - # but with delays this will be: - # `delta` = (t4 - t1 + delay14) - (t3 - t2 + delay23) - # Estimated server time is t3 + (delta div 2) - # Estimated clock skew `theta` = t3 + (delta div 2) - t4 - let - delay14 = delay.nanoseconds - delay23 = int64(stamps.delay) - offset = (int64(stamps.timestamp2) - int64(timestamp1) + - int64(stamps.timestamp3) - int64(timestamp4) + - delay14 - delay23) div 2 - offset - else: - let error = decodeBytes(RestErrorMessage, resp.data, - resp.contentType).valueOr: - let msg = "Incorrect response error format (" & $resp.status & - ") [" & $error & "]" - raise (ref RestResponseError)(msg: msg, status: resp.status) - let msg = "Error response (" & $resp.status & ") [" & error.message & "]" - raise (ref RestResponseError)( - msg: msg, status: error.code, message: error.message) + # t1 - time when we sent request. + # t2 - time when remote server received request. + # t3 - time when remote server sent response. + # t4 - time when we received response. + # delay14 = validator client processing delay. + # delay23 = beacon node processing delay. + # + # Round-trip network delay `delta` = (t4 - t1) - (t3 - t2) + # but with delays this will be: + # `delta` = (t4 - t1 + delay14) - (t3 - t2 + delay23) + # Estimated server time is t3 + (delta div 2) + # Estimated clock skew `theta` = t3 + (delta div 2) - t4 + let + delay14 = delay.nanoseconds + delay23 = int64(stamps.delay) + offset = (int64(stamps.timestamp2) - int64(timestamp1) + + int64(stamps.timestamp3) - int64(timestamp4) + + delay14 - delay23) div 2 + offset + else: + let error = decodeBytes(RestErrorMessage, resp.data, + resp.contentType).valueOr: + let msg = "Incorrect response error format (" & $resp.status & + ") [" & $error & "]" + raise (ref RestResponseError)(msg: msg, status: resp.status) + let msg = "Error response (" & $resp.status & ") [" & error.message & "]" + raise (ref RestResponseError)( + msg: msg, status: error.code, message: error.message) diff --git a/beacon_chain/spec/eth2_apis/rest_types.nim b/beacon_chain/spec/eth2_apis/rest_types.nim index 95d70205ec..169e7572d3 100644 --- a/beacon_chain/spec/eth2_apis/rest_types.nim +++ b/beacon_chain/spec/eth2_apis/rest_types.nim @@ -46,6 +46,9 @@ const LowestScoreAggregatedAttestation* = phase0.Attestation( aggregation_bits: CommitteeValidatorsBits(BitSeq.init(1))) + LowestScoreAggregatedElectraAttestation* = + electra.Attestation( + aggregation_bits: ElectraCommitteeValidatorsBits(BitSeq.init(1))) static: doAssert(ClientMaximumValidatorIds <= ServerMaximumValidatorIds) @@ -464,6 +467,7 @@ type GetBlockV2Response* = ForkedSignedBeaconBlock GetStateV2Response* = ref ForkedHashedBeaconState + GetAggregatedAttestationV2Response* = ForkedAttestation RestRoot* = object root*: Eth2Digest @@ -497,7 +501,6 @@ type # Types based on the OAPI yaml file - used in responses to requests GetBeaconHeadResponse* = DataEnclosedObject[Slot] GetAggregatedAttestationResponse* = DataEnclosedObject[phase0.Attestation] - GetElectraAggregatedAttestationResponse* = DataEnclosedObject[electra.Attestation] GetAttesterDutiesResponse* = DataRootEnclosedObject[seq[RestAttesterDuty]] GetBlockAttestationsResponse* = DataEnclosedObject[seq[phase0.Attestation]] GetBlockHeaderResponse* = DataOptimisticAndFinalizedObject[RestBlockHeaderInfo] @@ -578,8 +581,17 @@ type extra_data*: RestExtraData func isLowestScoreAggregatedAttestation*(a: phase0.Attestation): bool = - (a.data.slot == Slot(0)) and (a.data.index == 0'u64) and - (a.data.source.epoch == Epoch(0)) and (a.data.target.epoch == Epoch(0)) + (a.data.slot == GENESIS_SLOT) and + (a.data.index == 0'u64) and + (a.data.source.epoch == GENESIS_EPOCH) and + (a.data.target.epoch == GENESIS_EPOCH) + +func isLowestScoreAggregatedAttestation*(a: ForkedAttestation): bool = + withAttestation(a): + (forkyAttestation.data.slot == GENESIS_SLOT) and + (forkyAttestation.data.index == 0'u64) and + (forkyAttestation.data.source.epoch == GENESIS_EPOCH) and + (forkyAttestation.data.target.epoch == GENESIS_EPOCH) func `==`*(a, b: RestValidatorIndex): bool {.borrow.} diff --git a/beacon_chain/spec/eth2_apis/rest_validator_calls.nim b/beacon_chain/spec/eth2_apis/rest_validator_calls.nim index 7ec88a48bb..54a8a25c31 100644 --- a/beacon_chain/spec/eth2_apis/rest_validator_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_validator_calls.nim @@ -72,13 +72,13 @@ proc getAggregatedAttestationPlain*( ## https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestation proc getAggregatedAttestationPlainV2*( - attestation_data_root: Eth2Digest, - slot: Slot, - committee_index: CommitteeIndex - ): RestPlainResponse {. - rest, endpoint: "/eth/v2/validator/aggregate_attestation" - meth: MethodGet.} - ## https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/getPoolAttestationsV2 + attestation_data_root: Eth2Digest, + slot: Slot, + committee_index: CommitteeIndex +): RestPlainResponse {. + rest, endpoint: "/eth/v2/validator/aggregate_attestation" + meth: MethodGet.} + ## https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Validator/getAggregatedAttestationV2 proc publishAggregateAndProofs*( body: seq[phase0.SignedAggregateAndProof] @@ -87,12 +87,24 @@ proc publishAggregateAndProofs*( meth: MethodPost.} ## https://ethereum.github.io/beacon-APIs/#/Validator/publishAggregateAndProofs -proc publishAggregateAndProofsV2*( - body: seq[phase0.SignedAggregateAndProof | electra.SignedAggregateAndProof] - ): RestPlainResponse {. - rest, endpoint: "/eth/v2/validator/aggregate_and_proofs", - meth: MethodPost.} +proc publishAggregateAndProofsV2Plain*( + body: seq[ForkySignedAggregateAndProof] +): RestPlainResponse {. + rest, endpoint: "/eth/v2/validator/aggregate_and_proofs", + meth: MethodPost.} + ## https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Validator/publishAggregateAndProofsV2 + +proc publishAggregateAndProofsV2*[T: ForkySignedAggregateAndProof]( + client: RestClientRef, + body: seq[T] +): Future[RestPlainResponse] {. + async: (raises: [CancelledError, RestEncodingError, RestDnsResolveError, + RestCommunicationError], raw: true).} = ## https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Validator/publishAggregateAndProofsV2 + let + consensus = T.kind.toString() + client.publishAggregateAndProofsV2Plain( + body, extraHeaders = @[("eth-consensus-version", consensus)]) proc prepareBeaconCommitteeSubnet*( body: seq[RestCommitteeSubscription] diff --git a/beacon_chain/spec/forks.nim b/beacon_chain/spec/forks.nim index a99a29a5d4..221e66f73d 100644 --- a/beacon_chain/spec/forks.nim +++ b/beacon_chain/spec/forks.nim @@ -162,6 +162,19 @@ type phase0.SignedAggregateAndProof | electra.SignedAggregateAndProof + ForkyAttestation* = + phase0.Attestation | + electra.Attestation + + ForkedAttestation* = object + case kind*: ConsensusFork + of ConsensusFork.Phase0: phase0Data*: phase0.Attestation + of ConsensusFork.Altair: altairData*: phase0.Attestation + of ConsensusFork.Bellatrix: bellatrixData*: phase0.Attestation + of ConsensusFork.Capella: capellaData*: phase0.Attestation + of ConsensusFork.Deneb: denebData*: phase0.Attestation + of ConsensusFork.Electra: electraData*: electra.Attestation + ForkedAggregateAndProof* = object case kind*: ConsensusFork of ConsensusFork.Phase0: phase0Data*: phase0.AggregateAndProof @@ -324,7 +337,9 @@ template kind*( phase0.SigVerifiedSignedBeaconBlock | phase0.MsgTrustedSignedBeaconBlock | phase0.TrustedSignedBeaconBlock | - phase0.AggregateAndProof]): ConsensusFork = + phase0.Attestation | + phase0.AggregateAndProof | + phase0.SignedAggregateAndProof]): ConsensusFork = ConsensusFork.Phase0 template kind*( @@ -415,8 +430,10 @@ template kind*( electra.SigVerifiedSignedBeaconBlock | electra.MsgTrustedSignedBeaconBlock | electra.TrustedSignedBeaconBlock | - electra_mev.SignedBlindedBeaconBlock | - electra.AggregateAndProof]): ConsensusFork = + electra.Attestation | + electra.AggregateAndProof | + electra.SignedAggregateAndProof | + electra_mev.SignedBlindedBeaconBlock]): ConsensusFork = ConsensusFork.Electra template BeaconState*(kind: static ConsensusFork): auto = @@ -1241,6 +1258,33 @@ template withStateAndBlck*( template forkyBlck: untyped {.inject, used.} = b.phase0Data body +template withAttestation*(a: ForkedAttestation, body: untyped): untyped = + case a.kind + of ConsensusFork.Electra: + const consensusFork {.inject, used.} = ConsensusFork.Electra + template forkyAttestation: untyped {.inject.} = a.electraData + body + of ConsensusFork.Deneb: + const consensusFork {.inject, used.} = ConsensusFork.Deneb + template forkyAttestation: untyped {.inject.} = a.denebData + body + of ConsensusFork.Capella: + const consensusFork {.inject, used.} = ConsensusFork.Capella + template forkyAttestation: untyped {.inject.} = a.capellaData + body + of ConsensusFork.Bellatrix: + const consensusFork {.inject, used.} = ConsensusFork.Bellatrix + template forkyAttestation: untyped {.inject.} = a.bellatrixData + body + of ConsensusFork.Altair: + const consensusFork {.inject, used.} = ConsensusFork.Altair + template forkyAttestation: untyped {.inject.} = a.altairData + body + of ConsensusFork.Phase0: + const consensusFork {.inject, used.} = ConsensusFork.Phase0 + template forkyAttestation: untyped {.inject.} = a.phase0Data + body + template withAggregateAndProof*(a: ForkedAggregateAndProof, body: untyped): untyped = case a.kind @@ -1385,14 +1429,15 @@ func lcDataForkAtConsensusFork*( else: LightClientDataFork.None -func getForkSchedule*(cfg: RuntimeConfig): array[5, Fork] = +func getForkSchedule*(cfg: RuntimeConfig): array[6, Fork] = ## This procedure returns list of known and/or scheduled forks. ## ## This procedure is used by HTTP REST framework and validator client. ## ## NOTE: Update this procedure when new fork will be scheduled. + static: doAssert high(ConsensusFork) == ConsensusFork.Electra [cfg.genesisFork(), cfg.altairFork(), cfg.bellatrixFork(), cfg.capellaFork(), - cfg.denebFork()] + cfg.denebFork(), cfg.electraFork()] type # The first few fields of a state, shared across all forks @@ -1598,6 +1643,32 @@ func committee_index*(v: electra.Attestation, on_chain: static bool): uint64 = else: uint64 v.committee_bits.get_committee_index_one().expect("network attestation") +template init*(T: type ForkedAttestation, + attestation: phase0.Attestation, + fork: ConsensusFork): T = + case fork + of ConsensusFork.Phase0: + ForkedAttestation(kind: ConsensusFork.Phase0, phase0Data: attestation) + of ConsensusFork.Altair: + ForkedAttestation(kind: ConsensusFork.Altair, altairData: attestation) + of ConsensusFork.Bellatrix: + ForkedAttestation(kind: ConsensusFork.Bellatrix, bellatrixData: attestation) + of ConsensusFork.Capella: + ForkedAttestation(kind: ConsensusFork.Capella, capellaData: attestation) + of ConsensusFork.Deneb: + ForkedAttestation(kind: ConsensusFork.Deneb, denebData: attestation) + of ConsensusFork.Electra: + raiseAssert $fork & " fork should not be used for this type of attestation" + +template init*(T: type ForkedAttestation, + attestation: electra.Attestation, + fork: ConsensusFork): T = + case fork + of ConsensusFork.Phase0 .. ConsensusFork.Deneb: + raiseAssert $fork & " fork should not be used for this type of attestation" + of ConsensusFork.Electra: + ForkedAttestation(kind: ConsensusFork.Electra, electraData: attestation) + template init*(T: type ForkedAggregateAndProof, proof: phase0.AggregateAndProof, fork: ConsensusFork): T = diff --git a/beacon_chain/validator_client/api.nim b/beacon_chain/validator_client/api.nim index d02254aae2..05fe14e55b 100644 --- a/beacon_chain/validator_client/api.nim +++ b/beacon_chain/validator_client/api.nim @@ -16,6 +16,7 @@ import "."/[common, fallback_service, scoring] export eth2_rest_serialization, common const + ResponseContentTypeError = "Content-type is not supported" ResponseInvalidError = "Received invalid request response" ResponseInternalError = "Received internal error response" ResponseUnexpectedError = "Received unexpected error response" @@ -87,8 +88,12 @@ func init*[T, X](t: typedesc[BestNodeResponse], node: BeaconNodeServerRef, data: ApiResponse[T], score: X): BestNodeResponse[T, X] = BestNodeResponse[T, X](node: node, data: data, score: score) -proc lazyWaiter(node: BeaconNodeServerRef, request: FutureBase, - requestName: string, strategy: ApiStrategyKind) {.async.} = +proc lazyWaiter( + node: BeaconNodeServerRef, + request: FutureBase, + requestName: string, + strategy: ApiStrategyKind +) {.async: (raises: []).} = try: await allFutures(request) if request.failed(): @@ -99,9 +104,13 @@ proc lazyWaiter(node: BeaconNodeServerRef, request: FutureBase, except CancelledError: await cancelAndWait(request) -proc lazyWait(nodes: seq[BeaconNodeServerRef], requests: seq[FutureBase], - timerFut: Future[void], requestName: string, - strategy: ApiStrategyKind) {.async.} = +proc lazyWait( + nodes: seq[BeaconNodeServerRef], + requests: seq[FutureBase], + timerFut: Future[void], + requestName: string, + strategy: ApiStrategyKind +) {.async: (raises: [CancelledError]).} = doAssert(len(nodes) == len(requests)) if len(nodes) == 0: return @@ -112,12 +121,9 @@ proc lazyWait(nodes: seq[BeaconNodeServerRef], requests: seq[FutureBase], strategy)) if not(isNil(timerFut)): - await allFutures(futures) or timerFut + discard await race(allFutures(futures), timerFut) if timerFut.finished(): - var pending: seq[Future[void]] - for future in futures: - if not(future.finished()): - pending.add(future.cancelAndWait()) + let pending = futures.mapIt(it.cancelAndWait()) await allFutures(pending) else: await cancelAndWait(timerFut) @@ -173,11 +179,6 @@ template firstSuccessParallel*( if not(isNil(timerFut)) and not(timerFut.finished()): await timerFut.cancelAndWait() raise exc - except CatchableError as exc: - # This case could not be happened. - error "Unexpected exception while waiting for beacon nodes", - err_name = $exc.name, err_msg = $exc.msg - default(seq[BeaconNodeServerRef]) if len(onlineNodes) == 0: retRes = ApiResponse[handlerType].err("No online beacon node(s)") @@ -210,7 +211,7 @@ template firstSuccessParallel*( raceFut = race(pendingRequests) if not(isNil(timerFut)): - await raceFut or timerFut + discard await race(raceFut, timerFut) else: await allFutures(raceFut) @@ -228,7 +229,7 @@ template firstSuccessParallel*( requestsCancelled = true 0 else: - let res = pendingRequests.find(raceFut.read()) + let res = pendingRequests.find(raceFut.value) doAssert(res >= 0) res requestFut = pendingRequests[index] @@ -248,8 +249,6 @@ template firstSuccessParallel*( body2 except CancelledError as exc: raise exc - except CatchableError: - raiseAssert("Response handler must not raise exceptions") if handlerResponse.isOk(): retRes = handlerResponse @@ -269,13 +268,7 @@ template firstSuccessParallel*( pendingCancel.add(future.cancelAndWait()) await noCancel allFutures(pendingCancel) raise exc - except CatchableError as exc: - # This should not be happened, because allFutures() and race() did not - # raise any exceptions. - error "Unexpected exception while processing request", - err_name = $exc.name, err_msg = $exc.msg - retRes = ApiResponse[handlerType].err("Unexpected error") - resultReady = true + if resultReady: break if resultReady: @@ -326,11 +319,6 @@ template bestSuccess*( if not(isNil(timerFut)) and not(timerFut.finished()): await timerFut.cancelAndWait() raise exc - except CatchableError as exc: - # This case could not be happened. - error "Unexpected exception while waiting for beacon nodes", - err_name = $exc.name, err_msg = $exc.msg - default(seq[BeaconNodeServerRef]) if len(onlineNodes) == 0: retRes = ApiResponse[handlerType].err("No online beacon node(s)") @@ -354,12 +342,12 @@ template bestSuccess*( var finishedRequests: seq[FutureBase] finishedNodes: seq[BeaconNodeServerRef] - raceFut: Future[FutureBase] + raceFut: Future[FutureBase].Raising([ValueError, CancelledError]) try: raceFut = race(pendingRequests) if not(isNil(timerFut)): - await raceFut or timerFut + discard await race(raceFut, timerFut) else: await allFutures(raceFut) @@ -378,9 +366,6 @@ template bestSuccess*( bodyHandler except CancelledError as exc: raise exc - except CatchableError: - raiseAssert( - "Response handler must not raise exceptions") if handlerResponse.isOk(): let @@ -390,8 +375,7 @@ template bestSuccess*( bodyScore except CancelledError as exc: raise exc - except CatchableError: - raiseAssert("Score handler must not raise exceptions") + scores.add(ApiScore.init(node, score)) if bestResponse.isNone() or (score > bestResponse.get().score): @@ -436,13 +420,6 @@ template bestSuccess*( # Awaiting cancellations. await noCancel allFutures(pendingCancel) raise exc - except CatchableError as exc: - # This should not be happened, because allFutures() and race() - # did not raise any exceptions. - error "Unexpected exception while processing request", - err_name = $exc.name, err_msg = $exc.msg - retRes = ApiResponse[handlerType].err("Unexpected error") - break mainLoop if bestResponse.isSome(): retRes = bestResponse.get().data @@ -491,12 +468,6 @@ template onceToAll*( if not(isNil(timerFut)) and not(timerFut.finished()): await timerFut.cancelAndWait() raise exc - except CatchableError as exc: - # This case could not be happened. - error "Unexpected exception while waiting for beacon nodes", - err_name = $exc.name, err_msg = $exc.msg - var default: seq[BeaconNodeServerRef] - default if len(onlineNodes) == 0: # Timeout exceeded or operation was cancelled @@ -539,10 +510,6 @@ template onceToAll*( pendingCancel.add(timerFut.cancelAndWait()) await noCancel allFutures(pendingCancel) raise exc - except CatchableError: - # This should not be happened, because allFutures() and race() did not - # raise any exceptions. - ApiOperation.Failure let responses = block: @@ -553,7 +520,7 @@ template onceToAll*( let fut = pendingRequests[idx] if fut.finished(): if fut.failed() or fut.cancelled(): - let exc = fut.readError() + let exc = fut.error ApiNodeResponse[responseType]( node: pnode, data: ApiResponse[responseType].err("[" & $exc.name & "] " & @@ -562,7 +529,7 @@ template onceToAll*( else: ApiNodeResponse[responseType]( node: pnode, - data: ApiResponse[responseType].ok(fut.read()) + data: ApiResponse[responseType].ok(fut.value) ) else: case status @@ -626,10 +593,6 @@ template firstSuccessSequential*( if not(isNil(timerFut)) and not(timerFut.finished()): await timerFut.cancelAndWait() raise exc - except CatchableError: - # This case could not be happened. - var default: seq[BeaconNodeServerRef] - default if len(onlineNodes) == 0: # `onlineNodes` sequence is empty only if operation timeout exceeded. @@ -657,9 +620,6 @@ template firstSuccessSequential*( if not(bodyFut.finished()): await bodyFut.cancelAndWait() raise exc - except CatchableError: - # This case should not happen. - ApiOperation.Failure else: try: discard await race(bodyFut, timerFut) @@ -677,9 +637,6 @@ template firstSuccessSequential*( pending.add(timerFut.cancelAndWait()) await noCancel allFutures(pending) raise exc - except CatchableError: - # This case should not happen. - ApiOperation.Failure var handlerStatus = false block: @@ -687,10 +644,10 @@ template firstSuccessSequential*( block: if bodyFut.finished(): if bodyFut.failed() or bodyFut.cancelled(): - let exc = bodyFut.readError() + let exc = bodyFut.error ApiResponse[responseType].err("[" & $exc.name & "] " & $exc.msg) else: - ApiResponse[responseType].ok(bodyFut.read()) + ApiResponse[responseType].ok(bodyFut.value) else: case resOp of ApiOperation.Interrupt: @@ -702,11 +659,7 @@ template firstSuccessSequential*( # finished, and `Failure` processed when Future is finished. ApiResponse[responseType].err("Unexpected error") - handlerStatus = - try: - handlers - except CatchableError: - raiseAssert("Response handler must not raise exceptions") + handlerStatus = handlers if resOp == ApiOperation.Success: if handlerStatus: @@ -788,6 +741,12 @@ template handle404(): untyped {.dirty.} = node.updateStatus(RestBeaconNodeStatus.Incompatible, failure) failures.add(failure) +template handle415(): untyped {.dirty.} = + let failure = ApiNodeFailure.init(ApiFailure.UnsupportedContentType, + RequestName, strategy, node, response.status, response.getErrorMessage()) + node.updateStatus(RestBeaconNodeStatus.Incompatible, failure) + failures.add(failure) + template handle500(): untyped {.dirty.} = let failure = ApiNodeFailure.init(ApiFailure.Internal, RequestName, strategy, node, response.status, response.getErrorMessage()) @@ -807,10 +766,11 @@ template handle503(): untyped {.dirty.} = failures.add(failure) proc getProposerDuties*( - vc: ValidatorClientRef, - epoch: Epoch, - strategy: ApiStrategyKind - ): Future[GetProposerDutiesResponse] {.async.} = + vc: ValidatorClientRef, + epoch: Epoch, + strategy: ApiStrategyKind +): Future[GetProposerDutiesResponse] {. + async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "getProposerDuties" var failures: seq[ApiNodeFailure] @@ -896,11 +856,12 @@ proc getProposerDuties*( msg: "Failed to get proposer duties", data: failures) proc getAttesterDuties*( - vc: ValidatorClientRef, - epoch: Epoch, - validators: seq[ValidatorIndex], - strategy: ApiStrategyKind - ): Future[GetAttesterDutiesResponse] {.async.} = + vc: ValidatorClientRef, + epoch: Epoch, + validators: seq[ValidatorIndex], + strategy: ApiStrategyKind +): Future[GetAttesterDutiesResponse] {. + async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "getAttesterDuties" var failures: seq[ApiNodeFailure] @@ -987,11 +948,12 @@ proc getAttesterDuties*( msg: "Failed to get attester duties", data: failures) proc getSyncCommitteeDuties*( - vc: ValidatorClientRef, - epoch: Epoch, - validators: seq[ValidatorIndex], - strategy: ApiStrategyKind - ): Future[GetSyncCommitteeDutiesResponse] {.async.} = + vc: ValidatorClientRef, + epoch: Epoch, + validators: seq[ValidatorIndex], + strategy: ApiStrategyKind +): Future[GetSyncCommitteeDutiesResponse] {. + async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "getSyncCommitteeDuties" var failures: seq[ApiNodeFailure] @@ -1080,79 +1042,90 @@ proc getSyncCommitteeDuties*( msg: "Failed to get sync committee duties", data: failures) proc getForkSchedule*( - vc: ValidatorClientRef, - strategy: ApiStrategyKind - ): Future[seq[Fork]] {.async.} = + vc: ValidatorClientRef +): Future[seq[Fork]] {.async: (raises: [CancelledError]).} = const RequestName = "getForkSchedule" - var failures: seq[ApiNodeFailure] - - case strategy - of ApiStrategyKind.First, ApiStrategyKind.Best: - let res = vc.firstSuccessParallel(RestPlainResponse, - GetForkScheduleResponse, - SlotDuration, - ViableNodeStatus, - {BeaconNodeRole.Duties}, - getForkSchedulePlain(it)): - if apiResponse.isErr(): - handleCommunicationError() - ApiResponse[GetForkScheduleResponse].err(apiResponse.error) - else: - let response = apiResponse.get() - case response.status - of 200: - let res = decodeBytes(GetForkScheduleResponse, response.data, - response.contentType) - if res.isErr(): - handleUnexpectedData() - ApiResponse[GetForkScheduleResponse].err($res.error) - else: - ApiResponse[GetForkScheduleResponse].ok(res.get()) - of 500: - handle500() - ApiResponse[GetForkScheduleResponse].err(ResponseInternalError) - else: - handleUnexpectedCode() - ApiResponse[GetForkScheduleResponse].err(ResponseUnexpectedError) - - if res.isErr(): - raise (ref ValidatorApiError)(msg: res.error, data: failures) - return res.get().data - - of ApiStrategyKind.Priority: - vc.firstSuccessSequential(RestPlainResponse, - SlotDuration, - ViableNodeStatus, - {BeaconNodeRole.Duties}, - getForkSchedulePlain(it)): - if apiResponse.isErr(): - handleCommunicationError() - false + let + resp = vc.onceToAll(RestPlainResponse, + SlotDuration, + ViableNodeStatus, + {BeaconNodeRole.Duties}, + getForkSchedulePlain(it)) + case resp.status + of ApiOperation.Timeout: + debug "Unable to obtain fork schedule in time", timeout = SlotDuration + default(seq[Fork]) + of ApiOperation.Interrupt: + debug "Fork schedule request was interrupted" + default(seq[Fork]) + of ApiOperation.Failure: + debug "Unexpected error happened while trying to get fork schedule" + default(seq[Fork]) + of ApiOperation.Success: + var + biggestForkSchedule: seq[Fork] + biggestNode: BeaconNodeServerRef + for apiResponse in resp.data: + if apiResponse.data.isErr(): + debug "Unable to get fork schedule", + endpoint = apiResponse.node, reason = apiResponse.data.error else: - let response = apiResponse.get() + let response = apiResponse.data.get() case response.status of 200: - let res = decodeBytes(GetForkScheduleResponse, response.data, - response.contentType) - if res.isOk(): return res.get().data + let schedule = decodeBytes(GetForkScheduleResponse, response.data, + response.contentType).valueOr: + let failure = ApiNodeFailure.init( + ApiFailure.UnexpectedResponse, RequestName, + apiResponse.node, response.status, $error) + debug ResponseDecodeError, reason = getFailureReason(failure) + apiResponse.node.updateStatus( + RestBeaconNodeStatus.Incompatible, failure) + continue - handleUnexpectedData() - false + if len(schedule.data) > len(biggestForkSchedule): + if biggestForkSchedule notin schedule.data: + let failure = ApiNodeFailure.init( + ApiFailure.UnexpectedResponse, RequestName, + apiResponse.node, response.status, "Incompatible fork schedule") + debug "Incompatible fork schedule", endpoint = biggestNode + biggestNode.updateStatus( + RestBeaconNodeStatus.Incompatible, failure) + biggestForkSchedule = schedule.data + biggestNode = apiResponse.node + else: + if schedule.data notin biggestForkSchedule: + let failure = ApiNodeFailure.init( + ApiFailure.UnexpectedResponse, RequestName, + apiResponse.node, response.status, "Incompatible fork schedule") + debug "Incompatible fork schedule", endpoint = apiResponse.node + apiResponse.node.updateStatus( + RestBeaconNodeStatus.Incompatible, failure) + continue of 500: - handle500() - false + let failure = + ApiNodeFailure.init(ApiFailure.Internal, RequestName, + apiResponse.node, response.status, response.getErrorMessage()) + apiResponse.node.updateStatus( + RestBeaconNodeStatus.InternalError, failure) + continue else: - handleUnexpectedCode() - false + let failure = + ApiNodeFailure.init(ApiFailure.UnexpectedCode, RequestName, + apiResponse.node, response.status, response.getErrorMessage()) + debug ResponseUnexpectedError, reason = getFailureReason(failure) + apiResponse.node.updateStatus( + RestBeaconNodeStatus.Incompatible, failure) + continue - raise (ref ValidatorApiError)( - msg: "Failed to get fork schedule", data: failures) + biggestForkSchedule proc getHeadBlockRoot*( - vc: ValidatorClientRef, - strategy: ApiStrategyKind - ): Future[DataOptimisticObject[RestRoot]] {.async.} = + vc: ValidatorClientRef, + strategy: ApiStrategyKind +): Future[DataOptimisticObject[RestRoot]] {. + async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "getHeadBlockRoot" var failures: seq[ApiNodeFailure] @@ -1294,10 +1267,11 @@ proc getHeadBlockRoot*( msg: "Failed to get head block root", data: failures) proc getValidators*( - vc: ValidatorClientRef, - id: seq[ValidatorIdent], - strategy: ApiStrategyKind - ): Future[seq[RestValidator]] {.async.} = + vc: ValidatorClientRef, + id: seq[ValidatorIdent], + strategy: ApiStrategyKind +): Future[seq[RestValidator]] {. + async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "getStateValidators" let stateIdent = StateIdent.init(StateIdentType.Head) @@ -1386,11 +1360,12 @@ proc getValidators*( msg: "Failed to get state's validators", data: failures) proc produceAttestationData*( - vc: ValidatorClientRef, - slot: Slot, - committee_index: CommitteeIndex, - strategy: ApiStrategyKind - ): Future[AttestationData] {.async.} = + vc: ValidatorClientRef, + slot: Slot, + committee_index: CommitteeIndex, + strategy: ApiStrategyKind +): Future[AttestationData] {. + async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "produceAttestationData" var failures: seq[ApiNodeFailure] @@ -1516,11 +1491,78 @@ proc produceAttestationData*( raise (ref ValidatorApiError)( msg: "Failed to produce attestation data", data: failures) +proc submitPoolAttestationsV2*( + vc: ValidatorClientRef, + data: seq[ForkyAttestation], + strategy: ApiStrategyKind +): Future[bool] {.async: (raises: [CancelledError, ValidatorApiError]).} = + const + RequestName = "submitPoolAttestationsV2" + + var failures: seq[ApiNodeFailure] + + case strategy + of ApiStrategyKind.First, ApiStrategyKind.Best: + let res = vc.firstSuccessParallel(RestPlainResponse, + bool, + SlotDuration, + ViableNodeStatus, + {BeaconNodeRole.AttestationPublish}, + submitPoolAttestationsV2(it, data)): + if apiResponse.isErr(): + handleCommunicationError() + ApiResponse[bool].err(apiResponse.error) + else: + let response = apiResponse.get() + case response.status + of 200: + ApiResponse[bool].ok(true) + of 400: + handle400Indexed() + ApiResponse[bool].err(ResponseInvalidError) + of 500: + handle500() + ApiResponse[bool].err(ResponseInternalError) + else: + handleUnexpectedCode() + ApiResponse[bool].err(ResponseUnexpectedError) + + if res.isErr(): + raise (ref ValidatorApiError)(msg: res.error, data: failures) + return res.get() + + of ApiStrategyKind.Priority: + vc.firstSuccessSequential(RestPlainResponse, + SlotDuration, + ViableNodeStatus, + {BeaconNodeRole.AttestationPublish}, + submitPoolAttestationsV2(it, data)): + if apiResponse.isErr(): + handleCommunicationError() + false + else: + let response = apiResponse.get() + case response.status + of 200: + return true + of 400: + handle400Indexed() + false + of 500: + handle500() + false + else: + handleUnexpectedCode() + false + + raise (ref ValidatorApiError)( + msg: "Failed to submit attestations", data: failures) + proc submitPoolAttestations*( - vc: ValidatorClientRef, - data: seq[phase0.Attestation], - strategy: ApiStrategyKind - ): Future[bool] {.async.} = + vc: ValidatorClientRef, + data: seq[phase0.Attestation], + strategy: ApiStrategyKind +): Future[bool] {.async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "submitPoolAttestations" @@ -1584,10 +1626,10 @@ proc submitPoolAttestations*( msg: "Failed to submit attestations", data: failures) proc submitPoolSyncCommitteeSignature*( - vc: ValidatorClientRef, - data: SyncCommitteeMessage, - strategy: ApiStrategyKind - ): Future[bool] {.async.} = + vc: ValidatorClientRef, + data: SyncCommitteeMessage, + strategy: ApiStrategyKind +): Future[bool] {.async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "submitPoolSyncCommitteeSignatures" @@ -1660,11 +1702,12 @@ proc submitPoolSyncCommitteeSignature*( msg: "Failed to submit sync committee message", data: failures) proc getAggregatedAttestation*( - vc: ValidatorClientRef, - slot: Slot, - root: Eth2Digest, - strategy: ApiStrategyKind - ): Future[phase0.Attestation] {.async.} = + vc: ValidatorClientRef, + slot: Slot, + root: Eth2Digest, + strategy: ApiStrategyKind +): Future[phase0.Attestation] {. + async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "getAggregatedAttestation" @@ -1697,6 +1740,10 @@ proc getAggregatedAttestation*( handle400() ApiResponse[GetAggregatedAttestationResponse].err( ResponseInvalidError) + of 404: + handle404() + ApiResponse[GetAggregatedAttestationResponse].err( + ResponseNotFoundError) of 500: handle500() ApiResponse[GetAggregatedAttestationResponse].err( @@ -1708,7 +1755,7 @@ proc getAggregatedAttestation*( if res.isErr(): raise (ref ValidatorApiError)(msg: res.error, data: failures) - return res.get().data + res.get().data of ApiStrategyKind.Best: let res = vc.bestSuccess( @@ -1756,7 +1803,7 @@ proc getAggregatedAttestation*( if res.isErr(): raise (ref ValidatorApiError)(msg: res.error, data: failures) - return res.get().data + res.get().data of ApiStrategyKind.Priority: vc.firstSuccessSequential( @@ -1780,6 +1827,148 @@ proc getAggregatedAttestation*( of 400: handle400() false + of 404: + handle404() + false + of 500: + handle500() + false + else: + handleUnexpectedCode() + false + + raise (ref ValidatorApiError)( + msg: "Failed to get aggregated attestation", data: failures) + +proc getAggregatedAttestationV2*( + vc: ValidatorClientRef, + slot: Slot, + root: Eth2Digest, + committee_index: CommitteeIndex, + strategy: ApiStrategyKind +): Future[ForkedAttestation] {. + async: (raises: [CancelledError, ValidatorApiError]).} = + const + RequestName = "getAggregatedAttestationV2" + + var failures: seq[ApiNodeFailure] + + case strategy + of ApiStrategyKind.First: + let res = vc.firstSuccessParallel( + RestPlainResponse, + GetAggregatedAttestationV2Response, + OneThirdDuration, + ViableNodeStatus, + {BeaconNodeRole.AggregatedData}, + getAggregatedAttestationPlainV2(it, root, slot, committee_index)): + if apiResponse.isErr(): + handleCommunicationError() + ApiResponse[GetAggregatedAttestationV2Response].err(apiResponse.error) + else: + let response = apiResponse.get() + case response.status: + of 200: + let res = decodeBytes(GetAggregatedAttestationV2Response, + response.data, response.contentType) + if res.isErr(): + handleUnexpectedData() + ApiResponse[GetAggregatedAttestationV2Response].err($res.error) + else: + ApiResponse[GetAggregatedAttestationV2Response].ok(res.get()) + of 400: + handle400() + ApiResponse[GetAggregatedAttestationV2Response].err( + ResponseInvalidError) + of 404: + handle404() + ApiResponse[GetAggregatedAttestationV2Response].err( + ResponseNotFoundError) + of 500: + handle500() + ApiResponse[GetAggregatedAttestationV2Response].err( + ResponseInternalError) + else: + handleUnexpectedCode() + ApiResponse[GetAggregatedAttestationV2Response].err( + ResponseUnexpectedError) + + if res.isErr(): + raise (ref ValidatorApiError)(msg: res.error, data: failures) + res.get() + + of ApiStrategyKind.Best: + let res = vc.bestSuccess( + RestPlainResponse, + GetAggregatedAttestationV2Response, + float64, + OneThirdDuration, + ViableNodeStatus, + {BeaconNodeRole.AggregatedData}, + getAggregatedAttestationPlainV2(it, root, slot, committee_index), + getAggregatedAttestationDataScore(itresponse)): + if apiResponse.isErr(): + handleCommunicationError() + ApiResponse[GetAggregatedAttestationV2Response].err(apiResponse.error) + else: + let response = apiResponse.get() + case response.status: + of 200: + let res = decodeBytes(GetAggregatedAttestationV2Response, + response.data, response.contentType) + if res.isErr(): + handleUnexpectedData() + ApiResponse[GetAggregatedAttestationV2Response].err($res.error) + else: + ApiResponse[GetAggregatedAttestationV2Response].ok(res.get()) + of 400: + handle400() + ApiResponse[GetAggregatedAttestationV2Response].err( + ResponseInvalidError) + of 404: + # A 404 error must be returned if no attestation is available for the + # requested `attestation_data_root`. + ApiResponse[GetAggregatedAttestationV2Response].ok( + ForkedAttestation.init( + LowestScoreAggregatedElectraAttestation, ConsensusFork.Electra)) + of 500: + handle500() + ApiResponse[GetAggregatedAttestationV2Response].err( + ResponseInternalError) + else: + handleUnexpectedCode() + ApiResponse[GetAggregatedAttestationV2Response].err( + ResponseUnexpectedError) + + if res.isErr(): + raise (ref ValidatorApiError)(msg: res.error, data: failures) + res.get() + + of ApiStrategyKind.Priority: + vc.firstSuccessSequential( + RestPlainResponse, + OneThirdDuration, + ViableNodeStatus, + {BeaconNodeRole.AggregatedData}, + getAggregatedAttestationPlainV2(it, root, slot, committee_index)): + if apiResponse.isErr(): + handleCommunicationError() + false + else: + let response = apiResponse.get() + case response.status: + of 200: + let res = decodeBytes(GetAggregatedAttestationV2Response, + response.data, response.contentType) + if res.isOk(): return res.get() + handleUnexpectedData() + false + of 400: + handle400() + false + of 404: + handle404() + false of 500: handle500() false @@ -1791,12 +1980,13 @@ proc getAggregatedAttestation*( msg: "Failed to get aggregated attestation", data: failures) proc produceSyncCommitteeContribution*( - vc: ValidatorClientRef, - slot: Slot, - subcommitteeIndex: SyncSubcommitteeIndex, - root: Eth2Digest, - strategy: ApiStrategyKind - ): Future[SyncCommitteeContribution] {.async.} = + vc: ValidatorClientRef, + slot: Slot, + subcommitteeIndex: SyncSubcommitteeIndex, + root: Eth2Digest, + strategy: ApiStrategyKind +): Future[SyncCommitteeContribution] {. + async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "produceSyncCommitteeContribution" @@ -1919,11 +2109,84 @@ proc produceSyncCommitteeContribution*( raise (ref ValidatorApiError)( msg: "Failed to produce sync committee contribution", data: failures) +proc publishAggregateAndProofsV2*( + vc: ValidatorClientRef, + data: seq[ForkySignedAggregateAndProof], + strategy: ApiStrategyKind +): Future[bool] {.async: (raises: [CancelledError, ValidatorApiError]).} = + const + RequestName = "publishAggregateAndProofsV2" + + var failures: seq[ApiNodeFailure] + + case strategy + of ApiStrategyKind.First, ApiStrategyKind.Best: + let res = vc.firstSuccessParallel(RestPlainResponse, + bool, + SlotDuration, + ViableNodeStatus, + {BeaconNodeRole.AggregatedPublish}, + publishAggregateAndProofsV2(it, data)): + if apiResponse.isErr(): + handleCommunicationError() + ApiResponse[bool].err(apiResponse.error) + else: + let response = apiResponse.get() + case response.status: + of 200: + ApiResponse[bool].ok(true) + of 400: + handle400() + ApiResponse[bool].err(ResponseInvalidError) + of 404: + handle404() + ApiResponse[bool].err(ResponseNotFoundError) + of 500: + handle500() + ApiResponse[bool].err(ResponseInternalError) + else: + handleUnexpectedCode() + ApiResponse[bool].err(ResponseUnexpectedError) + + if res.isErr(): + raise (ref ValidatorApiError)(msg: res.error, data: failures) + res.get() + + of ApiStrategyKind.Priority: + vc.firstSuccessSequential(RestPlainResponse, + SlotDuration, + ViableNodeStatus, + {BeaconNodeRole.AggregatedPublish}, + publishAggregateAndProofsV2(it, data)): + if apiResponse.isErr(): + handleCommunicationError() + false + else: + let response = apiResponse.get() + case response.status: + of 200: + return true + of 400: + handle400() + false + of 404: + + false + of 500: + handle500() + false + else: + handleUnexpectedCode() + false + + raise (ref ValidatorApiError)( + msg: "Failed to publish aggregated attestation", data: failures) + proc publishAggregateAndProofs*( - vc: ValidatorClientRef, - data: seq[phase0.SignedAggregateAndProof], - strategy: ApiStrategyKind - ): Future[bool] {.async.} = + vc: ValidatorClientRef, + data: seq[phase0.SignedAggregateAndProof], + strategy: ApiStrategyKind +): Future[bool] {.async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "publishAggregateAndProofs" @@ -1987,10 +2250,11 @@ proc publishAggregateAndProofs*( msg: "Failed to publish aggregated attestation", data: failures) proc publishContributionAndProofs*( - vc: ValidatorClientRef, - data: seq[RestSignedContributionAndProof], - strategy: ApiStrategyKind - ): Future[bool] {.async.} = + vc: ValidatorClientRef, + data: seq[RestSignedContributionAndProof], + strategy: ApiStrategyKind +): Future[bool] {. + async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "publishContributionAndProofs" @@ -2054,13 +2318,14 @@ proc publishContributionAndProofs*( msg: "Failed to publish sync committee contribution", data: failures) proc produceBlockV3*( - vc: ValidatorClientRef, - slot: Slot, - randao_reveal: ValidatorSig, - graffiti: GraffitiBytes, - builder_boost_factor: uint64, - strategy: ApiStrategyKind - ): Future[ProduceBlockResponseV3] {.async.} = + vc: ValidatorClientRef, + slot: Slot, + randao_reveal: ValidatorSig, + graffiti: GraffitiBytes, + builder_boost_factor: uint64, + strategy: ApiStrategyKind +): Future[ProduceBlockResponseV3] {. + async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "produceBlockV3" @@ -2213,11 +2478,128 @@ proc produceBlockV3*( raise (ref ValidatorApiError)( msg: "Failed to produce block", data: failures) +proc publishBlockV2*( + vc: ValidatorClientRef, + data: RestPublishedSignedBlockContents, + broadcast_validation: BroadcastValidationType, + strategy: ApiStrategyKind +): Future[bool] {.async: (raises: [CancelledError, ValidatorApiError]).} = + const + RequestName = "publishBlockV2" + BlockBroadcasted = "Block not passed validation, but still published" + + var failures: seq[ApiNodeFailure] + + case strategy + of ApiStrategyKind.First, ApiStrategyKind.Best: + let res = block: + vc.firstSuccessParallel(RestPlainResponse, + bool, + SlotDuration, + ViableNodeStatus, + {BeaconNodeRole.BlockProposalPublish}): + case data.kind + of ConsensusFork.Phase0: + publishBlockV2(it, some(broadcast_validation), data.phase0Data) + of ConsensusFork.Altair: + publishBlockV2(it, some(broadcast_validation), data.altairData) + of ConsensusFork.Bellatrix: + publishBlockV2(it, some(broadcast_validation), data.bellatrixData) + of ConsensusFork.Capella: + publishBlockV2(it, some(broadcast_validation), data.capellaData) + of ConsensusFork.Deneb: + publishBlockV2(it, some(broadcast_validation), data.denebData) + of ConsensusFork.Electra: + publishBlockV2(it, some(broadcast_validation), data.electraData) + do: + if apiResponse.isErr(): + handleCommunicationError() + ApiResponse[bool].err(apiResponse.error) + else: + let response = apiResponse.get() + case response.status: + of 200: + ApiResponse[bool].ok(true) + of 202: + debug BlockBroadcasted, node = node, + blck = shortLog(ForkedSignedBeaconBlock.init(data)) + ApiResponse[bool].ok(true) + of 400: + handle400() + ApiResponse[bool].err(ResponseInvalidError) + of 415: + handle415() + ApiResponse[bool].err(ResponseContentTypeError) + of 500: + handle500() + ApiResponse[bool].err(ResponseInternalError) + of 503: + handle503() + ApiResponse[bool].err(ResponseNoSyncError) + else: + handleUnexpectedCode() + ApiResponse[bool].err(ResponseUnexpectedError) + + if res.isErr(): + raise (ref ValidatorApiError)(msg: res.error, data: failures) + res.get() + + of ApiStrategyKind.Priority: + vc.firstSuccessSequential(RestPlainResponse, + SlotDuration, + ViableNodeStatus, + {BeaconNodeRole.BlockProposalPublish}): + case data.kind + of ConsensusFork.Phase0: + publishBlockV2(it, some(broadcast_validation), data.phase0Data) + of ConsensusFork.Altair: + publishBlockV2(it, some(broadcast_validation), data.altairData) + of ConsensusFork.Bellatrix: + publishBlockV2(it, some(broadcast_validation), data.bellatrixData) + of ConsensusFork.Capella: + publishBlockV2(it, some(broadcast_validation), data.capellaData) + of ConsensusFork.Deneb: + publishBlockV2(it, some(broadcast_validation), data.denebData) + of ConsensusFork.Electra: + publishBlockV2(it, some(broadcast_validation), data.electraData) + + do: + if apiResponse.isErr(): + handleCommunicationError() + false + else: + let response = apiResponse.get() + case response.status: + of 200: + return true + of 202: + debug BlockBroadcasted, node = node, + blck = shortLog(ForkedSignedBeaconBlock.init(data)) + return true + of 400: + handle400() + false + of 415: + handle415() + false + of 500: + handle500() + false + of 503: + handle503() + false + else: + handleUnexpectedCode() + false + + raise (ref ValidatorApiError)( + msg: "Failed to publish block", data: failures) + proc publishBlock*( - vc: ValidatorClientRef, - data: RestPublishedSignedBlockContents, - strategy: ApiStrategyKind - ): Future[bool] {.async.} = + vc: ValidatorClientRef, + data: RestPublishedSignedBlockContents, + strategy: ApiStrategyKind +): Future[bool] {.async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "publishBlock" BlockBroadcasted = "Block not passed validation, but still published" @@ -2324,12 +2706,13 @@ proc publishBlock*( msg: "Failed to publish block", data: failures) proc produceBlindedBlock*( - vc: ValidatorClientRef, - slot: Slot, - randao_reveal: ValidatorSig, - graffiti: GraffitiBytes, - strategy: ApiStrategyKind - ): Future[ProduceBlindedBlockResponse] {.async.} = + vc: ValidatorClientRef, + slot: Slot, + randao_reveal: ValidatorSig, + graffiti: GraffitiBytes, + strategy: ApiStrategyKind +): Future[ProduceBlindedBlockResponse] {. + async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "produceBlindedBlock" @@ -2419,11 +2802,137 @@ proc produceBlindedBlock*( raise (ref ValidatorApiError)( msg: "Failed to produce blinded block", data: failures) +proc publishBlindedBlockV2*( + vc: ValidatorClientRef, + data: ForkedSignedBlindedBeaconBlock, + broadcast_validation: BroadcastValidationType, + strategy: ApiStrategyKind +): Future[bool] {.async: (raises: [CancelledError, ValidatorApiError]).} = + const + RequestName = "publishBlindedBlockV2" + BlockBroadcasted = "Block not passed validation, but still published" + + var failures: seq[ApiNodeFailure] + + case strategy + of ApiStrategyKind.First, ApiStrategyKind.Best: + let res = block: + vc.firstSuccessParallel(RestPlainResponse, + bool, + SlotDuration, + ViableNodeStatus, + {BeaconNodeRole.BlockProposalPublish}): + case data.kind + of ConsensusFork.Phase0: + publishBlindedBlockV2(it, some(broadcast_validation), + data.phase0Data) + of ConsensusFork.Altair: + publishBlindedBlockV2(it, some(broadcast_validation), + data.altairData) + of ConsensusFork.Bellatrix: + publishBlindedBlockV2(it, some(broadcast_validation), + data.bellatrixData) + of ConsensusFork.Capella: + publishBlindedBlockV2(it, some(broadcast_validation), + data.capellaData) + of ConsensusFork.Deneb: + publishBlindedBlockV2(it, some(broadcast_validation), + data.denebData) + of ConsensusFork.Electra: + publishBlindedBlockV2(it, some(broadcast_validation), + data.electraData) + do: + if apiResponse.isErr(): + handleCommunicationError() + ApiResponse[bool].err(apiResponse.error) + else: + let response = apiResponse.get() + case response.status: + of 200: + ApiResponse[bool].ok(true) + of 202: + debug BlockBroadcasted, node = node, blck = shortLog(data) + ApiResponse[bool].ok(true) + of 400: + handle400() + ApiResponse[bool].err(ResponseInvalidError) + of 415: + handle415() + ApiResponse[bool].err(ResponseContentTypeError) + of 500: + handle500() + ApiResponse[bool].err(ResponseInternalError) + of 503: + handle503() + ApiResponse[bool].err(ResponseNoSyncError) + else: + handleUnexpectedCode() + ApiResponse[bool].err(ResponseUnexpectedError) + + if res.isErr(): + raise (ref ValidatorApiError)(msg: res.error, data: failures) + res.get() + + of ApiStrategyKind.Priority: + vc.firstSuccessSequential(RestPlainResponse, + SlotDuration, + ViableNodeStatus, + {BeaconNodeRole.BlockProposalPublish}): + case data.kind + of ConsensusFork.Phase0: + publishBlindedBlockV2(it, some(broadcast_validation), + data.phase0Data) + of ConsensusFork.Altair: + publishBlindedBlockV2(it, some(broadcast_validation), + data.altairData) + of ConsensusFork.Bellatrix: + publishBlindedBlockV2(it, some(broadcast_validation), + data.bellatrixData) + of ConsensusFork.Capella: + publishBlindedBlockV2(it, some(broadcast_validation), + data.capellaData) + of ConsensusFork.Deneb: + publishBlindedBlockV2(it, some(broadcast_validation), + data.denebData) + of ConsensusFork.Electra: + publishBlindedBlockV2(it, some(broadcast_validation), + data.electraData) + do: + if apiResponse.isErr(): + handleCommunicationError() + false + else: + let response = apiResponse.get() + case response.status: + of 200: + return true + of 202: + debug BlockBroadcasted, node = node, blck = shortLog(data) + return true + of 400: + handle400() + false + of 415: + handle415() + false + of 500: + handle500() + false + of 503: + handle503() + false + else: + handleUnexpectedCode() + false + + raise (ref ValidatorApiError)( + msg: "Failed to publish blinded block", data: failures) + proc publishBlindedBlock*( - vc: ValidatorClientRef, - data: ForkedSignedBlindedBeaconBlock, - strategy: ApiStrategyKind - ): Future[bool] {.async.} = + vc: ValidatorClientRef, + data: ForkedSignedBlindedBeaconBlock, + strategy: ApiStrategyKind +): Future[bool] {.async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "publishBlindedBlock" BlockBroadcasted = "Block not passed validation, but still published" @@ -2527,9 +3036,9 @@ proc publishBlindedBlock*( msg: "Failed to publish blinded block", data: failures) proc prepareBeaconCommitteeSubnet*( - vc: ValidatorClientRef, - data: seq[RestCommitteeSubscription], - ): Future[int] {.async.} = + vc: ValidatorClientRef, + data: seq[RestCommitteeSubscription], +): Future[int] {.async: (raises: [CancelledError, ValidatorApiError]).} = logScope: request = "prepareBeaconCommitteeSubnet" let resp = vc.onceToAll(RestPlainResponse, SlotDuration, @@ -2571,9 +3080,9 @@ proc prepareBeaconCommitteeSubnet*( return count proc prepareSyncCommitteeSubnets*( - vc: ValidatorClientRef, - data: seq[RestSyncCommitteeSubscription], - ): Future[int] {.async.} = + vc: ValidatorClientRef, + data: seq[RestSyncCommitteeSubscription], +): Future[int] {.async: (raises: [CancelledError, ValidatorApiError]).} = logScope: request = "prepareSyncCommitteeSubnet" let resp = vc.onceToAll(RestPlainResponse, SlotDuration, @@ -2611,12 +3120,12 @@ proc prepareSyncCommitteeSubnets*( debug "Sync committee subnets preparation failed", status = response.status, endpoint = apiResponse.node, message = response.getErrorMessage() - return count + count proc prepareBeaconProposer*( - vc: ValidatorClientRef, - data: seq[PrepareBeaconProposer] - ): Future[int] {.async.} = + vc: ValidatorClientRef, + data: seq[PrepareBeaconProposer] +): Future[int] {.async: (raises: [CancelledError, ValidatorApiError]).} = logScope: request = "prepareBeaconProposer" let resp = vc.onceToAll(RestPlainResponse, SlotDuration, @@ -2653,12 +3162,12 @@ proc prepareBeaconProposer*( else: debug "Beacon proposer preparation failed", status = response.status, endpoint = apiResponse.node, reason = response.getErrorMessage() - return count + count proc registerValidator*( - vc: ValidatorClientRef, - data: seq[SignedValidatorRegistrationV1] - ): Future[int] {.async.} = + vc: ValidatorClientRef, + data: seq[SignedValidatorRegistrationV1] +): Future[int] {.async: (raises: [CancelledError, ValidatorApiError]).} = logScope: request = "registerValidators" let resp = vc.onceToAll(RestPlainResponse, SlotDuration, @@ -2699,9 +3208,10 @@ proc registerValidator*( return count proc getValidatorsLiveness*( - vc: ValidatorClientRef, epoch: Epoch, - validators: seq[ValidatorIndex] - ): Future[GetValidatorsLivenessResponse] {.async.} = + vc: ValidatorClientRef, + epoch: Epoch, + validators: seq[ValidatorIndex] +): Future[GetValidatorsLivenessResponse] {.async: (raises: [CancelledError]).} = const RequestName = "getLiveness" let resp = vc.onceToAll(RestPlainResponse, @@ -2808,8 +3318,8 @@ proc getValidatorsLiveness*( return GetValidatorsLivenessResponse(data: response) proc getFinalizedBlockHeader*( - vc: ValidatorClientRef, - ): Future[Opt[GetBlockHeaderResponse]] {.async.} = + vc: ValidatorClientRef, +): Future[Opt[GetBlockHeaderResponse]] {.async: (raises: [CancelledError]).} = const RequestName = "getFinalizedBlockHeader" let @@ -2897,10 +3407,11 @@ proc getFinalizedBlockHeader*( return Opt.none(GetBlockHeaderResponse) proc submitBeaconCommitteeSelections*( - vc: ValidatorClientRef, - data: seq[RestBeaconCommitteeSelection], - strategy: ApiStrategyKind - ): Future[SubmitBeaconCommitteeSelectionsResponse] {.async.} = + vc: ValidatorClientRef, + data: seq[RestBeaconCommitteeSelection], + strategy: ApiStrategyKind +): Future[SubmitBeaconCommitteeSelectionsResponse] {. + async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "submitBeaconCommitteeSelections" @@ -2993,10 +3504,11 @@ proc submitBeaconCommitteeSelections*( msg: "Failed to submit beacon committee selections", data: failures) proc submitSyncCommitteeSelections*( - vc: ValidatorClientRef, - data: seq[RestSyncCommitteeSelection], - strategy: ApiStrategyKind - ): Future[SubmitSyncCommitteeSelectionsResponse] {.async.} = + vc: ValidatorClientRef, + data: seq[RestSyncCommitteeSelection], + strategy: ApiStrategyKind +): Future[SubmitSyncCommitteeSelectionsResponse] {. + async: (raises: [CancelledError, ValidatorApiError]).} = const RequestName = "submitBeaconCommitteeSelections" diff --git a/beacon_chain/validator_client/attestation_service.nim b/beacon_chain/validator_client/attestation_service.nim index 33feb85dd4..9e215feebd 100644 --- a/beacon_chain/validator_client/attestation_service.nim +++ b/beacon_chain/validator_client/attestation_service.nim @@ -22,72 +22,90 @@ type AggregateItem* = object aggregator_index: uint64 selection_proof: ValidatorSig + committee_index: CommitteeIndex validator: AttachedValidator +func getAttesterDutiesByCommittee( + duties: openArray[DutyAndProof] +): Table[CommitteeIndex, seq[DutyAndProof]] = + var res: Table[CommitteeIndex, seq[DutyAndProof]] + for item in duties: + res.mgetOrPut(item.data.committee_index, default(seq[DutyAndProof])). + add(item) + res + proc serveAttestation( - service: AttestationServiceRef, registered: RegisteredAttestation): - Future[bool] {.async.} = + service: AttestationServiceRef, + registered: RegisteredAttestation +): Future[bool] {.async: (raises: [CancelledError]).} = let vc = service.client fork = vc.forkAtEpoch(registered.data.slot.epoch) validator = registered.validator + attestationSlot = registered.data.slot + afterElectra = vc.isPastElectraFork(attestationSlot.epoch) logScope: validator = validatorLog(validator) - let attestation = block: - let signature = - try: - let res = await validator.getAttestationSignature( + let signature = + try: + let res = + await validator.getAttestationSignature( fork, vc.beaconGenesis.genesis_validators_root, registered.data) - if res.isErr(): - warn "Unable to sign attestation", reason = res.error() - return false - res.get() - except CancelledError as exc: - debug "Attestation signature process was interrupted" - raise exc - except CatchableError as exc: - error "An unexpected error occurred while signing attestation", - err_name = exc.name, err_msg = exc.msg + if res.isErr(): + warn "Unable to sign attestation", reason = res.error() return false - registered.toAttestation(signature) + res.get() + except CancelledError as exc: + debug "Attestation signature process was interrupted" + raise exc logScope: - attestation = shortLog(attestation) - delay = vc.getDelay(registered.data.slot.attestation_deadline()) + delay = vc.getDelay(attestationSlot.attestation_deadline()) debug "Sending attestation" - validator.doppelgangerActivity(attestation.data.slot.epoch) + validator.doppelgangerActivity(attestationSlot.epoch) - let res = + template submitAttestation(atst: untyped): untyped = + logScope: + attestation = shortLog(atst) try: - await vc.submitPoolAttestations(@[attestation], ApiStrategyKind.First) + when atst is electra.Attestation: + await vc.submitPoolAttestationsV2(@[atst], ApiStrategyKind.First) + else: + await vc.submitPoolAttestations(@[atst], ApiStrategyKind.First) except ValidatorApiError as exc: warn "Unable to publish attestation", reason = exc.getFailureReason() return false except CancelledError as exc: debug "Attestation publishing process was interrupted" raise exc - except CatchableError as exc: - error "Unexpected error occured while publishing attestation", - err_name = exc.name, err_msg = exc.msg - return false + + let res = + if afterElectra: + let attestation = registered.toElectraAttestation(signature) + submitAttestation(attestation) + else: + let attestation = registered.toAttestation(signature) + submitAttestation(attestation) if res: - let delay = vc.getDelay(attestation.data.slot.attestation_deadline()) + let delay = vc.getDelay(attestationSlot.attestation_deadline()) beacon_attestations_sent.inc() beacon_attestation_sent_delay.observe(delay.toFloatSeconds()) notice "Attestation published" else: warn "Attestation was not accepted by beacon node" - return res -proc serveAggregateAndProof*(service: AttestationServiceRef, - proof: phase0.AggregateAndProof, - validator: AttachedValidator): Future[bool] {. - async.} = + res + +proc serveAggregateAndProof*( + service: AttestationServiceRef, + proof: phase0.AggregateAndProof, + validator: AttachedValidator +): Future[bool] {.async: (raises: [CancelledError]).} = let vc = service.client genesisRoot = vc.beaconGenesis.genesis_validators_root @@ -112,10 +130,6 @@ proc serveAggregateAndProof*(service: AttestationServiceRef, except CancelledError as exc: debug "Aggregated attestation signing process was interrupted" raise exc - except CatchableError as exc: - error "Unexpected error occured while signing aggregated attestation", - err_name = exc.name, err_msg = exc.msg - return false let signedProof = phase0.SignedAggregateAndProof( message: proof, signature: signature) @@ -136,10 +150,6 @@ proc serveAggregateAndProof*(service: AttestationServiceRef, except CancelledError as exc: debug "Publish aggregate and proofs request was interrupted" raise exc - except CatchableError as exc: - error "Unexpected error occured while publishing aggregated attestation", - err_name = exc.name, err_msg = exc.msg - return false if res: beacon_aggregates_sent.inc() @@ -148,18 +158,85 @@ proc serveAggregateAndProof*(service: AttestationServiceRef, warn "Aggregated attestation was not accepted by beacon node" return res -proc produceAndPublishAttestations*(service: AttestationServiceRef, - slot: Slot, committee_index: CommitteeIndex, - duties: seq[DutyAndProof] - ): Future[AttestationData] {. - async.} = +proc serveAggregateAndProofV2*( + service: AttestationServiceRef, + proof: ForkyAggregateAndProof, + validator: AttachedValidator +): Future[bool] {.async: (raises: [CancelledError]).} = + let + vc = service.client + genesisRoot = vc.beaconGenesis.genesis_validators_root + slot = proof.aggregate.data.slot + fork = vc.forkAtEpoch(slot.epoch) + + logScope: + validator = validatorLog(validator) + attestation = shortLog(proof.aggregate) + + debug "Signing aggregate", fork = fork + + let signature = + try: + let res = + await validator.getAggregateAndProofSignature(fork, genesisRoot, proof) + if res.isErr(): + warn "Unable to sign aggregate and proof using remote signer", + reason = res.error() + return false + res.get() + except CancelledError as exc: + debug "Aggregated attestation signing process was interrupted" + raise exc + + let signedProof = + when proof is phase0.AggregateAndProof: + phase0.SignedAggregateAndProof( + message: proof, signature: signature) + elif proof is electra.AggregateAndProof: + electra.SignedAggregateAndProof( + message: proof, signature: signature) + else: + static: + raiseAssert "Unsupported SignedAggregateAndProof" + + logScope: + delay = vc.getDelay(slot.aggregate_deadline()) + + debug "Sending aggregated attestation", fork = fork + + validator.doppelgangerActivity(proof.aggregate.data.slot.epoch) + + let res = + try: + await vc.publishAggregateAndProofsV2(@[signedProof], + ApiStrategyKind.First) + except ValidatorApiError as exc: + warn "Unable to publish aggregated attestation", + reason = exc.getFailureReason() + return false + except CancelledError as exc: + debug "Publish aggregate and proofs request was interrupted" + raise exc + + if res: + beacon_aggregates_sent.inc() + notice "Aggregated attestation published" + else: + warn "Aggregated attestation was not accepted by beacon node" + res + +proc produceAndPublishAttestations*( + service: AttestationServiceRef, + slot: Slot, + committee_index: CommitteeIndex, + duties: seq[DutyAndProof] +): Future[AttestationData] {. + async: (raises: [CancelledError, ValidatorApiError]).} = doAssert(MAX_VALIDATORS_PER_COMMITTEE <= uint64(high(int))) let vc = service.client fork = vc.forkAtEpoch(slot.epoch) - # This call could raise ValidatorApiError, but it is handled in - # publishAttestationsAndAggregates(). let data = await vc.produceAttestationData(slot, committee_index, ApiStrategyKind.Best) @@ -202,6 +279,7 @@ proc produceAndPublishAttestations*(service: AttestationServiceRef, tmp.add(RegisteredAttestation( validator: validator, + committee_index: duty.data.committee_index, index_in_committee: duty.data.validator_committee_index, committee_len: int duty.data.committee_length, data: data @@ -227,7 +305,7 @@ proc produceAndPublishAttestations*(service: AttestationServiceRef, for future in pendingAttestations: if future.completed(): - if future.read(): + if future.value: inc(succeed) else: inc(failed) @@ -241,11 +319,13 @@ proc produceAndPublishAttestations*(service: AttestationServiceRef, not_accepted = statistics[2], delay = delay, slot = slot, committee_index = committee_index, duties_count = len(duties) - return data + data -proc produceAndPublishAggregates(service: AttestationServiceRef, - adata: AttestationData, - duties: seq[DutyAndProof]) {.async.} = +proc produceAndPublishAggregates( + service: AttestationServiceRef, + adata: AttestationData, + duties: seq[DutyAndProof] +) {.async: (raises: [CancelledError]).} = let vc = service.client slot = adata.slot @@ -272,39 +352,35 @@ proc produceAndPublishAggregates(service: AttestationServiceRef, if is_aggregator(duty.data.committee_length, slotSignature): res.add(AggregateItem( aggregator_index: uint64(duty.data.validator_index), + committee_index: CommitteeIndex(committeeIndex), selection_proof: slotSignature, validator: validator )) res if len(aggregateItems) > 0: - let aggAttestation = - try: - await vc.getAggregatedAttestation(slot, attestationRoot, - ApiStrategyKind.Best) - except ValidatorApiError as exc: - warn "Unable to get aggregated attestation data", slot = slot, - attestation_root = shortLog(attestationRoot), - reason = exc.getFailureReason() - return - except CancelledError as exc: - debug "Aggregated attestation request was interrupted" - raise exc - except CatchableError as exc: - error "Unexpected error occured while getting aggregated attestation", - slot = slot, attestation_root = shortLog(attestationRoot), - err_name = exc.name, err_msg = exc.msg - return + let aggregates = + block: + let aggAttestation = + try: + await vc.getAggregatedAttestation(slot, attestationRoot, + ApiStrategyKind.Best) + except ValidatorApiError as exc: + warn "Unable to get aggregated attestation data", slot = slot, + attestation_root = shortLog(attestationRoot), + reason = exc.getFailureReason() + return + except CancelledError as exc: + debug "Aggregated attestation request was interrupted" + raise exc - if isLowestScoreAggregatedAttestation(aggAttestation): - warn "Aggregated attestation with the root was not seen by the " & - "beacon node", - attestation_root = shortLog(attestationRoot) - return + if isLowestScoreAggregatedAttestation(aggAttestation): + warn "Aggregated attestation with the root was not seen by the " & + "beacon node", + attestation_root = shortLog(attestationRoot) + return - let pendingAggregates = - block: - var res: seq[Future[bool]] + var res: seq[Future[bool].Raising([CancelledError])] for item in aggregateItems: let proof = phase0.AggregateAndProof( aggregator_index: item.aggregator_index, @@ -318,16 +394,16 @@ proc produceAndPublishAggregates(service: AttestationServiceRef, block: var errored, succeed, failed = 0 try: - await allFutures(pendingAggregates) + await allFutures(aggregates) except CancelledError as exc: - let pending = pendingAggregates + let pending = aggregates .filterIt(not(it.finished())).mapIt(it.cancelAndWait()) await noCancel allFutures(pending) raise exc - for future in pendingAggregates: + for future in aggregates: if future.completed(): - if future.read(): + if future.value: inc(succeed) else: inc(failed) @@ -336,7 +412,7 @@ proc produceAndPublishAggregates(service: AttestationServiceRef, (succeed, errored, failed) let delay = vc.getDelay(slot.aggregate_deadline()) - debug "Aggregated attestation statistics", total = len(pendingAggregates), + debug "Aggregated attestation statistics", total = len(aggregates), succeed = statistics[0], failed_to_deliver = statistics[1], not_accepted = statistics[2], delay = delay, slot = slot, committee_index = committeeIndex @@ -345,10 +421,12 @@ proc produceAndPublishAggregates(service: AttestationServiceRef, debug "No aggregate and proofs scheduled for slot", slot = slot, committee_index = committeeIndex -proc publishAttestationsAndAggregates(service: AttestationServiceRef, - slot: Slot, - committee_index: CommitteeIndex, - duties: seq[DutyAndProof]) {.async.} = +proc publishAttestationsAndAggregates( + service: AttestationServiceRef, + slot: Slot, + committee_index: CommitteeIndex, + duties: seq[DutyAndProof] +) {.async: (raises: [CancelledError]).} = let vc = service.client block: @@ -367,11 +445,6 @@ proc publishAttestationsAndAggregates(service: AttestationServiceRef, except CancelledError as exc: debug "Publish attestation request was interrupted" raise exc - except CatchableError as exc: - error "Unexpected error while producing attestations", slot = slot, - committee_index = committee_index, duties_count = len(duties), - err_name = exc.name, err_msg = exc.msg - return let aggregateTime = # chronos.Duration substraction could not return negative value, in such @@ -385,22 +458,274 @@ proc publishAttestationsAndAggregates(service: AttestationServiceRef, debug "Producing aggregate and proofs", delay = delay await service.produceAndPublishAggregates(ad, duties) -proc spawnAttestationTasks(service: AttestationServiceRef, - slot: Slot) {.async.} = - let vc = service.client - let dutiesByCommittee = +proc produceAndPublishAttestationsV2*( + service: AttestationServiceRef, + slot: Slot, + duties: seq[DutyAndProof] +): Future[AttestationData] {. + async: (raises: [CancelledError, ValidatorApiError]).} = + doAssert(MAX_VALIDATORS_PER_COMMITTEE <= uint64(high(int))) + let + vc = service.client + fork = vc.forkAtEpoch(slot.epoch) + data = await vc.produceAttestationData(slot, CommitteeIndex(0), + ApiStrategyKind.Best) + registeredRes = + vc.attachedValidators[].slashingProtection.withContext: + var tmp: seq[RegisteredAttestation] + for duty in duties: + if (duty.data.slot != data.slot): + warn "Inconsistent validator duties during attestation signing", + pubkey = shortLog(duty.data.pubkey), + duty_slot = duty.data.slot, + duty_index = duty.data.committee_index, + attestation_slot = data.slot + continue + + let validator = + vc.getValidatorForDuties(duty.data.pubkey, duty.data.slot).valueOr: + continue + + doAssert(validator.index.isSome()) + let validator_index = validator.index.get() + + logScope: + validator = validatorLog(validator) + + # TODO: signing_root is recomputed in getAttestationSignature just + # after, but not for locally attached validators. + let + signingRoot = + compute_attestation_signing_root( + fork, vc.beaconGenesis.genesis_validators_root, data) + registered = + registerAttestationInContext( + validator_index, validator.pubkey, data.source.epoch, + data.target.epoch, signingRoot) + + if registered.isErr(): + warn "Slashing protection activated for attestation", + attestationData = shortLog(data), + signingRoot = shortLog(signingRoot), + badVoteDetails = $registered.error() + continue + + tmp.add(RegisteredAttestation( + validator: validator, + committee_index: duty.data.committee_index, + index_in_committee: duty.data.validator_committee_index, + committee_len: int(duty.data.committee_length), + data: data + )) + tmp + + if registeredRes.isErr(): + warn "Could not update slashing database, skipping attestation duties", + reason = registeredRes.error() + return + + let + pendingAttestations = registeredRes[].mapIt(service.serveAttestation(it)) + statistics = + block: + var errored, succeed, failed = 0 + try: + await allFutures(pendingAttestations) + except CancelledError as exc: + let pending = pendingAttestations + .filterIt(not(it.finished())).mapIt(it.cancelAndWait()) + await noCancel allFutures(pending) + raise exc + + for future in pendingAttestations: + if future.completed(): + if future.value: + inc(succeed) + else: + inc(failed) + else: + inc(errored) + (succeed, errored, failed) + + delay = vc.getDelay(slot.attestation_deadline()) + + debug "Attestation statistics", total = len(pendingAttestations), + succeed = statistics[0], failed_to_deliver = statistics[1], + not_accepted = statistics[2], delay = delay, slot = slot, + duties_count = len(duties) + data + +proc produceAndPublishAggregatesV2( + service: AttestationServiceRef, + adata: AttestationData, + duties: seq[DutyAndProof] +) {.async: (raises: [CancelledError]).} = + let + vc = service.client + slot = adata.slot + attestationRoot = adata.hash_tree_root() + + let aggregateItems = + block: + var res: seq[AggregateItem] + for duty in duties: + let validator = + vc.getValidatorForDuties(duty.data.pubkey, slot).valueOr: + continue + + if duty.data.slot != slot: + warn "Inconsistent validator duties during aggregate signing", + duty_slot = duty.data.slot, slot = slot, + duty_committee_index = duty.data.committee_index + continue + if duty.slotSig.isSome(): + let slotSignature = duty.slotSig.get() + if is_aggregator(duty.data.committee_length, slotSignature): + res.add(AggregateItem( + aggregator_index: uint64(duty.data.validator_index), + committee_index: duty.data.committee_index, + selection_proof: slotSignature, + validator: validator + )) + res + + if len(aggregateItems) == 0: + debug "No aggregate and proofs scheduled for slot", slot = slot + return + + # All duties should be sorted by `committee_index`. + let committee_index = duties[0].data.committee_index + + let aggregates = block: - var res: Table[CommitteeIndex, seq[DutyAndProof]] - let attesters = vc.getAttesterDutiesForSlot(slot) - var default: seq[DutyAndProof] - for item in attesters: - res.mgetOrPut(item.data.committee_index, default).add(item) + let attestation = + try: + await vc.getAggregatedAttestationV2(slot, attestationRoot, + committee_index, + ApiStrategyKind.Best) + except ValidatorApiError as exc: + warn "Unable to get aggregated attestation data", slot = slot, + attestation_root = shortLog(attestationRoot), + reason = exc.getFailureReason() + return + except CancelledError as exc: + debug "Aggregated attestation request was interrupted" + raise exc + + if isLowestScoreAggregatedAttestation(attestation): + warn "Aggregated attestation with the root was not seen by the " & + "beacon node", + attestation_root = shortLog(attestationRoot) + return + + var res: seq[Future[bool].Raising([CancelledError])] + for item in aggregateItems: + withAttestation(attestation): + when consensusFork > ConsensusFork.Deneb: + let proof = + electra.AggregateAndProof( + aggregator_index: item.aggregator_index, + aggregate: forkyAttestation, + selection_proof: item.selection_proof + ) + res.add(service.serveAggregateAndProofV2(proof, item.validator)) + else: + let proof = + phase0.AggregateAndProof( + aggregator_index: item.aggregator_index, + aggregate: forkyAttestation, + selection_proof: item.selection_proof + ) + res.add(service.serveAggregateAndProofV2(proof, item.validator)) res + let statistics = + block: + var errored, succeed, failed = 0 + try: + await allFutures(aggregates) + except CancelledError as exc: + let pending = aggregates + .filterIt(not(it.finished())).mapIt(it.cancelAndWait()) + await noCancel allFutures(pending) + raise exc + + for future in aggregates: + if future.completed(): + if future.value: + inc(succeed) + else: + inc(failed) + else: + inc(errored) + (succeed, errored, failed) + + let delay = vc.getDelay(slot.aggregate_deadline()) + debug "Aggregated attestation statistics", total = len(aggregates), + succeed = statistics[0], failed_to_deliver = statistics[1], + not_accepted = statistics[2], delay = delay, slot = slot, + committee_index = committeeIndex + +proc publishAttestationsAndAggregatesV2( + service: AttestationServiceRef, + slot: Slot, + duties: seq[DutyAndProof] +) {.async: (raises: [CancelledError]).} = + let + vc = service.client + + block: + let delay = vc.getDelay(slot.attestation_deadline()) + debug "Producing attestations", delay = delay, slot = slot, + duties_count = len(duties) + + let ad = + try: + await service.produceAndPublishAttestationsV2(slot, duties) + except ValidatorApiError as exc: + warn "Unable to proceed attestations", slot = slot, + duties_count = len(duties), reason = exc.getFailureReason() + return + except CancelledError as exc: + debug "Publish attestation request was interrupted" + raise exc + + let aggregateTime = + # chronos.Duration substraction could not return negative value, in such + # case it will return `ZeroDuration`. + vc.beaconClock.durationToNextSlot() - OneThirdDuration + if aggregateTime != ZeroDuration: + await sleepAsync(aggregateTime) + + block: + let + delay = vc.getDelay(slot.aggregate_deadline()) + dutiesByCommittee = getAttesterDutiesByCommittee(duties) + debug "Producing aggregate and proofs", delay = delay + var tasks: seq[Future[void].Raising([CancelledError])] + try: + for index, cduties in dutiesByCommittee: + tasks.add(service.produceAndPublishAggregatesV2(ad, cduties)) + await allFutures(tasks) + except CancelledError as exc: + # Cancelling all the pending tasks. + let pending = tasks.filterIt(not(it.finished())).mapIt(it.cancelAndWait()) + await noCancel allFutures(pending) + raise exc + +proc spawnAttestationTasks( + service: AttestationServiceRef, + slot: Slot +) {.async: (raises: [CancelledError]).} = + let + vc = service.client + dutiesByCommittee = + getAttesterDutiesByCommittee(vc.getAttesterDutiesForSlot(slot)) + # Waiting for blocks to be published before attesting. await vc.waitForBlock(slot, attestationSlotOffset) - var tasks: seq[Future[void]] + var tasks: seq[Future[void].Raising([CancelledError])] try: for index, duties in dutiesByCommittee: tasks.add(service.publishAttestationsAndAggregates(slot, index, duties)) @@ -415,11 +740,28 @@ proc spawnAttestationTasks(service: AttestationServiceRef, let pending = tasks.filterIt(not(it.finished())).mapIt(it.cancelAndWait()) await noCancel allFutures(pending) raise exc - except CatchableError as exc: - error "Unexpected error while processing attestation duties", - error_name = exc.name, error_message = exc.msg -proc mainLoop(service: AttestationServiceRef) {.async.} = +proc spawnAttestationTasksV2( + service: AttestationServiceRef, + slot: Slot +) {.async: (raises: [CancelledError]).} = + let + vc = service.client + duties = vc.getAttesterDutiesForSlot(slot) + + # Waiting for blocks to be published before attesting. + await vc.waitForBlock(slot, attestationSlotOffset) + + try: + let timeout = vc.beaconClock.durationToNextSlot() + await service.publishAttestationsAndAggregatesV2(slot, duties).wait(timeout) + except AsyncTimeoutError: + discard + except CancelledError as exc: + # Cancelling all the pending tasks. + raise exc + +proc mainLoop(service: AttestationServiceRef) {.async: (raises: []).} = let vc = service.client service.state = ServiceState.Running debug "Service started" @@ -435,50 +777,39 @@ proc mainLoop(service: AttestationServiceRef) {.async.} = except CancelledError: debug "Service interrupted" return - except CatchableError as exc: - warn "Service crashed with unexpected error", err_name = exc.name, - err_msg = exc.msg - return doAssert(len(vc.forks) > 0, "Fork schedule must not be empty at this point") var currentSlot: Opt[Slot] while true: - # This loop could look much more nicer/better, when - # https://github.com/nim-lang/Nim/issues/19911 will be fixed, so it could - # become safe to combine loops, breaks and exception handlers. - let breakLoop = - try: - let - # We use zero offset here, because we do waiting in - # waitForBlock(attestationSlotOffset). - slot = await vc.checkedWaitForNextSlot(currentSlot, - ZeroTimeDiff, false) - if slot.isNone(): - debug "System time adjusted backwards significantly, exiting" - true - else: - currentSlot = slot - await service.spawnAttestationTasks(currentSlot.get()) - false - except CancelledError: - debug "Service interrupted" - true - except CatchableError as exc: - warn "Service crashed with unexpected error", err_name = exc.name, - err_msg = exc.msg - true - - if breakLoop: - break - -proc init*(t: typedesc[AttestationServiceRef], - vc: ValidatorClientRef): Future[AttestationServiceRef] {.async.} = + try: + let + # We use zero offset here, because we do waiting in + # waitForBlock(attestationSlotOffset). + slot = await vc.checkedWaitForNextSlot(currentSlot, + ZeroTimeDiff, false) + if slot.isNone(): + debug "System time adjusted backwards significantly, exiting" + return + + currentSlot = slot + if vc.isPastElectraFork(currentSlot.get().epoch()): + await service.spawnAttestationTasksV2(currentSlot.get()) + else: + await service.spawnAttestationTasks(currentSlot.get()) + except CancelledError: + debug "Service interrupted" + return + +proc init*( + t: typedesc[AttestationServiceRef], + vc: ValidatorClientRef +): Future[AttestationServiceRef] {.async: (raises: []).} = logScope: service = ServiceName let res = AttestationServiceRef(name: ServiceName, client: vc, state: ServiceState.Initialized) debug "Initializing service" - return res + res proc start*(service: AttestationServiceRef) = service.lifeFut = mainLoop(service) diff --git a/beacon_chain/validator_client/block_service.nim b/beacon_chain/validator_client/block_service.nim index df0d37a4d7..4f2fbe2762 100644 --- a/beacon_chain/validator_client/block_service.nim +++ b/beacon_chain/validator_client/block_service.nim @@ -22,17 +22,6 @@ const logScope: service = ServiceName -type - PreparedBeaconBlock = object - blockRoot*: Eth2Digest - data*: ForkedBeaconBlock - kzgProofsOpt*: Opt[deneb.KzgProofs] - blobsOpt*: Opt[deneb.Blobs] - - PreparedBlindedBeaconBlock = object - blockRoot*: Eth2Digest - data*: ForkedBlindedBeaconBlock - func shortLog(v: Opt[UInt256]): auto = if v.isNone(): "" else: toString(v.get, 10) @@ -46,12 +35,20 @@ func shortLog(v: ForkedMaybeBlindedBeaconBlock): auto = else: shortLog(forkyMaybeBlindedBlck.`block`) -proc proposeBlock(vc: ValidatorClientRef, slot: Slot, - proposerKey: ValidatorPubKey) {.async.} - -proc prepareRandao(vc: ValidatorClientRef, slot: Slot, - proposerKey: ValidatorPubKey) {.async.} = - if slot == GENESIS_SLOT: +proc proposeBlock( + vc: ValidatorClientRef, + slot: Slot, + proposerKey: ValidatorPubKey +) {.async: (raises: [CancelledError]).} + +proc prepareRandao( + vc: ValidatorClientRef, + slot: Slot, + proposerKey: ValidatorPubKey +) {.async: (raises: [CancelledError]).} = + if slot == vc.beaconClock.now().slotOrZero(): + # Its impossible to prepare RANDAO in the beginning of the epoch. Epoch + # signature will be requested by block proposer. return let @@ -101,9 +98,13 @@ proc spawnProposalTask(vc: ValidatorClientRef, duty: duty ) -proc publishBlockV3(vc: ValidatorClientRef, currentSlot, slot: Slot, - fork: Fork, randaoReveal: ValidatorSig, - validator: AttachedValidator) {.async.} = +proc publishBlockV3( + vc: ValidatorClientRef, + currentSlot, slot: Slot, + fork: Fork, + randaoReveal: ValidatorSig, + validator: AttachedValidator +) {.async: (raises: [CancelledError]).} = let genesisRoot = vc.beaconGenesis.genesis_validators_root graffiti = @@ -131,10 +132,6 @@ proc publishBlockV3(vc: ValidatorClientRef, currentSlot, slot: Slot, except CancelledError as exc: debug "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 withForkyMaybeBlindedBlck(maybeBlock): when isBlinded: @@ -177,10 +174,6 @@ proc publishBlockV3(vc: ValidatorClientRef, currentSlot, slot: Slot, 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 let signedBlock = @@ -189,7 +182,13 @@ proc publishBlockV3(vc: ValidatorClientRef, currentSlot, slot: Slot, res = try: debug "Sending blinded block" - await vc.publishBlindedBlock(signedBlock, ApiStrategyKind.First) + if vc.isPastElectraFork(slot.epoch()): + await vc.publishBlindedBlockV2( + signedBlock, BroadcastValidationType.Gossip, + ApiStrategyKind.First) + else: + await vc.publishBlindedBlock( + signedBlock, ApiStrategyKind.First) except ValidatorApiError as exc: warn "Unable to publish blinded block", reason = exc.getFailureReason() @@ -197,11 +196,6 @@ proc publishBlockV3(vc: ValidatorClientRef, currentSlot, slot: Slot, 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()) @@ -260,10 +254,6 @@ proc publishBlockV3(vc: ValidatorClientRef, currentSlot, slot: Slot, 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 signedBlockContents = RestPublishedSignedBlockContents.init( @@ -272,17 +262,19 @@ proc publishBlockV3(vc: ValidatorClientRef, currentSlot, slot: Slot, res = try: debug "Sending block" - await vc.publishBlock(signedBlockContents, ApiStrategyKind.First) + if vc.isPastElectraFork(slot.epoch()): + await vc.publishBlockV2( + signedBlockContents, BroadcastValidationType.Gossip, + ApiStrategyKind.First) + else: + await vc.publishBlock( + signedBlockContents, ApiStrategyKind.First) except ValidatorApiError as exc: warn "Unable to publish block", reason = exc.getFailureReason() 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()) @@ -292,8 +284,11 @@ proc publishBlockV3(vc: ValidatorClientRef, currentSlot, slot: Slot, else: warn "Block was not accepted by beacon node" -proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot, - validator: AttachedValidator) {.async.} = +proc publishBlock( + vc: ValidatorClientRef, + currentSlot, slot: Slot, + validator: AttachedValidator +) {.async: (raises: [CancelledError]).} = let genesisRoot = vc.beaconGenesis.genesis_validators_root graffiti = vc.getGraffitiBytes(validator) @@ -320,15 +315,14 @@ proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot, except CancelledError as exc: debug "RANDAO reveal production has been interrupted" raise exc - except CatchableError as exc: - error "An unexpected error occurred while receiving RANDAO data", - error_name = exc.name, error_msg = exc.msg - return await vc.publishBlockV3(currentSlot, slot, fork, randaoReveal, validator) -proc proposeBlock(vc: ValidatorClientRef, slot: Slot, - proposerKey: ValidatorPubKey) {.async.} = +proc proposeBlock( + vc: ValidatorClientRef, + slot: Slot, + proposerKey: ValidatorPubKey +) {.async: (raises: [CancelledError]).} = let currentSlot = (await vc.checkedWaitForSlot(slot, ZeroTimeDiff, false)).valueOr: @@ -348,9 +342,6 @@ proc proposeBlock(vc: ValidatorClientRef, slot: Slot, debug "Block proposing process was interrupted", slot = slot, validator = validatorLog(validator) raise exc - except CatchableError: - error "Unexpected error encountered while proposing block", - slot = slot, validator = validatorLog(validator) proc contains(data: openArray[RestProposerDuty], task: ProposerTask): bool = for item in data: @@ -451,7 +442,8 @@ proc addOrReplaceProposers*(vc: ValidatorClientRef, epoch: Epoch, vc.proposers[epoch] = ProposedData.init(epoch, dependentRoot, tasks) proc pollForEvents(service: BlockServiceRef, node: BeaconNodeServerRef, - response: RestHttpResponseRef) {.async.} = + response: RestHttpResponseRef) {. + async: (raises: [CancelledError]).} = let vc = service.client logScope: @@ -461,15 +453,14 @@ proc pollForEvents(service: BlockServiceRef, node: BeaconNodeServerRef, let events = try: await response.getServerSentEvents() + except HttpError as exc: + debug "Unable to receive server-sent event", reason = $exc.msg + return except RestError as exc: debug "Unable to receive server-sent event", reason = $exc.msg return except CancelledError as exc: raise exc - except CatchableError as exc: - warn "Got an unexpected error, " & - "while reading server-sent event stream", reason = $exc.msg - return for event in events: case event.name @@ -489,7 +480,8 @@ proc pollForEvents(service: BlockServiceRef, node: BeaconNodeServerRef, break proc runBlockEventMonitor(service: BlockServiceRef, - node: BeaconNodeServerRef) {.async.} = + node: BeaconNodeServerRef) {. + async: (raises: [CancelledError]).} = let vc = service.client roles = {BeaconNodeRole.BlockProposalData} @@ -519,6 +511,9 @@ proc runBlockEventMonitor(service: BlockServiceRef, debug "Unable to obtain events stream", code = resp.status, reason = reason Opt.none(HttpClientResponseRef) + except HttpError as exc: + debug "Unable to obtain events stream", reason = $exc.msg + Opt.none(HttpClientResponseRef) except RestError as exc: if not(isNil(resp)): await resp.closeWait() debug "Unable to obtain events stream", reason = $exc.msg @@ -527,11 +522,6 @@ proc runBlockEventMonitor(service: BlockServiceRef, if not(isNil(resp)): await resp.closeWait() debug "Block monitoring loop has been interrupted" raise exc - except CatchableError as exc: - if not(isNil(resp)): await resp.closeWait() - warn "Got an unexpected error while trying to establish event stream", - reason = $exc.msg - Opt.none(HttpClientResponseRef) if response.isSome(): debug "Block monitoring connection has been established" @@ -539,16 +529,14 @@ proc runBlockEventMonitor(service: BlockServiceRef, await service.pollForEvents(node, response.get()) except CancelledError as exc: raise exc - except CatchableError as exc: - warn "Got an unexpected error while receiving block events", - reason = $exc.msg finally: debug "Block monitoring connection has been lost" await response.get().closeWait() proc pollForBlockHeaders(service: BlockServiceRef, node: BeaconNodeServerRef, slot: Slot, waitTime: Duration, - index: int): Future[bool] {.async.} = + index: int): Future[bool] {. + async: (raises: [CancelledError]).} = let vc = service.client logScope: @@ -573,10 +561,6 @@ proc pollForBlockHeaders(service: BlockServiceRef, node: BeaconNodeServerRef, return false except CancelledError as exc: raise exc - except CatchableError as exc: - warn "Unexpected error encountered while receiving block header", - reason = $exc.msg, error = $exc.name - return false if bres.isNone(): trace "Beacon node does not yet have block" @@ -590,10 +574,11 @@ proc pollForBlockHeaders(service: BlockServiceRef, node: BeaconNodeServerRef, optimistic: blockHeader.execution_optimistic ) vc.registerBlock(eventBlock, node) - return true + true proc runBlockPollMonitor(service: BlockServiceRef, - node: BeaconNodeServerRef) {.async.} = + node: BeaconNodeServerRef) {. + async: (raises: [CancelledError]).} = let vc = service.client roles = {BeaconNodeRole.BlockProposalData} @@ -603,11 +588,9 @@ proc runBlockPollMonitor(service: BlockServiceRef, node = node while true: - let currentSlot = - block: - let res = await vc.checkedWaitForNextSlot(ZeroTimeDiff, false) - if res.isNone(): continue - res.geT() + let currentSlot {.used.} = + (await vc.checkedWaitForNextSlot(ZeroTimeDiff, false)).valueOr: + continue while node.status notin statuses: await vc.waitNodes(nil, statuses, roles, true) @@ -643,13 +626,17 @@ proc runBlockPollMonitor(service: BlockServiceRef, res try: while true: - let completedFuture = await race(pendingTasks) + let completedFuture = + try: + await race(pendingTasks) + except ValueError: + raiseAssert "Number of pending tasks should not be zero" let blockReceived = block: var res = false for future in pendingTasks: if not(future.completed()): continue - if not(cast[Future[bool]](future).read()): continue + if not(cast[Future[bool]](future).value): continue res = true break res @@ -667,11 +654,9 @@ proc runBlockPollMonitor(service: BlockServiceRef, pendingTasks.filterIt(not(it.finished())).mapIt(it.cancelAndWait()) await noCancel allFutures(pending) raise exc - except CatchableError as exc: - warn "An unexpected error occurred while running block monitoring", - reason = $exc.msg, error = $exc.name -proc runBlockMonitor(service: BlockServiceRef) {.async.} = +proc runBlockMonitor(service: BlockServiceRef) {. + async: (raises: [CancelledError]).} = let vc = service.client blockNodes = vc.filterNodes(ResolvedBeaconNodeStatuses, @@ -680,17 +665,11 @@ proc runBlockMonitor(service: BlockServiceRef) {.async.} = case vc.config.monitoringType of BlockMonitoringType.Disabled: debug "Block monitoring disabled" - @[newFuture[void]("block.monitor.disabled")] + @[Future[void].Raising([CancelledError]).init("block.monitor.disabled")] of BlockMonitoringType.Poll: - var res: seq[Future[void]] - for node in blockNodes: - res.add(service.runBlockPollMonitor(node)) - res + blockNodes.mapIt(service.runBlockPollMonitor(it)) of BlockMonitoringType.Event: - var res: seq[Future[void]] - for node in blockNodes: - res.add(service.runBlockEventMonitor(node)) - res + blockNodes.mapIt(service.runBlockEventMonitor(it)) try: await allFutures(pendingTasks) @@ -699,12 +678,8 @@ proc runBlockMonitor(service: BlockServiceRef) {.async.} = pendingTasks.filterIt(not(it.finished())).mapIt(it.cancelAndWait()) await noCancel allFutures(pending) raise exc - except CatchableError as exc: - warn "An unexpected error occurred while running block monitoring", - reason = $exc.msg, error = $exc.name - return -proc mainLoop(service: BlockServiceRef) {.async.} = +proc mainLoop(service: BlockServiceRef) {.async: (raises: []).} = let vc = service.client service.state = ServiceState.Running debug "Service started" @@ -715,9 +690,6 @@ proc mainLoop(service: BlockServiceRef) {.async.} = await future except CancelledError: debug "Service interrupted" - except CatchableError as exc: - error "Service crashed with unexpected error", err_name = exc.name, - err_msg = exc.msg # We going to cleanup all the pending proposer tasks. var res: seq[FutureBase] @@ -729,13 +701,15 @@ proc mainLoop(service: BlockServiceRef) {.async.} = res.add(duty.randaoFut.cancelAndWait()) await noCancel allFutures(res) -proc init*(t: typedesc[BlockServiceRef], - vc: ValidatorClientRef): Future[BlockServiceRef] {.async.} = +proc init*( + t: typedesc[BlockServiceRef], + vc: ValidatorClientRef +): Future[BlockServiceRef] {.async: (raises: []).} = logScope: service = ServiceName - var res = BlockServiceRef(name: ServiceName, client: vc, + let res = BlockServiceRef(name: ServiceName, client: vc, state: ServiceState.Initialized) debug "Initializing service" - return res + res proc start*(service: BlockServiceRef) = service.lifeFut = mainLoop(service) diff --git a/beacon_chain/validator_client/common.nim b/beacon_chain/validator_client/common.nim index ba09cc272c..5b77c310f3 100644 --- a/beacon_chain/validator_client/common.nim +++ b/beacon_chain/validator_client/common.nim @@ -61,7 +61,7 @@ type ClientServiceRef* = ref object of RootObj name*: string state*: ServiceState - lifeFut*: Future[void] + lifeFut*: Future[void].Raising([]) client*: ValidatorClientRef DutiesServiceRef* = ref object of ClientServiceRef @@ -190,12 +190,8 @@ type blocks: seq[Eth2Digest] waiters*: seq[BlockWaiter] - ValidatorRuntimeConfig* = object - forkConfig*: Opt[VCForkConfig] - ValidatorClient* = object config*: ValidatorClientConf - runtimeConfig*: ValidatorRuntimeConfig metricsServer*: Opt[MetricsHttpServerRef] graffitiBytes*: GraffitiBytes beaconNodes*: seq[BeaconNodeServerRef] @@ -206,7 +202,7 @@ type blockService*: BlockServiceRef syncCommitteeService*: SyncCommitteeServiceRef doppelgangerService*: DoppelgangerServiceRef - runSlotLoopFut*: Future[void] + runSlotLoopFut*: Future[void].Raising([CancelledError]) runKeystoreCachePruningLoopFut*: Future[void] sigintHandleFut*: Future[void] sigtermHandleFut*: Future[void] @@ -216,6 +212,7 @@ type beaconClock*: BeaconClock attachedValidators*: ref ValidatorPool forks*: seq[Fork] + forkConfig*: Opt[VCForkConfig] preGenesisEvent*: AsyncEvent genesisEvent*: AsyncEvent forksAvailable*: AsyncEvent @@ -241,7 +238,8 @@ type ApiFailure* {.pure.} = enum Communication, Invalid, NotFound, OptSynced, NotSynced, Internal, - NotImplemented, UnexpectedCode, UnexpectedResponse, NoError + NotImplemented, UnexpectedCode, UnexpectedResponse, UnsupportedContentType, + NoError ApiNodeFailure* = object node*: BeaconNodeServerRef @@ -375,6 +373,7 @@ proc `$`*(failure: ApiFailure): string = of ApiFailure.NotImplemented: "not-implemented" of ApiFailure.UnexpectedCode: "unexpected-code" of ApiFailure.UnexpectedResponse: "unexpected-data" + of ApiFailure.UnsupportedContentType: "unsupported-content-type" of ApiFailure.NoError: "status-update" proc getNodeCounts*(vc: ValidatorClientRef): BeaconNodesCounters = @@ -629,7 +628,7 @@ proc updateStatus*(node: BeaconNodeServerRef, warn "Beacon node's clock is out of order, (beacon node is unusable)" node.status = status -proc stop*(csr: ClientServiceRef) {.async.} = +proc stop*(csr: ClientServiceRef) {.async: (raises: []).} = debug "Stopping service", service = csr.name if csr.state == ServiceState.Running: csr.state = ServiceState.Closing @@ -899,6 +898,60 @@ proc forkAtEpoch*(vc: ValidatorClientRef, epoch: Epoch): Fork = break res +proc isPastElectraFork*(vc: ValidatorClientRef, epoch: Epoch): bool = + doAssert(len(vc.forks) > 0) + doAssert(vc.forkConfig.isSome()) + let electraVersion = + try: + vc.forkConfig.get()[ConsensusFork.Electra].version + except KeyError: + raiseAssert "Electra fork should be in forks configuration" + var res = false + for item in vc.forks: + if item.epoch <= epoch: + if item.current_version == electraVersion: + res = true + else: + break + res + +proc isPastAltairFork*(vc: ValidatorClientRef, epoch: Epoch): bool = + doAssert(len(vc.forks) > 0) + doAssert(vc.forkConfig.isSome()) + + let altairVersion = + try: + vc.forkConfig.get()[ConsensusFork.Altair].version + except KeyError: + raiseAssert "Altair fork should be in forks configuration" + + var res = false + for item in vc.forks: + if item.epoch <= epoch: + if item.current_version == altairVersion: + res = true + else: + break + res + +proc getForkEpoch*(vc: ValidatorClientRef, fork: ConsensusFork): Opt[Epoch] = + doAssert(len(vc.forks) > 0) + doAssert(vc.forkConfig.isSome()) + + let forkVersion = + try: + vc.forkConfig.get()[fork].version + except KeyError: + raiseAssert $fork & " fork should be in forks configuration" + + for item in vc.forks: + if item.current_version == forkVersion: + return Opt.some(item.epoch) + Opt.none(Epoch) + +proc getAltairEpoch*(vc: ValidatorClientRef): Epoch = + getForkEpoch(vc, ConsensusFork.Altair).get() + proc getSubcommitteeIndex*(index: IndexInSyncCommittee): SyncSubcommitteeIndex = SyncSubcommitteeIndex(uint16(index) div SYNC_SUBCOMMITTEE_SIZE) @@ -924,7 +977,8 @@ proc addValidator*(vc: ValidatorClientRef, keystore: KeystoreData) = discard vc.attachedValidators[].addValidator(keystore, feeRecipient, gasLimit) proc removeValidator*(vc: ValidatorClientRef, - pubkey: ValidatorPubKey) {.async.} = + pubkey: ValidatorPubKey) {. + async: (raises: [CancelledError]).} = let validator = vc.attachedValidators[].getValidator(pubkey).valueOr: return # Remove validator from ValidatorPool. @@ -1059,10 +1113,11 @@ proc getValidatorRegistration( err(RegistrationKind.Cached) proc prepareRegistrationList*( - vc: ValidatorClientRef, - timestamp: Time, - fork: Fork - ): Future[seq[SignedValidatorRegistrationV1]] {.async.} = + vc: ValidatorClientRef, + timestamp: Time, + fork: Fork +): Future[seq[SignedValidatorRegistrationV1]] {. + async: (raises: [CancelledError]).} = var messages: seq[SignedValidatorRegistrationV1] @@ -1104,7 +1159,7 @@ proc prepareRegistrationList*( for index, future in futures.pairs(): if future.completed(): - let sres = future.read() + let sres = future.value if sres.isOk(): var reg = messages[index] reg.signature = sres.get() @@ -1122,7 +1177,7 @@ proc prepareRegistrationList*( index_missing = indexMissing, fee_missing = feeMissing, incorrect_time = timed - return registrations + registrations func init*(t: typedesc[ApiNodeFailure], failure: ApiFailure, request: string, strategy: ApiStrategyKind, @@ -1156,7 +1211,8 @@ func init*(t: typedesc[ApiNodeFailure], failure: ApiFailure, proc checkedWaitForSlot*(vc: ValidatorClientRef, destinationSlot: Slot, offset: TimeDiff, - showLogs: bool): Future[Opt[Slot]] {.async.} = + showLogs: bool): Future[Opt[Slot]] {. + async: (raises: [CancelledError]).} = let currentTime = vc.beaconClock.now() currentSlot = currentTime.slotOrZero() @@ -1173,7 +1229,7 @@ proc checkedWaitForSlot*(vc: ValidatorClientRef, destinationSlot: Slot, time_to_slot = shortLog(timeToSlot) while true: - await sleepAsync(timeToSlot) + await sleepAsync2(timeToSlot) let wallTime = vc.beaconClock.now() @@ -1218,7 +1274,8 @@ proc checkedWaitForSlot*(vc: ValidatorClientRef, destinationSlot: Slot, proc checkedWaitForNextSlot*(vc: ValidatorClientRef, curSlot: Opt[Slot], offset: TimeDiff, - showLogs: bool): Future[Opt[Slot]] = + showLogs: bool): Future[Opt[Slot]] {. + async: (raises: [CancelledError], raw: true).} = let currentTime = vc.beaconClock.now() currentSlot = curSlot.valueOr: currentTime.slotOrZero() @@ -1227,7 +1284,8 @@ proc checkedWaitForNextSlot*(vc: ValidatorClientRef, curSlot: Opt[Slot], vc.checkedWaitForSlot(nextSlot, offset, showLogs) proc checkedWaitForNextSlot*(vc: ValidatorClientRef, offset: TimeDiff, - showLogs: bool): Future[Opt[Slot]] = + showLogs: bool): Future[Opt[Slot]] {. + async: (raises: [CancelledError], raw: true).} = let currentTime = vc.beaconClock.now() currentSlot = currentTime.slotOrZero() @@ -1236,7 +1294,8 @@ proc checkedWaitForNextSlot*(vc: ValidatorClientRef, offset: TimeDiff, vc.checkedWaitForSlot(nextSlot, offset, showLogs) proc expectBlock*(vc: ValidatorClientRef, slot: Slot, - confirmations: int = 1): Future[seq[Eth2Digest]] = + confirmations: int = 1): Future[seq[Eth2Digest]] {. + async: (raises: [CancelledError], raw: true).}= var retFuture = newFuture[seq[Eth2Digest]]("expectBlock") waiter = BlockWaiter(future: retFuture, count: confirmations) @@ -1298,7 +1357,7 @@ proc waitForBlock*( slot: Slot, timediff: TimeDiff, confirmations: int = 1 - ) {.async.} = + ): Future[void] {.async: (raises: [CancelledError]).} = ## This procedure will wait for a block proposal for a ``slot`` received ## by the beacon node. let @@ -1328,11 +1387,6 @@ proc waitForBlock*( let dur = Moment.now() - startTime debug "Block awaiting was interrupted", duration = dur raise exc - except CatchableError as exc: - let dur = Moment.now() - startTime - error "Unexpected error occured while waiting for block publication", - err_name = exc.name, err_msg = exc.msg, duration = dur - return let dur = Moment.now() - startTime @@ -1380,41 +1434,44 @@ func `==`*(a, b: TimeOffset): bool = a.value == b.value func nanoseconds*(to: TimeOffset): int64 = to.value proc waitForNextEpoch*(service: ClientServiceRef, - delay: Duration) {.async.} = + delay: Duration): Future[void] {. + async: (raises: [CancelledError], raw: true) .}= let vc = service.client sleepTime = vc.beaconClock.durationToNextEpoch() + delay debug "Sleeping until next epoch", service = service.name, sleep_time = sleepTime, delay = delay - await sleepAsync(sleepTime) + sleepAsync(sleepTime) -proc waitForNextEpoch*(service: ClientServiceRef): Future[void] = +proc waitForNextEpoch*(service: ClientServiceRef): Future[void] {. + async: (raises: [CancelledError], raw: true).}= waitForNextEpoch(service, ZeroDuration) -proc waitForNextSlot*(service: ClientServiceRef) {.async.} = - let vc = service.client - let sleepTime = vc.beaconClock.durationToNextSlot() - await sleepAsync(sleepTime) +proc waitForNextSlot*(service: ClientServiceRef): Future[void] {. + async: (raises: [CancelledError], raw: true).} = + let + vc = service.client + sleepTime = vc.beaconClock.durationToNextSlot() + sleepAsync(sleepTime) func compareUnsorted*[T](a, b: openArray[T]): bool = if len(a) != len(b): return false - return - case len(a) - of 0: - true - of 1: - a[0] == b[0] - of 2: - ((a[0] == b[0]) and (a[1] == b[1])) or ((a[0] == b[1]) and (a[1] == b[0])) - else: - let asorted = sorted(a) - let bsorted = sorted(b) - for index, item in asorted.pairs(): - if item != bsorted[index]: - return false - true + case len(a) + of 0: + true + of 1: + a[0] == b[0] + of 2: + ((a[0] == b[0]) and (a[1] == b[1])) or ((a[0] == b[1]) and (a[1] == b[0])) + else: + let asorted = sorted(a) + let bsorted = sorted(b) + for index, item in asorted.pairs(): + if item != bsorted[index]: + return false + true func `==`*(a, b: SyncCommitteeDuty): bool = (a.pubkey == b.pubkey) and @@ -1422,88 +1479,56 @@ func `==`*(a, b: SyncCommitteeDuty): bool = compareUnsorted(a.validator_sync_committee_indices, b.validator_sync_committee_indices) -proc updateRuntimeConfig*(vc: ValidatorClientRef, - node: BeaconNodeServerRef, - info: VCRuntimeConfig): Result[void, string] = - var forkConfig = ? info.getConsensusForkConfig() +proc validateForkCompatibility( + vc: ValidatorClientRef, + consensusFork: ConsensusFork, + forkVersion: Version, + forkEpoch: Epoch, + forkConfig: VCForkConfig +): Result[void, string] = + let + item = + try: + vc.forkConfig.get()[consensusFork] + except KeyError: + raiseAssert "Fork should be present in configuration" + + if forkVersion != item.version: + return err("Beacon node has conflicting " & + consensusFork.forkVersionConfigKey() & " value") + + if forkEpoch != item.epoch: + if forkEpoch == FAR_FUTURE_EPOCH: + return err("Beacon node do not know about " & + $consensusFork & " starting epoch") + else: + if item.epoch != FAR_FUTURE_EPOCH: + return err("Beacon node has conflicting " & + consensusFork.forkEpochConfigKey() & " value") + ok() - if vc.runtimeConfig.forkConfig.isNone(): - vc.runtimeConfig.forkConfig = Opt.some(forkConfig) - else: - template localForkConfig: untyped = vc.runtimeConfig.forkConfig.get() - let wallEpoch = vc.beaconClock.now().slotOrZero().epoch() - - proc validateForkVersionCompatibility( - consensusFork: ConsensusFork, - localForkVersion: Opt[Version], - localForkEpoch: Epoch, - forkVersion: Opt[Version]): Result[void, string] = - if localForkVersion.isNone(): - ok() # Potentially discovered new fork, save it at end of function - else: - if forkVersion.isSome(): - if forkVersion.get() == localForkVersion.get(): - ok() # Already known - else: - err("Beacon node has conflicting " & - consensusFork.forkVersionConfigKey() & " value") - else: - if wallEpoch < localForkEpoch: - debug "Beacon node must be updated before fork activates", - node = node, - consensusFork, - forkEpoch = localForkEpoch - ok() - else: - err("Beacon node must be updated and report correct " & - $consensusFork & " config value") - - ? ConsensusFork.Capella.validateForkVersionCompatibility( - localForkConfig.capellaVersion, - localForkConfig.capellaEpoch, - forkConfig.capellaVersion) - - proc validateForkEpochCompatibility( - consensusFork: ConsensusFork, - localForkEpoch: Epoch, - forkEpoch: Epoch): Result[void, string] = - if localForkEpoch == FAR_FUTURE_EPOCH: - ok() # Potentially discovered new fork, save it at end of function - else: - if forkEpoch != FAR_FUTURE_EPOCH: - if forkEpoch == localForkEpoch: - ok() # Already known - else: - err("Beacon node has conflicting " & - consensusFork.forkEpochConfigKey() & " value") - else: - if wallEpoch < localForkEpoch: - debug "Beacon node must be updated before fork activates", - node = node, - consensusFork, - forkEpoch = localForkEpoch - ok() - else: - err("Beacon node must be updated and report correct " & - $consensusFork & " config value") - - ? ConsensusFork.Altair.validateForkEpochCompatibility( - localForkConfig.altairEpoch, forkConfig.altairEpoch) - ? ConsensusFork.Capella.validateForkEpochCompatibility( - localForkConfig.capellaEpoch, forkConfig.capellaEpoch) - ? ConsensusFork.Deneb.validateForkEpochCompatibility( - localForkConfig.denebEpoch, forkConfig.denebEpoch) - - # Save newly discovered forks. - if localForkConfig.altairEpoch == FAR_FUTURE_EPOCH: - localForkConfig.altairEpoch = forkConfig.altairEpoch - if localForkConfig.capellaVersion.isNone(): - localForkConfig.capellaVersion = forkConfig.capellaVersion - if localForkConfig.capellaEpoch == FAR_FUTURE_EPOCH: - localForkConfig.capellaEpoch = forkConfig.capellaEpoch - if localForkConfig.denebEpoch == FAR_FUTURE_EPOCH: - localForkConfig.denebEpoch = forkConfig.denebEpoch +proc updateRuntimeConfig*( + vc: ValidatorClientRef, + node: BeaconNodeServerRef, + info: VCRuntimeConfig +): Result[void, string] = + let forkConfig = ? info.getConsensusForkConfig() + if vc.forkConfig.isNone(): + vc.forkConfig = Opt.some(forkConfig) + else: + var localForkConfig = vc.forkConfig.get() + for fork in ConsensusFork: + try: + let item = forkConfig[fork] + ? vc.validateForkCompatibility(fork, item.version, item.epoch, + localForkConfig) + # Save newly discovered forks. + if localForkConfig[fork].epoch == FAR_FUTURE_EPOCH: + localForkConfig[fork].epoch = item.epoch + except KeyError: + raiseAssert "All the forks should be present inside forks configuration" + vc.forkConfig = Opt.some(localForkConfig) ok() proc `+`*(slot: Slot, epochs: Epoch): Slot = @@ -1517,3 +1542,17 @@ proc getGraffitiBytes*(vc: ValidatorClientRef, validator: AttachedValidator): GraffitiBytes = getGraffiti(vc.config.validatorsDir, vc.config.defaultGraffitiBytes(), validator.pubkey) + +proc contains*(a, b: openArray[Fork]): bool = + if len(a) < len(b): + return false + for bfork in b: + var found = false + block subLoop: + for afork in a: + if afork == bfork: + found = true + break subLoop + if not(found): + return false + true diff --git a/beacon_chain/validator_client/doppelganger_service.nim b/beacon_chain/validator_client/doppelganger_service.nim index 28abd3691c..8af2c06188 100644 --- a/beacon_chain/validator_client/doppelganger_service.nim +++ b/beacon_chain/validator_client/doppelganger_service.nim @@ -44,7 +44,7 @@ proc processActivities(service: DoppelgangerServiceRef, epoch: Epoch, break -proc mainLoop(service: DoppelgangerServiceRef) {.async.} = +proc mainLoop(service: DoppelgangerServiceRef) {.async: (raises: []).} = let vc = service.client service.state = ServiceState.Running @@ -64,10 +64,6 @@ proc mainLoop(service: DoppelgangerServiceRef) {.async.} = except CancelledError: debug "Service interrupted" return - except CatchableError as exc: - warn "Service crashed with unexpected error", err_name = exc.name, - err_msg = exc.msg - return # On (re)start, we skip the remainder of the epoch before we start monitoring # for doppelgangers so we don't trigger on the attestations we produced before @@ -75,7 +71,11 @@ proc mainLoop(service: DoppelgangerServiceRef) {.async.} = # before that, we can safely perform the check for epoch 0 and thus keep # validating in epoch 1 if vc.beaconClock.now().slotOrZero() > GENESIS_SLOT: - await service.waitForNextEpoch(TIME_DELAY_FROM_SLOT) + try: + await service.waitForNextEpoch(TIME_DELAY_FROM_SLOT) + except CancelledError: + debug "Service interrupted" + return while try: # Wait for the epoch to end - at the end (or really, the beginning of the @@ -100,20 +100,18 @@ proc mainLoop(service: DoppelgangerServiceRef) {.async.} = except CancelledError: debug "Service interrupted" false - except CatchableError as exc: - warn "Service crashed with unexpected error", err_name = exc.name, - err_msg = exc.msg - false : discard -proc init*(t: type DoppelgangerServiceRef, - vc: ValidatorClientRef): Future[DoppelgangerServiceRef] {.async.} = +proc init*( + t: type DoppelgangerServiceRef, + vc: ValidatorClientRef +): Future[DoppelgangerServiceRef] {.async: (raises: []).} = logScope: service = ServiceName let res = DoppelgangerServiceRef(name: ServiceName, client: vc, state: ServiceState.Initialized, enabled: vc.config.doppelgangerDetection) debug "Initializing service" - return res + res proc start*(service: DoppelgangerServiceRef) = service.lifeFut = mainLoop(service) diff --git a/beacon_chain/validator_client/duties_service.nim b/beacon_chain/validator_client/duties_service.nim index dddae8885a..b0e39756d1 100644 --- a/beacon_chain/validator_client/duties_service.nim +++ b/beacon_chain/validator_client/duties_service.nim @@ -48,7 +48,9 @@ proc checkDuty(duty: RestAttesterDuty): bool = proc checkSyncDuty(duty: RestSyncCommitteeDuty): bool = uint64(duty.validator_index) <= VALIDATOR_REGISTRY_LIMIT -proc pollForValidatorIndices*(service: DutiesServiceRef) {.async.} = +proc pollForValidatorIndices*( + service: DutiesServiceRef +) {.async: (raises: [CancelledError]).} = let vc = service.client let validatorIdents = @@ -74,10 +76,7 @@ proc pollForValidatorIndices*(service: DutiesServiceRef) {.async.} = except CancelledError as exc: debug "Validator's indices processing was interrupted" raise exc - except CatchableError as exc: - error "Unexpected error occurred while getting validator information", - err_name = exc.name, err_msg = exc.msg - return + for item in res: validators.add(item) @@ -107,8 +106,10 @@ proc pollForValidatorIndices*(service: DutiesServiceRef) {.async.} = updated_validators = updated vc.indicesAvailable.fire() -proc pollForAttesterDuties*(service: DutiesServiceRef, - epoch: Epoch): Future[int] {.async.} = +proc pollForAttesterDuties*( + service: DutiesServiceRef, + epoch: Epoch +): Future[int] {.async: (raises: [CancelledError]).} = var currentRoot: Opt[Eth2Digest] let vc = service.client @@ -131,10 +132,7 @@ proc pollForAttesterDuties*(service: DutiesServiceRef, except CancelledError as exc: debug "Attester duties processing was interrupted" raise exc - except CatchableError as exc: - error "Unexpected error while getting attester duties", - epoch = epoch, err_name = exc.name, err_msg = exc.msg - return 0 + if currentRoot.isNone(): # First request currentRoot = Opt.some(res.dependent_root) @@ -212,14 +210,13 @@ proc pruneSyncCommitteeSelectionProofs*(service: DutiesServiceRef, slot: Slot) = vc.syncCommitteeProofs.del(epoch) proc pollForSyncCommitteeDuties*( - service: DutiesServiceRef, - period: SyncCommitteePeriod - ): Future[int] {.async.} = + service: DutiesServiceRef, + period: SyncCommitteePeriod +): Future[int] {.async: (raises: [CancelledError]).} = let vc = service.client indices = toSeq(vc.attachedValidators[].indices()) - altairEpoch = vc.runtimeConfig.forkConfig.get().altairEpoch - epoch = max(period.start_epoch(), altairEpoch) + epoch = max(period.start_epoch(), vc.getAltairEpoch()) relevantDuties = block: var duties: seq[RestSyncCommitteeDuty] @@ -239,11 +236,6 @@ proc pollForSyncCommitteeDuties*( debug "Sync committee duties processing was interrupted", period = period, epoch = epoch raise exc - except CatchableError as exc: - error "Unexpected error while getting sync committee duties", - period = period, epoch = epoch, - err_name = exc.name, err_msg = exc.msg - return 0 for duty in res.data: if checkSyncDuty(duty) and (duty.pubkey in vc.attachedValidators[]): @@ -296,7 +288,9 @@ proc pruneAttesterDuties(service: DutiesServiceRef, epoch: Epoch) = attesters[key] = v vc.attesters = attesters -proc pollForAttesterDuties*(service: DutiesServiceRef) {.async.} = +proc pollForAttesterDuties*( + service: DutiesServiceRef +) {.async: (raises: [CancelledError]).} = ## Query the beacon node for attestation duties for all known validators. ## ## This function will perform (in the following order): @@ -365,7 +359,15 @@ proc pollForAttesterDuties*(service: DutiesServiceRef) {.async.} = res if len(subscriptions) > 0: - let res = await vc.prepareBeaconCommitteeSubnet(subscriptions) + let res = + try: + await vc.prepareBeaconCommitteeSubnet(subscriptions) + except ValidatorApiError as exc: + warn "Failed to subscribe validators to beacon committee subnets", + slot = currentSlot, epoch = currentEpoch, + subscriptions_count = len(subscriptions), + reason = exc.msg + 0 if res == 0: warn "Failed to subscribe validators to beacon committee subnets", slot = currentSlot, epoch = currentEpoch, @@ -373,19 +375,18 @@ proc pollForAttesterDuties*(service: DutiesServiceRef) {.async.} = service.pruneAttesterDuties(currentEpoch) -proc pollForSyncCommitteeDuties*(service: DutiesServiceRef) {.async.} = - let vc = service.client +proc pollForSyncCommitteeDuties*( + service: DutiesServiceRef +) {.async: (raises: [CancelledError]).} = let + vc = service.client currentSlot = vc.getCurrentSlot().get(Slot(0)) currentEpoch = currentSlot.epoch() - altairEpoch = - if vc.runtimeConfig.forkConfig.isSome(): - vc.runtimeConfig.forkConfig.get().altairEpoch - else: - return - if currentEpoch < altairEpoch: - # We are not going to poll for sync committee duties until `altairEpoch`. + if not vc.isPastAltairFork(currentEpoch): + notice "Sync committee duties will not be queried before ALTAIR fork", + epoch = currentEpoch + # We are not going to poll for sync committee duties until `ALTAIR` epoch. return let @@ -466,11 +467,17 @@ proc pollForSyncCommitteeDuties*(service: DutiesServiceRef) {.async.} = res.add(sub) res if len(subscriptions) > 0: - let res = await vc.prepareSyncCommitteeSubnets(subscriptions) + let (res, reason) = + try: + (await vc.prepareSyncCommitteeSubnets(subscriptions), "") + except ValidatorApiError as exc: + (0, $exc.msg) + if res == 0: warn "Failed to subscribe validators to sync committee subnets", slot = currentSlot, epoch = currentPeriod, period = currentPeriod, - periods = periods, subscriptions_count = len(subscriptions) + periods = periods, subscriptions_count = len(subscriptions), + reason = reason else: service.syncSubscriptionEpoch = Opt.some(currentEpoch) @@ -489,7 +496,9 @@ proc pruneBeaconProposers(service: DutiesServiceRef, epoch: Epoch) = loop = ProposerLoop vc.proposers = proposers -proc pollForBeaconProposers*(service: DutiesServiceRef) {.async.} = +proc pollForBeaconProposers*( + service: DutiesServiceRef +) {.async: (raises: [CancelledError]).} = let vc = service.client let currentSlot = vc.getCurrentSlot().get(Slot(0)) @@ -515,15 +524,13 @@ proc pollForBeaconProposers*(service: DutiesServiceRef) {.async.} = except CancelledError as exc: debug "Proposer duties processing was interrupted" raise exc - except CatchableError as exc: - debug "Unexpected error occured while getting proposer duties", - slot = currentSlot, epoch = currentEpoch, err_name = exc.name, - err_msg = exc.msg service.pruneBeaconProposers(currentEpoch) vc.pruneBlocksSeen(currentEpoch) -proc prepareBeaconProposers*(service: DutiesServiceRef) {.async.} = +proc prepareBeaconProposers*( + service: DutiesServiceRef +) {.async: (raises: [CancelledError]).} = let vc = service.client let currentSlot = vc.getCurrentSlot().get(Slot(0)) @@ -542,17 +549,15 @@ proc prepareBeaconProposers*(service: DutiesServiceRef) {.async.} = except CancelledError as exc: debug "Beacon proposer preparation processing was interrupted" raise exc - except CatchableError as exc: - error "Unexpected error occured while preparing beacon proposers", - slot = currentSlot, epoch = currentEpoch, err_name = exc.name, - err_msg = exc.msg - 0 + debug "Beacon proposers prepared", validators_count = vc.attachedValidators[].count(), proposers_count = len(proposers), prepared_count = count -proc registerValidators*(service: DutiesServiceRef) {.async.} = +proc registerValidators*( + service: DutiesServiceRef +) {.async: (raises: [CancelledError]).} = let vc = service.client let currentSlot = vc.getCurrentSlot().get(Slot(0)) @@ -564,12 +569,6 @@ proc registerValidators*(service: DutiesServiceRef) {.async.} = debug "Validator registration preparation was interrupted", slot = currentSlot, fork = genesisFork raise exc - except CatchableError as exc: - var default: seq[SignedValidatorRegistrationV1] - error "Unexpected error occured while preparing validators " & - "registration data", slot = currentSlot, fork = genesisFork, - err_name = exc.name, err_msg = exc.msg - default count = if len(registrations) > 0: @@ -584,11 +583,6 @@ proc registerValidators*(service: DutiesServiceRef) {.async.} = debug "Validator registration was interrupted", slot = currentSlot, fork = genesisFork raise exc - except CatchableError as exc: - error "Unexpected error occured while registering validators", - slot = currentSlot, fork = genesisFork, err_name = exc.name, - err_msg = exc.msg - 0 else: 0 @@ -597,7 +591,9 @@ proc registerValidators*(service: DutiesServiceRef) {.async.} = beacon_nodes_count = count, registrations = len(registrations), validators_count = vc.attachedValidators[].count() -proc attesterDutiesLoop(service: DutiesServiceRef) {.async.} = +proc attesterDutiesLoop( + service: DutiesServiceRef +) {.async: (raises: [CancelledError]).} = let vc = service.client debug "Attester duties loop is waiting for initialization" await allFutures( @@ -615,7 +611,9 @@ proc attesterDutiesLoop(service: DutiesServiceRef) {.async.} = # Spawning new attestation duties task. service.pollingAttesterDutiesTask = service.pollForAttesterDuties() -proc proposerDutiesLoop(service: DutiesServiceRef) {.async.} = +proc proposerDutiesLoop( + service: DutiesServiceRef +) {.async: (raises: [CancelledError]).} = let vc = service.client debug "Proposer duties loop is waiting for initialization" await allFutures( @@ -628,7 +626,9 @@ proc proposerDutiesLoop(service: DutiesServiceRef) {.async.} = await service.pollForBeaconProposers() await service.waitForNextSlot() -proc validatorIndexLoop(service: DutiesServiceRef) {.async.} = +proc validatorIndexLoop( + service: DutiesServiceRef +) {.async: (raises: [CancelledError]).} = let vc = service.client debug "Validator indices loop is waiting for initialization" await vc.preGenesisEvent.wait() @@ -636,9 +636,11 @@ proc validatorIndexLoop(service: DutiesServiceRef) {.async.} = await service.pollForValidatorIndices() await service.waitForNextSlot() -proc dynamicValidatorsLoop*(service: DutiesServiceRef, - web3signerUrl: Web3SignerUrl, - intervalInSeconds: int) {.async.} = +proc dynamicValidatorsLoop*( + service: DutiesServiceRef, + web3signerUrl: Web3SignerUrl, + intervalInSeconds: int +) {.async: (raises: [CancelledError]).} = let vc = service.client doAssert(intervalInSeconds > 0) @@ -671,7 +673,9 @@ proc dynamicValidatorsLoop*(service: DutiesServiceRef, except CancelledError: true -proc proposerPreparationsLoop(service: DutiesServiceRef) {.async.} = +proc proposerPreparationsLoop( + service: DutiesServiceRef +) {.async: (raises: [CancelledError]).} = let vc = service.client debug "Beacon proposer preparation loop is waiting for initialization" await allFutures( @@ -682,7 +686,9 @@ proc proposerPreparationsLoop(service: DutiesServiceRef) {.async.} = await service.prepareBeaconProposers() await service.waitForNextSlot() -proc validatorRegisterLoop(service: DutiesServiceRef) {.async.} = +proc validatorRegisterLoop( + service: DutiesServiceRef +) {.async: (raises: [CancelledError]).} = let vc = service.client doAssert(vc.config.payloadBuilderEnable) debug "Validator registration loop is waiting for initialization" @@ -696,7 +702,9 @@ proc validatorRegisterLoop(service: DutiesServiceRef) {.async.} = await service.registerValidators() await service.waitForNextSlot() -proc syncCommitteeDutiesLoop(service: DutiesServiceRef) {.async.} = +proc syncCommitteeDutiesLoop( + service: DutiesServiceRef +) {.async: (raises: [CancelledError]).} = let vc = service.client debug "Sync committee duties loop is waiting for initialization" await allFutures( @@ -724,7 +732,9 @@ proc getNextEpochMiddleSlot(vc: ValidatorClientRef): Slot = else: currentSlot + (uint64(middleSlot) - uint64(slotInEpoch)) -proc pruneSlashingDatabase(service: DutiesServiceRef) {.async.} = +proc pruneSlashingDatabase( + service: DutiesServiceRef +) {.async: (raises: [CancelledError]).} = let vc = service.client currentSlot = vc.beaconClock.now().slotOrZero() @@ -736,11 +746,6 @@ proc pruneSlashingDatabase(service: DutiesServiceRef) {.async.} = debug "Finalized block header request was interrupted", slot = currentSlot raise exc - except CatchableError as exc: - error "Unexpected error occured while requesting " & - "finalized block header", slot = currentSlot, - err_name = exc.name, err_msg = exc.msg - Opt.none(GetBlockHeaderResponse) checkpointTime = Moment.now() if blockHeader.isSome(): let epoch = blockHeader.get().data.header.message.slot.epoch @@ -757,7 +762,9 @@ proc pruneSlashingDatabase(service: DutiesServiceRef) {.async.} = elapsed_time = (finishTime - startTime), pruning_time = (finishTime - checkpointTime) -proc slashingDatabasePruningLoop(service: DutiesServiceRef) {.async.} = +proc slashingDatabasePruningLoop( + service: DutiesServiceRef +) {.async: (raises: [CancelledError]).} = let vc = service.client debug "Slashing database pruning loop is waiting for initialization" await allFutures( @@ -781,7 +788,7 @@ template checkAndRestart(serviceLoop: DutiesServiceLoop, future: Future[void], body: untyped): untyped = if future.finished(): if future.failed(): - let error = future.readError() + let error = future.error debug "The loop ended unexpectedly with an error", error_name = error.name, error_msg = error.msg, loop = serviceLoop elif future.cancelled(): @@ -791,7 +798,7 @@ template checkAndRestart(serviceLoop: DutiesServiceLoop, loop = serviceLoop future = body -proc mainLoop(service: DutiesServiceRef) {.async.} = +proc mainLoop(service: DutiesServiceRef) {.async: (raises: []).} = let vc = service.client service.state = ServiceState.Running @@ -835,7 +842,10 @@ proc mainLoop(service: DutiesServiceRef) {.async.} = for fut in dynamicFuts: futures.add fut if not(isNil(registerFut)): futures.add(FutureBase(registerFut)) - discard await race(futures) + try: + discard await race(futures) + except ValueError: + raiseAssert "Futures sequence will never be empty" checkAndRestart(AttesterLoop, attestFut, service.attesterDutiesLoop()) checkAndRestart(ProposerLoop, proposeFut, service.proposerDutiesLoop()) checkAndRestart(IndicesLoop, indicesFut, service.validatorIndexLoop()) @@ -881,23 +891,21 @@ proc mainLoop(service: DutiesServiceRef) {.async.} = if not(isNil(service.pruneSlashingDatabaseTask)) and not(service.pruneSlashingDatabaseTask.finished()): pending.add(service.pruneSlashingDatabaseTask.cancelAndWait()) - await allFutures(pending) - true - except CatchableError as exc: - warn "Service crashed with unexpected error", err_name = exc.name, - err_msg = exc.msg + await noCancel allFutures(pending) true if breakLoop: break -proc init*(t: typedesc[DutiesServiceRef], - vc: ValidatorClientRef): Future[DutiesServiceRef] {.async.} = +proc init*( + t: typedesc[DutiesServiceRef], + vc: ValidatorClientRef +): Future[DutiesServiceRef] {.async: (raises: []).} = logScope: service = ServiceName let res = DutiesServiceRef(name: ServiceName, client: vc, state: ServiceState.Initialized) debug "Initializing service" - return res + res proc start*(service: DutiesServiceRef) = service.lifeFut = mainLoop(service) diff --git a/beacon_chain/validator_client/fallback_service.nim b/beacon_chain/validator_client/fallback_service.nim index 57cc774811..02b9fbd978 100644 --- a/beacon_chain/validator_client/fallback_service.nim +++ b/beacon_chain/validator_client/fallback_service.nim @@ -65,7 +65,8 @@ proc preGenesisNodes*(vc: ValidatorClientRef): seq[BeaconNodeServerRef] = proc waitNodes*(vc: ValidatorClientRef, timeoutFut: Future[void], statuses: set[RestBeaconNodeStatus], - roles: set[BeaconNodeRole], waitChanges: bool) {.async.} = + roles: set[BeaconNodeRole], waitChanges: bool) {. + async: (raises: [CancelledError]).} = doAssert(not(isNil(vc.fallbackService))) var iterations = 0 while true: @@ -121,7 +122,7 @@ proc checkName*( proc checkCompatible( vc: ValidatorClientRef, node: BeaconNodeServerRef - ): Future[RestBeaconNodeStatus] {.async.} = + ): Future[RestBeaconNodeStatus] {.async: (raises: [CancelledError]).} = ## Could return only {Offline, Incompatible, Compatible} logScope: endpoint = node let info = @@ -137,11 +138,6 @@ proc checkCompatible( debug "Unable to obtain beacon node's configuration", error_name = exc.name, error_message = exc.msg return RestBeaconNodeStatus.Offline - except CatchableError as exc: - if node.status != RestBeaconNodeStatus.Offline: - error "Unexpected exception", error_name = exc.name, - error_message = exc.msg - return RestBeaconNodeStatus.Offline let genesis = try: @@ -156,11 +152,6 @@ proc checkCompatible( debug "Unable to obtain beacon node's genesis", error_name = exc.name, error_message = exc.msg return RestBeaconNodeStatus.Offline - except CatchableError as exc: - if node.status != RestBeaconNodeStatus.Offline: - error "Unexpected exception", error_name = exc.name, - error_message = exc.msg - return RestBeaconNodeStatus.Offline let genesisFlag = (genesis != vc.beaconGenesis) @@ -169,25 +160,24 @@ proc checkCompatible( node.config = info node.genesis = Opt.some(genesis) - return - if configFlag or genesisFlag: - if node.status != RestBeaconNodeStatus.Incompatible: - warn "Beacon node has incompatible configuration", - genesis_flag = genesisFlag, config_flag = configFlag + if configFlag or genesisFlag: + if node.status != RestBeaconNodeStatus.Incompatible: + warn "Beacon node has incompatible configuration", + genesis_flag = genesisFlag, config_flag = configFlag + RestBeaconNodeStatus.Incompatible + else: + let res = vc.updateRuntimeConfig(node, node.config) + if res.isErr(): + warn "Beacon nodes report different configuration values", + reason = res.error RestBeaconNodeStatus.Incompatible else: - let res = vc.updateRuntimeConfig(node, node.config) - if res.isErr(): - warn "Beacon nodes report different configuration values", - reason = res.error - RestBeaconNodeStatus.Incompatible - else: - RestBeaconNodeStatus.Compatible + RestBeaconNodeStatus.Compatible proc checkSync( vc: ValidatorClientRef, node: BeaconNodeServerRef - ): Future[RestBeaconNodeStatus] {.async.} = + ): Future[RestBeaconNodeStatus] {.async: (raises: [CancelledError]).} = ## Could return only {Offline, NotSynced, Synced, OptSynced} logScope: endpoint = node let syncInfo = @@ -203,26 +193,19 @@ proc checkSync( debug "Unable to obtain beacon node's sync status", error_name = exc.name, error_message = exc.msg return RestBeaconNodeStatus.Offline - except CatchableError as exc: - if node.status != RestBeaconNodeStatus.Offline: - error "Unexpected exception", error_name = exc.name, - error_message = exc.msg - return RestBeaconNodeStatus.Offline + node.syncInfo = Opt.some(syncInfo) - let res = - block: - if not(syncInfo.is_syncing) or (syncInfo.sync_distance < SYNC_TOLERANCE): - if not(syncInfo.is_optimistic.get(false)): - RestBeaconNodeStatus.Synced - else: - RestBeaconNodeStatus.OptSynced - else: - RestBeaconNodeStatus.NotSynced - return res + if not(syncInfo.is_syncing) or (syncInfo.sync_distance < SYNC_TOLERANCE): + if not(syncInfo.is_optimistic.get(false)): + RestBeaconNodeStatus.Synced + else: + RestBeaconNodeStatus.OptSynced + else: + RestBeaconNodeStatus.NotSynced proc checkOnline( node: BeaconNodeServerRef - ): Future[RestBeaconNodeStatus] {.async.} = + ): Future[RestBeaconNodeStatus] {.async: (raises: [CancelledError]).} = ## Could return only {Offline, Online}. logScope: endpoint = node debug "Checking beacon node status" @@ -237,12 +220,9 @@ proc checkOnline( debug "Unable to check beacon node's status", error_name = exc.name, error_message = exc.msg return RestBeaconNodeStatus.Offline - except CatchableError as exc: - error "Unexpected exception", error_name = exc.name, - error_message = exc.msg - return RestBeaconNodeStatus.Offline + node.ident = Opt.some(agent.version) - return RestBeaconNodeStatus.Online + RestBeaconNodeStatus.Online func getReason(status: RestBeaconNodeStatus): string = case status @@ -258,7 +238,8 @@ func getReason(status: RestBeaconNodeStatus): string = "Beacon node reports" proc checkNode(vc: ValidatorClientRef, - node: BeaconNodeServerRef): Future[bool] {.async.} = + node: BeaconNodeServerRef): Future[bool] {. + async: (raises: [CancelledError]).} = let nstatus = node.status debug "Checking beacon node", endpoint = node, status = node.status @@ -313,7 +294,8 @@ proc checkNode(vc: ValidatorClientRef, node.updateStatus(status, failure) return nstatus != status -proc checkNodes*(service: FallbackServiceRef): Future[bool] {.async.} = +proc checkNodes*(service: FallbackServiceRef): Future[bool] {. + async: (raises: [CancelledError]).} = let vc = service.client nodesToCheck = @@ -326,14 +308,14 @@ proc checkNodes*(service: FallbackServiceRef): Future[bool] {.async.} = try: await allFutures(pendingChecks) for fut in pendingChecks: - if fut.completed() and fut.read(): + if fut.completed() and fut.value(): res = true except CancelledError as exc: let pending = pendingChecks .filterIt(not(it.finished())).mapIt(it.cancelAndWait()) await noCancel allFutures(pending) raise exc - return res + res proc checkOffsetStatus(node: BeaconNodeServerRef, offset: TimeOffset) = logScope: @@ -384,8 +366,10 @@ proc disableNimbusExtensions(node: BeaconNodeServerRef) = "Nimbus extensions no longer available") node.updateStatus(RestBeaconNodeStatus.Offline, failure) -proc runTimeMonitor(service: FallbackServiceRef, - node: BeaconNodeServerRef) {.async.} = +proc runTimeMonitor( + service: FallbackServiceRef, + node: BeaconNodeServerRef +) {.async: (raises: [CancelledError]).} = const NimbusExtensionsLog = "Beacon node does not support Nimbus extensions" let vc = service.client @@ -428,18 +412,14 @@ proc runTimeMonitor(service: FallbackServiceRef, return except CancelledError as exc: raise exc - except CatchableError as exc: - warn "An unexpected error occurred while asking for time offset", - reason = $exc.msg, error = $exc.name - notice NimbusExtensionsLog - node.disableNimbusExtensions() - return checkOffsetStatus(node, TimeOffset.init(tres)) await service.waitForNextSlot() -proc processTimeMonitoring(service: FallbackServiceRef) {.async.} = +proc processTimeMonitoring( + service: FallbackServiceRef +) {.async: (raises: [CancelledError]).} = let vc = service.client blockNodes = vc.filterNodes( @@ -456,12 +436,8 @@ proc processTimeMonitoring(service: FallbackServiceRef) {.async.} = .filterIt(not(it.finished())).mapIt(it.cancelAndWait()) await noCancel allFutures(pending) raise exc - except CatchableError as exc: - warn "An unexpected error occurred while running time monitoring", - reason = $exc.msg, error = $exc.name - return -proc mainLoop(service: FallbackServiceRef) {.async.} = +proc mainLoop(service: FallbackServiceRef) {.async: (raises: []).} = let vc = service.client service.state = ServiceState.Running debug "Service started" @@ -474,10 +450,6 @@ proc mainLoop(service: FallbackServiceRef) {.async.} = debug "Service interrupted" if not(timeMonitorFut.finished()): await timeMonitorFut.cancelAndWait() return - except CatchableError as exc: - warn "Service crashed with unexpected error", err_name = exc.name, - err_msg = exc.msg - return while true: # This loop could look much more nicer/better, when @@ -492,22 +464,20 @@ proc mainLoop(service: FallbackServiceRef) {.async.} = debug "Service interrupted" if not(timeMonitorFut.finished()): await timeMonitorFut.cancelAndWait() true - except CatchableError as exc: - error "Service crashed with unexpected error", err_name = exc.name, - err_msg = exc.msg - true if breakLoop: break -proc init*(t: typedesc[FallbackServiceRef], - vc: ValidatorClientRef): Future[FallbackServiceRef] {.async.} = +proc init*( + t: typedesc[FallbackServiceRef], + vc: ValidatorClientRef +): Future[FallbackServiceRef] {.async: (raises: []).} = logScope: service = ServiceName - var res = FallbackServiceRef(name: ServiceName, client: vc, + let res = FallbackServiceRef(name: ServiceName, client: vc, state: ServiceState.Initialized, changesEvent: newAsyncEvent()) debug "Initializing service" - return res + res proc start*(service: FallbackServiceRef) = service.lifeFut = mainLoop(service) diff --git a/beacon_chain/validator_client/fork_service.nim b/beacon_chain/validator_client/fork_service.nim index 94a619d36f..fa16f28c22 100644 --- a/beacon_chain/validator_client/fork_service.nim +++ b/beacon_chain/validator_client/fork_service.nim @@ -5,6 +5,8 @@ # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # at your option. This file may not be copied, modified, or distributed except according to those terms. +{.push raises: [].} + import std/algorithm import chronicles import "."/[common, api] @@ -42,10 +44,10 @@ proc sortForks(forks: openArray[Fork]): Result[seq[Fork], cstring] {. return err("Invalid fork schedule") ok(sortedForks) -proc pollForFork(vc: ValidatorClientRef) {.async.} = +proc pollForFork(vc: ValidatorClientRef) {.async: (raises: [CancelledError]).} = let forks = try: - await vc.getForkSchedule(ApiStrategyKind.Best) + await vc.getForkSchedule() except ValidatorApiError as exc: warn "Unable to retrieve fork schedule", reason = exc.getFailureReason(), err_msg = exc.msg @@ -53,10 +55,9 @@ proc pollForFork(vc: ValidatorClientRef) {.async.} = except CancelledError as exc: debug "Fork retrieval process was interrupted" raise exc - except CatchableError as exc: - error "Unexpected error occured while getting fork information", - err_name = exc.name, err_msg = exc.msg - return + + if len(forks) == 0: + return let sortedForks = block: @@ -71,7 +72,7 @@ proc pollForFork(vc: ValidatorClientRef) {.async.} = notice "Fork schedule updated", fork_schedule = sortedForks vc.forksAvailable.fire() -proc mainLoop(service: ForkServiceRef) {.async.} = +proc mainLoop(service: ForkServiceRef) {.async: (raises: []).} = let vc = service.client service.state = ServiceState.Running debug "Service started" @@ -81,10 +82,6 @@ proc mainLoop(service: ForkServiceRef) {.async.} = except CancelledError: debug "Service interrupted" return - except CatchableError as exc: - warn "Service crashed with unexpected error", err_name = exc.name, - err_msg = exc.msg - return while true: # This loop could look much more nicer/better, when @@ -98,21 +95,19 @@ proc mainLoop(service: ForkServiceRef) {.async.} = except CancelledError: debug "Service interrupted" true - except CatchableError as exc: - warn "Service crashed with unexpected error", err_name = exc.name, - err_msg = exc.msg - true if breakLoop: break -proc init*(t: typedesc[ForkServiceRef], - vc: ValidatorClientRef): Future[ForkServiceRef] {.async.} = +proc init*( + t: typedesc[ForkServiceRef], + vc: ValidatorClientRef +): Future[ForkServiceRef] {.async: (raises: []).} = logScope: service = ServiceName let res = ForkServiceRef(name: ServiceName, client: vc, state: ServiceState.Initialized) debug "Initializing service" - return res + res proc start*(service: ForkServiceRef) = service.lifeFut = mainLoop(service) diff --git a/beacon_chain/validator_client/scoring.nim b/beacon_chain/validator_client/scoring.nim index 19448b94da..1c50bf6101 100644 --- a/beacon_chain/validator_client/scoring.nim +++ b/beacon_chain/validator_client/scoring.nim @@ -103,6 +103,33 @@ proc getAggregatedAttestationDataScore*( ones_count = ones, score = shortScore(res) res +proc getAggregatedAttestationDataScore*( + adata: GetAggregatedAttestationV2Response + ): float64 = + # This procedure returns score value in range [0.0000, 1.0000) and `Inf`. + # It returns perfect score when all the bits was set to `1`, but this could + # provide wrong expectation for some edge cases (when different attestations + # has different committee sizes), but currently this is the only viable way + # to return perfect score. + withAttestation(adata): + const MaxLength = int(MAX_VALIDATORS_PER_COMMITTEE) + doAssert(len(forkyAttestation.aggregation_bits) <= MaxLength) + let + size = len(forkyAttestation.aggregation_bits) + ones = countOnes(forkyAttestation.aggregation_bits) + res = + if ones == size: + # We consider score perfect, when all bits was set to 1. + Inf + else: + float64(ones) / float64(size) + + debug "Aggregated attestation score", + attestation_data = shortLog(forkyAttestation.data), + block_slot = forkyAttestation.data.slot, committee_size = size, + ones_count = ones, score = shortScore(res) + res + proc getSyncCommitteeContributionDataScore*( cdata: ProduceSyncCommitteeContributionResponse ): float64 = diff --git a/beacon_chain/validator_client/selection_proofs.nim b/beacon_chain/validator_client/selection_proofs.nim index 33b788354c..2571f689b6 100644 --- a/beacon_chain/validator_client/selection_proofs.nim +++ b/beacon_chain/validator_client/selection_proofs.nim @@ -60,10 +60,10 @@ proc cmp(x, y: AttestationSlotRequest|SyncCommitteeSlotRequest): int = cmp(x.slot, y.slot) proc getAttesterDutiesRequests( - vc: ValidatorClientRef, - start, finish: Slot, - genesisRoot: Eth2Digest - ): seq[AttestationSlotRequest] = + vc: ValidatorClientRef, + start, finish: Slot, + genesisRoot: Eth2Digest +): seq[AttestationSlotRequest] = var res: seq[AttestationSlotRequest] for epoch in start.epoch() .. finish.epoch(): for duty in vc.attesterDutiesForEpoch(epoch): @@ -92,9 +92,9 @@ proc getAttesterDutiesRequests( sorted(res, cmp, order = SortOrder.Ascending) proc fillAttestationSelectionProofs*( - vc: ValidatorClientRef, - start, finish: Slot - ): Future[FillSignaturesResult] {.async.} = + vc: ValidatorClientRef, + start, finish: Slot +): Future[FillSignaturesResult] {.async: (raises: [CancelledError]).} = let genesisRoot = vc.beaconGenesis.genesis_validators_root var requests: seq[AttestationSlotRequest] @@ -108,6 +108,8 @@ proc fillAttestationSelectionProofs*( while len(pendingRequests) > 0: try: discard await race(pendingRequests) + except ValueError: + raiseAssert "Number of pendingRequests should not be zero" except CancelledError as exc: var pending: seq[Future[void]] for future in pendingRequests: @@ -125,7 +127,7 @@ proc fillAttestationSelectionProofs*( else: let signature = if mreq.future.completed(): - let sres = Future[SignatureResult](mreq.future).read() + let sres = Future[SignatureResult](mreq.future).value if sres.isErr(): warn "Unable to create slot signature using remote signer", reason = sres.error(), epoch = mreq.slot.epoch(), @@ -180,10 +182,6 @@ proc fillAttestationSelectionProofs*( except CancelledError as exc: debug "Beacon committee selections processing was interrupted" raise exc - except CatchableError as exc: - error "Unexpected error occured while trying to submit beacon " & - "committee selections", reason = exc.msg, error = exc.name - return sigres sigres.selectionsReceived = len(sresponse.data) @@ -229,8 +227,10 @@ proc fillAttestationSelectionProofs*( sigres -func getIndex*(proof: SyncCommitteeSelectionProof, - inindex: IndexInSyncCommittee): Opt[int] = +func getIndex*( + proof: SyncCommitteeSelectionProof, + inindex: IndexInSyncCommittee +): Opt[int] = if len(proof) == 0: return Opt.none(int) for index, value in proof.pairs(): @@ -238,30 +238,41 @@ func getIndex*(proof: SyncCommitteeSelectionProof, return Opt.some(index) Opt.none(int) -func hasSignature*(proof: SyncCommitteeSelectionProof, - inindex: IndexInSyncCommittee, - slot: Slot): bool = +func hasSignature*( + proof: SyncCommitteeSelectionProof, + inindex: IndexInSyncCommittee, + slot: Slot +): bool = let index = proof.getIndex(inindex).valueOr: return false proof[index].signatures[int(slot.since_epoch_start())].isSome() -func getSignature*(proof: SyncCommitteeSelectionProof, - inindex: IndexInSyncCommittee, - slot: Slot): Opt[ValidatorSig] = +func getSignature*( + proof: SyncCommitteeSelectionProof, + inindex: IndexInSyncCommittee, + slot: Slot +): Opt[ValidatorSig] = let index = proof.getIndex(inindex).valueOr: return Opt.none(ValidatorSig) proof[index].signatures[int(slot.since_epoch_start())] -proc setSignature*(proof: var SyncCommitteeSelectionProof, - inindex: IndexInSyncCommittee, slot: Slot, - signature: Opt[ValidatorSig]) = +proc setSignature*( + proof: var SyncCommitteeSelectionProof, + inindex: IndexInSyncCommittee, + slot: Slot, + signature: Opt[ValidatorSig] +) = let index = proof.getIndex(inindex).expect( "EpochSelectionProof should be present at this moment") proof[index].signatures[int(slot.since_epoch_start())] = signature -proc setSyncSelectionProof*(vc: ValidatorClientRef, pubkey: ValidatorPubKey, - inindex: IndexInSyncCommittee, slot: Slot, - duty: SyncCommitteeDuty, - signature: Opt[ValidatorSig]) = +proc setSyncSelectionProof*( + vc: ValidatorClientRef, + pubkey: ValidatorPubKey, + inindex: IndexInSyncCommittee, + slot: Slot, + duty: SyncCommitteeDuty, + signature: Opt[ValidatorSig] +) = let proof = block: @@ -279,7 +290,7 @@ proc getSyncCommitteeSelectionProof*( vc: ValidatorClientRef, pubkey: ValidatorPubKey, epoch: Epoch - ): Opt[SyncCommitteeSelectionProof] = +): Opt[SyncCommitteeSelectionProof] = vc.syncCommitteeProofs.withValue(epoch, epochProofs): epochProofs[].proofs.withValue(pubkey, validatorProofs): return Opt.some(validatorProofs[]) @@ -289,11 +300,11 @@ proc getSyncCommitteeSelectionProof*( return Opt.none(SyncCommitteeSelectionProof) proc getSyncCommitteeSelectionProof*( - vc: ValidatorClientRef, - pubkey: ValidatorPubKey, - slot: Slot, - inindex: IndexInSyncCommittee - ): Opt[ValidatorSig] = + vc: ValidatorClientRef, + pubkey: ValidatorPubKey, + slot: Slot, + inindex: IndexInSyncCommittee +): Opt[ValidatorSig] = vc.syncCommitteeProofs.withValue(slot.epoch(), epochProofs): epochProofs[].proofs.withValue(pubkey, validatorProofs): let index = getIndex(validatorProofs[], inindex).valueOr: @@ -305,10 +316,10 @@ proc getSyncCommitteeSelectionProof*( return Opt.none(ValidatorSig) proc getSyncCommitteeDutiesRequests*( - vc: ValidatorClientRef, - start, finish: Slot, - genesisRoot: Eth2Digest - ): seq[SyncCommitteeSlotRequest] = + vc: ValidatorClientRef, + start, finish: Slot, + genesisRoot: Eth2Digest +): seq[SyncCommitteeSlotRequest] = var res: seq[SyncCommitteeSlotRequest] for epoch in start.epoch() .. finish.epoch(): let @@ -349,11 +360,11 @@ proc getSyncCommitteeDutiesRequests*( sorted(res, cmp, order = SortOrder.Ascending) proc getSyncRequest*( - requests: var openArray[SyncCommitteeSlotRequest], - validator: AttachedValidator, - slot: Slot, - subcommittee_index: uint64 - ): Opt[SyncCommitteeSlotRequest] = + requests: var openArray[SyncCommitteeSlotRequest], + validator: AttachedValidator, + slot: Slot, + subcommittee_index: uint64 +): Opt[SyncCommitteeSlotRequest] = for mreq in requests.mitems(): if mreq.validator.pubkey == validator.pubkey and mreq.slot == slot and @@ -362,9 +373,9 @@ proc getSyncRequest*( Opt.none(SyncCommitteeSlotRequest) proc fillSyncCommitteeSelectionProofs*( - vc: ValidatorClientRef, - start, finish: Slot - ): Future[FillSignaturesResult] {.async.} = + vc: ValidatorClientRef, + start, finish: Slot +): Future[FillSignaturesResult] {.async: (raises: [CancelledError]).} = let genesisRoot = vc.beaconGenesis.genesis_validators_root var requests: seq[SyncCommitteeSlotRequest] @@ -378,6 +389,8 @@ proc fillSyncCommitteeSelectionProofs*( while len(pendingRequests) > 0: try: discard await race(pendingRequests) + except ValueError: + raiseAssert "Number of pendingRequests should not be zero" except CancelledError as exc: var pending: seq[Future[void]] for future in pendingRequests: @@ -395,7 +408,7 @@ proc fillSyncCommitteeSelectionProofs*( else: let signature = if mreq.future.completed(): - let sres = Future[SignatureResult](mreq.future).read() + let sres = Future[SignatureResult](mreq.future).value if sres.isErr(): warn "Unable to create selection proof using remote signer", reason = sres.error(), epoch = mreq.slot.epoch(), @@ -451,10 +464,6 @@ proc fillSyncCommitteeSelectionProofs*( except CancelledError as exc: debug "Sync committee selections processing was interrupted" raise exc - except CatchableError as exc: - error "Unexpected error occured while trying to submit sync " & - "committee selections", reason = exc.msg, error = exc.name - return sigres sigres.selectionsReceived = len(sresponse.data) diff --git a/beacon_chain/validator_client/sync_committee_service.nim b/beacon_chain/validator_client/sync_committee_service.nim index 6d1c7e05f4..63f6a91411 100644 --- a/beacon_chain/validator_client/sync_committee_service.nim +++ b/beacon_chain/validator_client/sync_committee_service.nim @@ -27,10 +27,12 @@ type validator: AttachedValidator subcommitteeIdx: SyncSubcommitteeIndex -proc serveSyncCommitteeMessage*(service: SyncCommitteeServiceRef, - slot: Slot, beaconBlockRoot: Eth2Digest, - duty: SyncCommitteeDuty): Future[bool] {. - async.} = +proc serveSyncCommitteeMessage*( + service: SyncCommitteeServiceRef, + slot: Slot, + beaconBlockRoot: Eth2Digest, + duty: SyncCommitteeDuty +): Future[bool] {.async: (raises: [CancelledError]).} = let vc = service.client startTime = Moment.now() @@ -72,10 +74,6 @@ proc serveSyncCommitteeMessage*(service: SyncCommitteeServiceRef, except CancelledError: debug "Publish sync committee message request was interrupted" return false - except CatchableError as exc: - error "Unexpected error occurred while publishing sync committee message", - error = exc.name, reason = exc.msg - return false let delay = vc.getDelay(message.slot.sync_committee_message_deadline()) @@ -91,11 +89,12 @@ proc serveSyncCommitteeMessage*(service: SyncCommitteeServiceRef, validator_index = vindex, delay = delay, duration = dur res -proc produceAndPublishSyncCommitteeMessages(service: SyncCommitteeServiceRef, - slot: Slot, - beaconBlockRoot: Eth2Digest, - duties: seq[SyncCommitteeDuty]) - {.async.} = +proc produceAndPublishSyncCommitteeMessages( + service: SyncCommitteeServiceRef, + slot: Slot, + beaconBlockRoot: Eth2Digest, + duties: seq[SyncCommitteeDuty] +): Future[void] {.async: (raises: [CancelledError]).} = let vc = service.client startTime = Moment.now() @@ -123,7 +122,7 @@ proc produceAndPublishSyncCommitteeMessages(service: SyncCommitteeServiceRef, for future in pendingSyncCommitteeMessages: if future.completed(): - if future.read(): + if future.value: inc(succeed) else: inc(failed) @@ -141,10 +140,11 @@ proc produceAndPublishSyncCommitteeMessages(service: SyncCommitteeServiceRef, not_accepted = statistics[2], delay = delay, duration = dur, slot = slot, duties_count = len(duties) -proc serveContributionAndProof*(service: SyncCommitteeServiceRef, - proof: ContributionAndProof, - validator: AttachedValidator): Future[bool] {. - async.} = +proc serveContributionAndProof*( + service: SyncCommitteeServiceRef, + proof: ContributionAndProof, + validator: AttachedValidator +): Future[bool] {.async: (raises: [CancelledError]).} = ## Signs ConributionAndProof object and sends it to BN. let vc = service.client @@ -166,10 +166,6 @@ proc serveContributionAndProof*(service: SyncCommitteeServiceRef, except CancelledError: debug "Sync contribution signing process was interrupted" return false - except CatchableError as exc: - error "Unexpected error occurred while signing sync contribution", - error = exc.name, reason = exc.msg - return false if res.isErr(): warn "Unable to sign sync committee contribution using remote signer", @@ -194,10 +190,6 @@ proc serveContributionAndProof*(service: SyncCommitteeServiceRef, except CancelledError: debug "Publication process of sync contribution was interrupted" return false - except CatchableError as err: - error "Unexpected error occurred while publishing sync contribution", - error = err.name, reason = err.msg - false let dur = Moment.now() - startTime if res: @@ -207,10 +199,12 @@ proc serveContributionAndProof*(service: SyncCommitteeServiceRef, warn "Sync contribution was not accepted by beacon node", duration = dur res -proc produceAndPublishContributions(service: SyncCommitteeServiceRef, - slot: Slot, - beaconBlockRoot: Eth2Digest, - duties: seq[SyncCommitteeDuty]) {.async.} = +proc produceAndPublishContributions( + service: SyncCommitteeServiceRef, + slot: Slot, + beaconBlockRoot: Eth2Digest, + duties: seq[SyncCommitteeDuty] +) {.async: (raises: [CancelledError]).} = let vc = service.client startTime = Moment.now() @@ -262,6 +256,8 @@ proc produceAndPublishContributions(service: SyncCommitteeServiceRef, while len(pendingFutures) > 0: try: discard await race(pendingFutures) + except ValueError: + raiseAssert "Number of pendingFutures should not be zero" except CancelledError as exc: let pending = pendingFutures .filterIt(not(it.finished())).mapIt(it.cancelAndWait()) @@ -277,7 +273,12 @@ proc produceAndPublishContributions(service: SyncCommitteeServiceRef, if index notin completed: completed.add(index) let aggContribution = try: - Opt.some(future.read()) + let tfut = + cast[Future[SyncCommitteeContribution]. + Raising([CancelledError, ValidatorApiError])](future) + Opt.some(tfut.read()) + except FuturePendingError: + raiseAssert "Future should be finished" except ValidatorApiError as exc: warn "Unable to get sync message contribution data", reason = exc.getFailureReason() @@ -286,11 +287,6 @@ proc produceAndPublishContributions(service: SyncCommitteeServiceRef, debug "Request for sync message contribution was " & "interrupted" raise exc - except CatchableError as exc: - error "Unexpected error occurred while getting sync " & - "message contribution", - error = exc.name, reason = exc.msg - Opt.none(SyncCommitteeContribution) if aggContribution.isSome(): let proof = ContributionAndProof( @@ -303,10 +299,10 @@ proc produceAndPublishContributions(service: SyncCommitteeServiceRef, pendingFutures = block: - var res: seq[FutureBase] + var tres: seq[FutureBase] for index, value in pendingFutures.pairs(): - if index notin completed: res.add(value) - res + if index notin completed: tres.add(value) + tres res statistics = block: @@ -321,7 +317,7 @@ proc produceAndPublishContributions(service: SyncCommitteeServiceRef, for future in pendingAggregates: if future.completed(): - if future.read(): + if future.value: inc(succeed) else: inc(failed) @@ -344,10 +340,11 @@ proc produceAndPublishContributions(service: SyncCommitteeServiceRef, else: debug "No contribution and proofs scheduled for the slot" -proc publishSyncMessagesAndContributions(service: SyncCommitteeServiceRef, - slot: Slot, - duties: seq[SyncCommitteeDuty]) {. - async.} = +proc publishSyncMessagesAndContributions( + service: SyncCommitteeServiceRef, + slot: Slot, + duties: seq[SyncCommitteeDuty] +) {.async: (raises: [CancelledError]).} = let vc = service.client await vc.waitForBlock(slot, syncCommitteeMessageSlotOffset) @@ -383,10 +380,6 @@ proc publishSyncMessagesAndContributions(service: SyncCommitteeServiceRef, except CancelledError: debug "Block root request was interrupted" return - except CatchableError as exc: - error "Unexpected error while requesting sync message block root", - error = exc.name, reason = exc.msg - return try: await service.produceAndPublishSyncCommitteeMessages( @@ -398,10 +391,6 @@ proc publishSyncMessagesAndContributions(service: SyncCommitteeServiceRef, except CancelledError: debug "Sync committee messages production was interrupted" return - except CatchableError as exc: - error "Unexpected error while producing sync committee messages", - duties_count = len(duties), error = exc.name, reason = exc.msg - return let currentTime = vc.beaconClock.now() if slot.sync_contribution_deadline() > currentTime: @@ -420,13 +409,11 @@ proc publishSyncMessagesAndContributions(service: SyncCommitteeServiceRef, except CancelledError: debug "Sync committee contributions production was interrupted" return - except CatchableError as exc: - error "Unexpected error while producing sync committee contributions", - duties_count = len(duties), error = exc.name, reason = exc.msg - return -proc processSyncCommitteeTasks(service: SyncCommitteeServiceRef, - slot: Slot) {.async.} = +proc processSyncCommitteeTasks( + service: SyncCommitteeServiceRef, + slot: Slot +) {.async: (raises: [CancelledError]).} = let vc = service.client duties = vc.getSyncCommitteeDutiesForSlot(slot + 1) @@ -444,11 +431,8 @@ proc processSyncCommitteeTasks(service: SyncCommitteeServiceRef, except CancelledError as exc: debug "Sync committee publish task has been interrupted" raise exc - except CatchableError as exc: - error "Unexpected error encountered while processing sync committee tasks", - error_name = exc.name, error_message = exc.msg -proc mainLoop(service: SyncCommitteeServiceRef) {.async.} = +proc mainLoop(service: SyncCommitteeServiceRef) {.async: (raises: []).} = let vc = service.client service.state = ServiceState.Running debug "Service started" @@ -464,10 +448,6 @@ proc mainLoop(service: SyncCommitteeServiceRef) {.async.} = except CancelledError: debug "Service interrupted" return - except CatchableError as exc: - warn "Service crashed with unexpected error", error = exc.name, - reason = exc.msg - return doAssert(len(vc.forks) > 0, "Fork schedule must not be empty at this point") @@ -493,16 +473,14 @@ proc mainLoop(service: SyncCommitteeServiceRef) {.async.} = except CancelledError: debug "Service interrupted" true - except CatchableError as exc: - warn "Service crashed with unexpected error", error = exc.name, - reason = exc.msg - true if breakLoop: break -proc init*(t: typedesc[SyncCommitteeServiceRef], - vc: ValidatorClientRef): Future[SyncCommitteeServiceRef] {.async.} = +proc init*( + t: typedesc[SyncCommitteeServiceRef], + vc: ValidatorClientRef +): Future[SyncCommitteeServiceRef] {.async: (raises: []).} = logScope: service = ServiceName let res = SyncCommitteeServiceRef(name: ServiceName, client: vc, state: ServiceState.Initialized) diff --git a/ncli/resttest-rules.json b/ncli/resttest-rules.json index 427373fda1..12f8683150 100644 --- a/ncli/resttest-rules.json +++ b/ncli/resttest-rules.json @@ -4746,7 +4746,7 @@ "headers": {"Accept": "application/json"} }, "response": { - "status": {"operator": "oneof", "value": ["400", "200"]} + "status": {"operator": "equals", "value": "400"} } }, { @@ -4756,7 +4756,7 @@ "headers": {"Accept": "application/json"} }, "response": { - "status": {"operator": "oneof", "value": ["400", "200"]} + "status": {"operator": "oneof", "value": ["404", "200"]} } }, { diff --git a/tests/test_signing_node.nim b/tests/test_signing_node.nim index 0379bd650e..8b0541ee2c 100644 --- a/tests/test_signing_node.nim +++ b/tests/test_signing_node.nim @@ -75,6 +75,9 @@ type process: AsyncProcessRef reader: Future[seq[byte]] + GetElectraAggregatedAttestationResponse = + DataEnclosedObject[electra.Attestation] + func getNodePort(basePort: int, rt: RemoteSignerType): int = # Individual port numbers derived by adding to configurable base port case rt