From 66cb18d69bb59375ac91641263baf19285df83f3 Mon Sep 17 00:00:00 2001 From: Eugene Kabanov Date: Mon, 23 Aug 2021 13:41:48 +0300 Subject: [PATCH] Number of REST fixes for Altair. (#2790) * Fix getForkSchedule call. Create cache of all configuration endpoints at node startup. Add prepareJsonResponse() call to create cached responses. Mark all procedures with `raises`. * Add getForkSchedule to VC. Fix getForkSchedule return type for API. More `raises` annotations. Fix VC fork_service.nim. * Use `push raises` instead of inline `raises`. * Improvements for REST API aggregated attestations and attestations processing. * Rename eth2_network.sendXXX procedures to eth2_network.broadcastXXX. Add broadcastBeaconBlock() and broadcastAggregateAndProof(). Fix links to specification in REST API declarations. Add implementation for v2 getStateV2(). Add validator_duties.sendXXX procedures which not only broadcast data, but also validate it. Fix JSON-RPC/REST to use new validator_duties.sendXXX procedures instead of own implementations. * Fix validator_client online nodes count incorrect value. Fix aggregate and proof attestation could be sent too late. * Adding timeout for block wait in attestations processing. Fix compilation errors. * Attempt to debug aggregate and proofs. * Fix Beacon AIP to use `sendAttestation`. Add link comment to produceBlockV2. * Add debug logs before publish operation for blocks, attestations and aggregated attestations. Fix attestations publishing issue. * logging fixes `indexInCommnittee` already logged in attestation Co-authored-by: Jacek Sieka --- beacon_chain/networking/eth2_network.nim | 30 +- beacon_chain/rpc/rest_beacon_api.nim | 173 +++++----- beacon_chain/rpc/rest_config_api.nim | 297 +++++++++--------- beacon_chain/rpc/rest_debug_api.nim | 50 ++- beacon_chain/rpc/rest_event_api.nim | 1 + beacon_chain/rpc/rest_node_api.nim | 7 + beacon_chain/rpc/rest_validator_api.nim | 51 +-- beacon_chain/rpc/rpc_beacon_api.nim | 34 +- .../eth2_apis/eth2_rest_serialization.nim | 268 ++++++++++------ beacon_chain/spec/eth2_apis/rest_types.nim | 2 +- beacon_chain/spec/forks.nim | 8 + beacon_chain/validator_client/api.nim | 27 +- .../validator_client/attestation_service.nim | 41 ++- .../validator_client/block_service.nim | 19 +- .../validator_client/fork_service.nim | 77 ++++- beacon_chain/validators/validator_duties.nim | 130 ++++++-- 16 files changed, 774 insertions(+), 441 deletions(-) diff --git a/beacon_chain/networking/eth2_network.nim b/beacon_chain/networking/eth2_network.nim index 3a5b9b31e..a261b612f 100644 --- a/beacon_chain/networking/eth2_network.nim +++ b/beacon_chain/networking/eth2_network.nim @@ -2061,8 +2061,8 @@ func forkDigestAtEpoch(node: Eth2Node, epoch: Epoch): ForkDigest = proc getWallEpoch(node: Eth2Node): Epoch = node.getBeaconTime().slotOrZero.epoch -proc sendAttestation*( - node: Eth2Node, subnet_id: SubnetId, attestation: Attestation) = +proc broadcastAttestation*(node: Eth2Node, subnet_id: SubnetId, + attestation: Attestation) = # Regardless of the contents of the attestation, # https://github.com/ethereum/consensus-specs/blob/v1.1.0-beta.2/specs/altair/p2p-interface.md#transitioning-the-gossip # implies that pre-fork, messages using post-fork digests might be @@ -2070,21 +2070,35 @@ proc sendAttestation*( # timer unsubscription point that means no new pre-fork-forkdigest # should be sent. let forkPrefix = node.forkDigestAtEpoch(node.getWallEpoch) - node.broadcast( - getAttestationTopic(forkPrefix, subnet_id), - attestation) + let topic = getAttestationTopic(forkPrefix, subnet_id) + node.broadcast(topic, attestation) -proc sendVoluntaryExit*(node: Eth2Node, exit: SignedVoluntaryExit) = +proc broadcastVoluntaryExit*(node: Eth2Node, exit: SignedVoluntaryExit) = let exitsTopic = getVoluntaryExitsTopic( node.forkDigestAtEpoch(node.getWallEpoch)) node.broadcast(exitsTopic, exit) -proc sendAttesterSlashing*(node: Eth2Node, slashing: AttesterSlashing) = +proc broadcastAttesterSlashing*(node: Eth2Node, slashing: AttesterSlashing) = let attesterSlashingsTopic = getAttesterSlashingsTopic( node.forkDigestAtEpoch(node.getWallEpoch)) node.broadcast(attesterSlashingsTopic, slashing) -proc sendProposerSlashing*(node: Eth2Node, slashing: ProposerSlashing) = +proc broadcastProposerSlashing*(node: Eth2Node, slashing: ProposerSlashing) = let proposerSlashingsTopic = getProposerSlashingsTopic( node.forkDigestAtEpoch(node.getWallEpoch)) node.broadcast(proposerSlashingsTopic, slashing) + +proc broadcastAggregateAndProof*(node: Eth2Node, + proof: SignedAggregateAndProof) = + let proofTopic = getAggregateAndProofsTopic( + node.forkDigestAtEpoch(node.getWallEpoch)) + node.broadcast(proofTopic, proof) + +proc broadcastBeaconBlock*(node: Eth2Node, forked: ForkedSignedBeaconBlock) = + case forked.kind + of BeaconBlockFork.Phase0: + let topic = getBeaconBlocksTopic(node.forkDigests.phase0) + node.broadcast(topic, forked.phase0Block) + of BeaconBlockFork.Altair: + let topic = getBeaconBlocksTopic(node.forkDigests.altair) + node.broadcast(topic, forked.altairBlock) diff --git a/beacon_chain/rpc/rest_beacon_api.nim b/beacon_chain/rpc/rest_beacon_api.nim index 8c1b7cd71..4ee8bbb5a 100644 --- a/beacon_chain/rpc/rest_beacon_api.nim +++ b/beacon_chain/rpc/rest_beacon_api.nim @@ -9,8 +9,7 @@ import chronicles, nimcrypto/utils as ncrutils, ../beacon_node_common, ../networking/eth2_network, - ../consensus_object_pools/[blockchain_dag, exit_pool], - ../gossip_processing/gossip_validation, + ../consensus_object_pools/[blockchain_dag, exit_pool, spec_cache], ../validators/validator_duties, ../spec/[eth2_merkleization, forks, network], ../spec/datatypes/[phase0, altair], @@ -105,7 +104,7 @@ proc getBeaconBlocksTopic(node: BeaconNode, kind: BeaconBlockFork): string = getBeaconBlocksTopic(node.dag.forkDigests.altair) proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getGenesis + # https://ethereum.github.io/beacon-APIs/#/Beacon/getGenesis router.api(MethodGet, "/api/eth/v1/beacon/genesis") do () -> RestApiResponse: return RestApiResponse.jsonResponse( ( @@ -116,7 +115,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = ) ) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateRoot + # https://ethereum.github.io/beacon-APIs/#/Beacon/getStateRoot router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/root") do ( state_id: StateIdent) -> RestApiResponse: let bslot = @@ -133,7 +132,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonResponse((root: stateRoot)) return RestApiResponse.jsonError(Http500, InternalServerError) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateFork + # https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFork router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/fork") do ( state_id: StateIdent) -> RestApiResponse: let bslot = @@ -159,7 +158,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = ) return RestApiResponse.jsonError(Http500, InternalServerError) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateFinalityCheckpoints + # https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFinalityCheckpoints router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/finality_checkpoints") do ( state_id: StateIdent) -> RestApiResponse: @@ -185,7 +184,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = ) return RestApiResponse.jsonError(Http500, InternalServerError) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateValidators + # https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidators router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/validators") do ( state_id: StateIdent, id: seq[ValidatorIdent], status: seq[ValidatorFilter]) -> RestApiResponse: @@ -276,7 +275,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http500, InternalServerError) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateValidator + # https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidator router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/validators/{validator_id}") do ( state_id: StateIdent, validator_id: ValidatorIdent) -> RestApiResponse: @@ -353,7 +352,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = ValidatorStatusNotFoundError) return RestApiResponse.jsonError(Http500, InternalServerError) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateValidatorBalances + # https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidatorBalances router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/validator_balances") do ( state_id: StateIdent, id: seq[ValidatorIdent]) -> RestApiResponse: @@ -427,7 +426,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http500, InternalServerError) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getEpochCommittees + # https://ethereum.github.io/beacon-APIs/#/Beacon/getEpochCommittees router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/committees") do ( state_id: StateIdent, epoch: Option[Epoch], index: Option[CommitteeIndex], @@ -511,7 +510,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http500, InternalServerError) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockHeaders + # https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeaders router.api(MethodGet, "/api/eth/v1/beacon/headers") do ( slot: Option[Slot], parent_root: Option[Eth2Digest]) -> RestApiResponse: # TODO (cheatfate): This call is incomplete, because structure @@ -568,7 +567,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = ] ) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockHeader + # https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeader router.api(MethodGet, "/api/eth/v1/beacon/headers/{block_id}") do ( block_id: BlockIdent) -> RestApiResponse: let bdata = @@ -600,10 +599,10 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = ) ) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/publishBlock + # https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlock router.api(MethodPost, "/api/eth/v1/beacon/blocks") do ( contentBody: Option[ContentBody]) -> RestApiResponse: - let blockData = + let forked = block: if contentBody.isNone(): return RestApiResponse.jsonError(Http400, EmptyRequestBodyError) @@ -641,37 +640,14 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http400, InvalidBlockObjectError, $phase0res.error()) - let head = node.dag.head - if not(node.isSynced(head)): + let res = await node.sendBeaconBlock(forked) + if res.isErr(): return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError) - - if head.slot >= blockData.slot(): - let blocksTopic = node.getBeaconBlocksTopic(blockData.kind) - withBlck(blockData): - node.network.broadcast(blocksTopic, blck) + if not(res.get()): return RestApiResponse.jsonError(Http202, BlockValidationError) - else: - let res = - when compiles(node.proposeSignedBlock(head, AttachedValidator(), - blockData)): - await node.proposeSignedBlock(head, AttachedValidator(), blockData) - else: - case blockData.kind - of BeaconBlockFork.Phase0: - await node.proposeSignedBlock(head, AttachedValidator(), - blockData.phase0Block) - of BeaconBlockFork.Altair: - head + return RestApiResponse.jsonMsgResponse(BlockValidationSuccess) - if res == head: - let blocksTopic = node.getBeaconBlocksTopic(blockData.kind) - withBlck(blockData): - node.network.broadcast(blocksTopic, blck) - return RestApiResponse.jsonError(Http202, BlockValidationError) - else: - return RestApiResponse.jsonMsgResponse(BlockValidationSuccess) - - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlock + # https://ethereum.github.io/beacon-APIs/#/Beacon/getBlock router.api(MethodGet, "/api/eth/v1/beacon/blocks/{block_id}") do ( block_id: BlockIdent) -> RestApiResponse: let bdata = @@ -690,7 +666,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = of BeaconBlockFork.Altair: RestApiResponse.jsonError(Http404, BlockNotFoundError) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockV2 + # https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockV2 router.api(MethodGet, "/api/eth/v2/beacon/blocks/{block_id}") do ( block_id: BlockIdent) -> RestApiResponse: let bdata = @@ -713,7 +689,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = (version: "altair", data: bdata.data.altairBlock) ) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockRoot + # https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockRoot router.api(MethodGet, "/api/eth/v1/beacon/blocks/{block_id}/root") do ( block_id: BlockIdent) -> RestApiResponse: let bdata = @@ -729,7 +705,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = withBlck(bdata.data): RestApiResponse.jsonResponse((root: blck.root)) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockAttestations + # https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockAttestations router.api(MethodGet, "/api/eth/v1/beacon/blocks/{block_id}/attestations") do ( block_id: BlockIdent) -> RestApiResponse: @@ -746,7 +722,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = withBlck(bdata.data): RestApiResponse.jsonResponse(blck.message.body.attestations.asSeq()) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getPoolAttestations + # https://ethereum.github.io/beacon-APIs/#/Beacon/getPoolAttestations router.api(MethodGet, "/api/eth/v1/beacon/pool/attestations") do ( slot: Option[Slot], committee_index: Option[CommitteeIndex]) -> RestApiResponse: @@ -774,7 +750,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = res.add(item) return RestApiResponse.jsonResponse(res) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolAttestations + # https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolAttestations router.api(MethodPost, "/api/eth/v1/beacon/pool/attestations") do ( contentBody: Option[ContentBody]) -> RestApiResponse: let attestations = @@ -788,13 +764,48 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = $dres.error()) dres.get() - var failures: seq[RestAttestationsFailure] - for atindex, attestation in attestations.pairs(): - debug "Attestation for pool", attestation = attestation, - signature = $attestation.signature - if not await node.sendAttestation(attestation): - failures.add(RestAttestationsFailure( - index: uint64(atindex), message: "Attestation failed validation")) + proc processAttestation(a: Attestation): Future[SendResult] {.async.} = + let res = await node.sendAttestation(a) + if res.isErr(): + return res + let + wallTime = node.processor.getCurrentBeaconTime() + deadline = a.data.slot.toBeaconTime() + + seconds(int(SECONDS_PER_SLOT div 3)) + (delayStr, delaySecs) = + if wallTime < deadline: + ("-" & $(deadline - wallTime), -toFloatSeconds(deadline - wallTime)) + else: + ($(wallTime - deadline), toFloatSeconds(wallTime - deadline)) + notice "Attestation sent", attestation = shortLog(a), delay = delayStr + return res + + # Since our validation logic supports batch processing, we will submit all + # attestations for validation. + let pending = + block: + var res: seq[Future[SendResult]] + for attestation in attestations: + res.add(processAttestation(attestation)) + res + let failures = + block: + var res: seq[RestAttestationsFailure] + await allFutures(pending) + for index, future in pending.pairs(): + if future.done(): + let fres = future.read() + if fres.isErr(): + let failure = RestAttestationsFailure(index: uint64(index), + message: $fres.error()) + res.add(failure) + elif future.failed() or future.cancelled(): + # This is unexpected failure, so we log the error message. + let exc = future.readError() + let failure = RestAttestationsFailure(index: uint64(index), + message: $exc.msg) + res.add(failure) + res if len(failures) > 0: return RestApiResponse.jsonErrorList(Http400, AttestationValidationError, @@ -802,7 +813,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = else: return RestApiResponse.jsonMsgResponse(AttestationValidationSuccess) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getPoolAttesterSlashings + # https://ethereum.github.io/beacon-APIs/#/Beacon/getPoolAttesterSlashings router.api(MethodGet, "/api/eth/v1/beacon/pool/attester_slashings") do ( ) -> RestApiResponse: var res: seq[AttesterSlashing] @@ -814,7 +825,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = res.add(item) return RestApiResponse.jsonResponse(res) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolAttesterSlashings + # https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolAttesterSlashings router.api(MethodPost, "/api/eth/v1/beacon/pool/attester_slashings") do ( contentBody: Option[ContentBody]) -> RestApiResponse: let slashing = @@ -826,17 +837,15 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http400, InvalidAttesterSlashingObjectError, $dres.error()) - let res = dres.get() - let vres = node.exitPool[].validateAttesterSlashing(res) - if vres.isErr(): - return RestApiResponse.jsonError(Http400, - AttesterSlashingValidationError, - $vres.error()) - res - node.network.sendAttesterSlashing(slashing) + dres.get() + let res = node.sendAttesterSlashing(slashing) + if res.isErr(): + return RestApiResponse.jsonError(Http400, + AttesterSlashingValidationError, + $res.error()) return RestApiResponse.jsonMsgResponse(AttesterSlashingValidationSuccess) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getPoolProposerSlashings + # https://ethereum.github.io/beacon-APIs/#/Beacon/getPoolProposerSlashings router.api(MethodGet, "/api/eth/v1/beacon/pool/proposer_slashings") do ( ) -> RestApiResponse: var res: seq[ProposerSlashing] @@ -848,7 +857,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = res.add(item) return RestApiResponse.jsonResponse(res) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolProposerSlashings + # https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolProposerSlashings router.api(MethodPost, "/api/eth/v1/beacon/pool/proposer_slashings") do ( contentBody: Option[ContentBody]) -> RestApiResponse: let slashing = @@ -860,17 +869,15 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http400, InvalidProposerSlashingObjectError, $dres.error()) - let res = dres.get() - let vres = node.exitPool[].validateProposerSlashing(res) - if vres.isErr(): - return RestApiResponse.jsonError(Http400, - ProposerSlashingValidationError, - $vres.error()) - res - node.network.sendProposerSlashing(slashing) + dres.get() + let res = node.sendProposerSlashing(slashing) + if res.isErr(): + return RestApiResponse.jsonError(Http400, + ProposerSlashingValidationError, + $res.error()) return RestApiResponse.jsonMsgResponse(ProposerSlashingValidationSuccess) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/getPoolVoluntaryExits + # https://ethereum.github.io/beacon-APIs/#/Beacon/getPoolVoluntaryExits router.api(MethodGet, "/api/eth/v1/beacon/pool/voluntary_exits") do ( ) -> RestApiResponse: var res: seq[SignedVoluntaryExit] @@ -882,7 +889,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = res.add(item) return RestApiResponse.jsonResponse(res) - # https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolVoluntaryExit + # https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolVoluntaryExit router.api(MethodPost, "/api/eth/v1/beacon/pool/voluntary_exits") do ( contentBody: Option[ContentBody]) -> RestApiResponse: let exit = @@ -894,14 +901,12 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http400, InvalidVoluntaryExitObjectError, $dres.error()) - let res = dres.get() - let vres = node.exitPool[].validateVoluntaryExit(res) - if vres.isErr(): - return RestApiResponse.jsonError(Http400, - VoluntaryExitValidationError, - $vres.error()) - res - node.network.sendVoluntaryExit(exit) + dres.get() + let res = node.sendVoluntaryExit(exit) + if res.isErr(): + return RestApiResponse.jsonError(Http400, + VoluntaryExitValidationError, + $res.error()) return RestApiResponse.jsonMsgResponse(VoluntaryExitValidationSuccess) router.redirect( diff --git a/beacon_chain/rpc/rest_config_api.nim b/beacon_chain/rpc/rest_config_api.nim index f0470973c..43b2a9390 100644 --- a/beacon_chain/rpc/rest_config_api.nim +++ b/beacon_chain/rpc/rest_config_api.nim @@ -4,164 +4,169 @@ # * 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. -import - stew/[endians2, base10], - presto, - chronicles, - nimcrypto/utils as ncrutils, - ../beacon_node_common, ../eth1/eth1_monitor, - ../spec/forks, - ./rest_utils +import stew/[endians2, base10], presto, chronicles, + nimcrypto/utils as ncrutils +import ".."/beacon_node_common, + ".."/eth1/eth1_monitor, + ".."/spec/forks, + "."/rest_utils logScope: topics = "rest_config" proc installConfigApiHandlers*(router: var RestRouter, node: BeaconNode) = + let + cachedForkSchedule = + RestApiResponse.prepareJsonResponse(getForkSchedule(node.dag.cfg)) + cachedConfigSpec = + RestApiResponse.prepareJsonResponse( + ( + CONFIG_NAME: + const_preset, + MAX_COMMITTEES_PER_SLOT: + Base10.toString(MAX_COMMITTEES_PER_SLOT), + TARGET_COMMITTEE_SIZE: + Base10.toString(TARGET_COMMITTEE_SIZE), + MAX_VALIDATORS_PER_COMMITTEE: + Base10.toString(MAX_VALIDATORS_PER_COMMITTEE), + MIN_PER_EPOCH_CHURN_LIMIT: + Base10.toString(node.dag.cfg.MIN_PER_EPOCH_CHURN_LIMIT), + CHURN_LIMIT_QUOTIENT: + Base10.toString(node.dag.cfg.CHURN_LIMIT_QUOTIENT), + SHUFFLE_ROUND_COUNT: + Base10.toString(SHUFFLE_ROUND_COUNT), + MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: + Base10.toString(node.dag.cfg.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT), + MIN_GENESIS_TIME: + Base10.toString(node.dag.cfg.MIN_GENESIS_TIME), + HYSTERESIS_QUOTIENT: + Base10.toString(HYSTERESIS_QUOTIENT), + HYSTERESIS_DOWNWARD_MULTIPLIER: + Base10.toString(HYSTERESIS_DOWNWARD_MULTIPLIER), + HYSTERESIS_UPWARD_MULTIPLIER: + Base10.toString(HYSTERESIS_UPWARD_MULTIPLIER), + SAFE_SLOTS_TO_UPDATE_JUSTIFIED: + Base10.toString(SAFE_SLOTS_TO_UPDATE_JUSTIFIED), + ETH1_FOLLOW_DISTANCE: + Base10.toString(node.dag.cfg.ETH1_FOLLOW_DISTANCE), + TARGET_AGGREGATORS_PER_COMMITTEE: + Base10.toString(TARGET_AGGREGATORS_PER_COMMITTEE), + RANDOM_SUBNETS_PER_VALIDATOR: + Base10.toString(RANDOM_SUBNETS_PER_VALIDATOR), + EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: + Base10.toString(EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION), + SECONDS_PER_ETH1_BLOCK: + Base10.toString(node.dag.cfg.SECONDS_PER_ETH1_BLOCK), + DEPOSIT_CHAIN_ID: + Base10.toString(uint64(node.dag.cfg.DEPOSIT_CHAIN_ID)), + DEPOSIT_NETWORK_ID: + Base10.toString(uint64(node.dag.cfg.DEPOSIT_NETWORK_ID)), + DEPOSIT_CONTRACT_ADDRESS: + $node.dag.cfg.DEPOSIT_CONTRACT_ADDRESS, + MIN_DEPOSIT_AMOUNT: + Base10.toString(MIN_DEPOSIT_AMOUNT), + MAX_EFFECTIVE_BALANCE: + Base10.toString(MAX_EFFECTIVE_BALANCE), + EJECTION_BALANCE: + Base10.toString(node.dag.cfg.EJECTION_BALANCE), + EFFECTIVE_BALANCE_INCREMENT: + Base10.toString(EFFECTIVE_BALANCE_INCREMENT), + GENESIS_FORK_VERSION: + "0x" & $node.dag.cfg.GENESIS_FORK_VERSION, + BLS_WITHDRAWAL_PREFIX: + "0x" & ncrutils.toHex([BLS_WITHDRAWAL_PREFIX]), + GENESIS_DELAY: + Base10.toString(node.dag.cfg.GENESIS_DELAY), + SECONDS_PER_SLOT: + Base10.toString(uint64(SECONDS_PER_SLOT)), + MIN_ATTESTATION_INCLUSION_DELAY: + Base10.toString(MIN_ATTESTATION_INCLUSION_DELAY), + SLOTS_PER_EPOCH: + Base10.toString(SLOTS_PER_EPOCH), + MIN_SEED_LOOKAHEAD: + Base10.toString(MIN_SEED_LOOKAHEAD), + MAX_SEED_LOOKAHEAD: + Base10.toString(MAX_SEED_LOOKAHEAD), + EPOCHS_PER_ETH1_VOTING_PERIOD: + Base10.toString(EPOCHS_PER_ETH1_VOTING_PERIOD), + SLOTS_PER_HISTORICAL_ROOT: + Base10.toString(SLOTS_PER_HISTORICAL_ROOT), + MIN_VALIDATOR_WITHDRAWABILITY_DELAY: + Base10.toString(node.dag.cfg.MIN_VALIDATOR_WITHDRAWABILITY_DELAY), + SHARD_COMMITTEE_PERIOD: + Base10.toString(node.dag.cfg.SHARD_COMMITTEE_PERIOD), + MIN_EPOCHS_TO_INACTIVITY_PENALTY: + Base10.toString(MIN_EPOCHS_TO_INACTIVITY_PENALTY), + EPOCHS_PER_HISTORICAL_VECTOR: + Base10.toString(EPOCHS_PER_HISTORICAL_VECTOR), + EPOCHS_PER_SLASHINGS_VECTOR: + Base10.toString(EPOCHS_PER_SLASHINGS_VECTOR), + HISTORICAL_ROOTS_LIMIT: + Base10.toString(HISTORICAL_ROOTS_LIMIT), + VALIDATOR_REGISTRY_LIMIT: + Base10.toString(VALIDATOR_REGISTRY_LIMIT), + BASE_REWARD_FACTOR: + Base10.toString(BASE_REWARD_FACTOR), + WHISTLEBLOWER_REWARD_QUOTIENT: + Base10.toString(WHISTLEBLOWER_REWARD_QUOTIENT), + PROPOSER_REWARD_QUOTIENT: + Base10.toString(PROPOSER_REWARD_QUOTIENT), + INACTIVITY_PENALTY_QUOTIENT: + Base10.toString(INACTIVITY_PENALTY_QUOTIENT), + MIN_SLASHING_PENALTY_QUOTIENT: + Base10.toString(MIN_SLASHING_PENALTY_QUOTIENT), + PROPORTIONAL_SLASHING_MULTIPLIER: + Base10.toString(PROPORTIONAL_SLASHING_MULTIPLIER), + MAX_PROPOSER_SLASHINGS: + Base10.toString(MAX_PROPOSER_SLASHINGS), + MAX_ATTESTER_SLASHINGS: + Base10.toString(MAX_ATTESTER_SLASHINGS), + MAX_ATTESTATIONS: + Base10.toString(MAX_ATTESTATIONS), + MAX_DEPOSITS: + Base10.toString(MAX_DEPOSITS), + MAX_VOLUNTARY_EXITS: + Base10.toString(MAX_VOLUNTARY_EXITS), + DOMAIN_BEACON_PROPOSER: + "0x" & ncrutils.toHex(uint32(DOMAIN_BEACON_PROPOSER).toBytesLE()), + DOMAIN_BEACON_ATTESTER: + "0x" & ncrutils.toHex(uint32(DOMAIN_BEACON_ATTESTER).toBytesLE()), + DOMAIN_RANDAO: + "0x" & ncrutils.toHex(uint32(DOMAIN_RANDAO).toBytesLE()), + DOMAIN_DEPOSIT: + "0x" & ncrutils.toHex(uint32(DOMAIN_DEPOSIT).toBytesLE()), + DOMAIN_VOLUNTARY_EXIT: + "0x" & ncrutils.toHex(uint32(DOMAIN_VOLUNTARY_EXIT).toBytesLE()), + DOMAIN_SELECTION_PROOF: + "0x" & ncrutils.toHex(uint32(DOMAIN_SELECTION_PROOF).toBytesLE()), + DOMAIN_AGGREGATE_AND_PROOF: + "0x" & ncrutils.toHex(uint32(DOMAIN_AGGREGATE_AND_PROOF).toBytesLE()) + ) + ) + cachedDepositContract = + RestApiResponse.prepareJsonResponse( + ( + chain_id: $node.dag.cfg.DEPOSIT_CHAIN_ID, + address: $node.dag.cfg.DEPOSIT_CONTRACT_ADDRESS + ) + ) + + # https://ethereum.github.io/beacon-APIs/#/Config/getForkSchedule router.api(MethodGet, "/api/eth/v1/config/fork_schedule") do () -> RestApiResponse: - # TODO: Implemenation needs a fix, when forks infrastructure will be - # established. - return RestApiResponse.jsonResponse( - [getStateField(node.dag.headState.data, fork)] - ) + return RestApiResponse.response(cachedForkSchedule, Http200, + "application/json") + # https://ethereum.github.io/beacon-APIs/#/Config/getSpec router.api(MethodGet, "/api/eth/v1/config/spec") do () -> RestApiResponse: - return RestApiResponse.jsonResponse( - ( - CONFIG_NAME: - const_preset, - MAX_COMMITTEES_PER_SLOT: - Base10.toString(MAX_COMMITTEES_PER_SLOT), - TARGET_COMMITTEE_SIZE: - Base10.toString(TARGET_COMMITTEE_SIZE), - MAX_VALIDATORS_PER_COMMITTEE: - Base10.toString(MAX_VALIDATORS_PER_COMMITTEE), - MIN_PER_EPOCH_CHURN_LIMIT: - Base10.toString(node.dag.cfg.MIN_PER_EPOCH_CHURN_LIMIT), - CHURN_LIMIT_QUOTIENT: - Base10.toString(node.dag.cfg.CHURN_LIMIT_QUOTIENT), - SHUFFLE_ROUND_COUNT: - Base10.toString(SHUFFLE_ROUND_COUNT), - MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: - Base10.toString( - node.dag.cfg.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - ), - MIN_GENESIS_TIME: - Base10.toString(node.dag.cfg.MIN_GENESIS_TIME), - HYSTERESIS_QUOTIENT: - Base10.toString(HYSTERESIS_QUOTIENT), - HYSTERESIS_DOWNWARD_MULTIPLIER: - Base10.toString(HYSTERESIS_DOWNWARD_MULTIPLIER), - HYSTERESIS_UPWARD_MULTIPLIER: - Base10.toString(HYSTERESIS_UPWARD_MULTIPLIER), - SAFE_SLOTS_TO_UPDATE_JUSTIFIED: - Base10.toString(SAFE_SLOTS_TO_UPDATE_JUSTIFIED), - ETH1_FOLLOW_DISTANCE: - Base10.toString(node.dag.cfg.ETH1_FOLLOW_DISTANCE), - TARGET_AGGREGATORS_PER_COMMITTEE: - Base10.toString(TARGET_AGGREGATORS_PER_COMMITTEE), - RANDOM_SUBNETS_PER_VALIDATOR: - Base10.toString(RANDOM_SUBNETS_PER_VALIDATOR), - EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: - Base10.toString(EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION), - SECONDS_PER_ETH1_BLOCK: - Base10.toString(node.dag.cfg.SECONDS_PER_ETH1_BLOCK), - DEPOSIT_CHAIN_ID: - Base10.toString(uint64(node.dag.cfg.DEPOSIT_CHAIN_ID)), - DEPOSIT_NETWORK_ID: - Base10.toString(uint64(node.dag.cfg.DEPOSIT_NETWORK_ID)), - DEPOSIT_CONTRACT_ADDRESS: - $node.dag.cfg.DEPOSIT_CONTRACT_ADDRESS, - MIN_DEPOSIT_AMOUNT: - Base10.toString(MIN_DEPOSIT_AMOUNT), - MAX_EFFECTIVE_BALANCE: - Base10.toString(MAX_EFFECTIVE_BALANCE), - EJECTION_BALANCE: - Base10.toString(node.dag.cfg.EJECTION_BALANCE), - EFFECTIVE_BALANCE_INCREMENT: - Base10.toString(EFFECTIVE_BALANCE_INCREMENT), - GENESIS_FORK_VERSION: - "0x" & $node.dag.cfg.GENESIS_FORK_VERSION, - BLS_WITHDRAWAL_PREFIX: - "0x" & ncrutils.toHex([BLS_WITHDRAWAL_PREFIX]), - GENESIS_DELAY: - Base10.toString(node.dag.cfg.GENESIS_DELAY), - SECONDS_PER_SLOT: - Base10.toString(uint64(SECONDS_PER_SLOT)), - MIN_ATTESTATION_INCLUSION_DELAY: - Base10.toString(MIN_ATTESTATION_INCLUSION_DELAY), - SLOTS_PER_EPOCH: - Base10.toString(SLOTS_PER_EPOCH), - MIN_SEED_LOOKAHEAD: - Base10.toString(MIN_SEED_LOOKAHEAD), - MAX_SEED_LOOKAHEAD: - Base10.toString(MAX_SEED_LOOKAHEAD), - EPOCHS_PER_ETH1_VOTING_PERIOD: - Base10.toString(EPOCHS_PER_ETH1_VOTING_PERIOD), - SLOTS_PER_HISTORICAL_ROOT: - Base10.toString(SLOTS_PER_HISTORICAL_ROOT), - MIN_VALIDATOR_WITHDRAWABILITY_DELAY: - Base10.toString( - node.dag.cfg.MIN_VALIDATOR_WITHDRAWABILITY_DELAY), - SHARD_COMMITTEE_PERIOD: - Base10.toString(node.dag.cfg.SHARD_COMMITTEE_PERIOD), - MIN_EPOCHS_TO_INACTIVITY_PENALTY: - Base10.toString(MIN_EPOCHS_TO_INACTIVITY_PENALTY), - EPOCHS_PER_HISTORICAL_VECTOR: - Base10.toString(EPOCHS_PER_HISTORICAL_VECTOR), - EPOCHS_PER_SLASHINGS_VECTOR: - Base10.toString(EPOCHS_PER_SLASHINGS_VECTOR), - HISTORICAL_ROOTS_LIMIT: - Base10.toString(HISTORICAL_ROOTS_LIMIT), - VALIDATOR_REGISTRY_LIMIT: - Base10.toString(VALIDATOR_REGISTRY_LIMIT), - BASE_REWARD_FACTOR: - Base10.toString(BASE_REWARD_FACTOR), - WHISTLEBLOWER_REWARD_QUOTIENT: - Base10.toString(WHISTLEBLOWER_REWARD_QUOTIENT), - PROPOSER_REWARD_QUOTIENT: - Base10.toString(PROPOSER_REWARD_QUOTIENT), - INACTIVITY_PENALTY_QUOTIENT: - Base10.toString(INACTIVITY_PENALTY_QUOTIENT), - MIN_SLASHING_PENALTY_QUOTIENT: - Base10.toString(MIN_SLASHING_PENALTY_QUOTIENT), - PROPORTIONAL_SLASHING_MULTIPLIER: - Base10.toString(PROPORTIONAL_SLASHING_MULTIPLIER), - MAX_PROPOSER_SLASHINGS: - Base10.toString(MAX_PROPOSER_SLASHINGS), - MAX_ATTESTER_SLASHINGS: - Base10.toString(MAX_ATTESTER_SLASHINGS), - MAX_ATTESTATIONS: - Base10.toString(MAX_ATTESTATIONS), - MAX_DEPOSITS: - Base10.toString(MAX_DEPOSITS), - MAX_VOLUNTARY_EXITS: - Base10.toString(MAX_VOLUNTARY_EXITS), - DOMAIN_BEACON_PROPOSER: - "0x" & ncrutils.toHex(uint32(DOMAIN_BEACON_PROPOSER).toBytesLE()), - DOMAIN_BEACON_ATTESTER: - "0x" & ncrutils.toHex(uint32(DOMAIN_BEACON_ATTESTER).toBytesLE()), - DOMAIN_RANDAO: - "0x" & ncrutils.toHex(uint32(DOMAIN_RANDAO).toBytesLE()), - DOMAIN_DEPOSIT: - "0x" & ncrutils.toHex(uint32(DOMAIN_DEPOSIT).toBytesLE()), - DOMAIN_VOLUNTARY_EXIT: - "0x" & ncrutils.toHex(uint32(DOMAIN_VOLUNTARY_EXIT).toBytesLE()), - DOMAIN_SELECTION_PROOF: - "0x" & ncrutils.toHex(uint32(DOMAIN_SELECTION_PROOF).toBytesLE()), - DOMAIN_AGGREGATE_AND_PROOF: - "0x" & ncrutils.toHex(uint32(DOMAIN_AGGREGATE_AND_PROOF).toBytesLE()) - ) - ) + return RestApiResponse.response(cachedConfigSpec, Http200, + "application/json") + # https://ethereum.github.io/beacon-APIs/#/Config/getDepositContract router.api(MethodGet, "/api/eth/v1/config/deposit_contract") do () -> RestApiResponse: - return RestApiResponse.jsonResponse( - ( - chain_id: $node.dag.cfg.DEPOSIT_CHAIN_ID, - address: $node.dag.cfg.DEPOSIT_CONTRACT_ADDRESS - ) - ) + return RestApiResponse.response(cachedDepositContract, Http200, + "application/json") router.redirect( MethodGet, diff --git a/beacon_chain/rpc/rest_debug_api.nim b/beacon_chain/rpc/rest_debug_api.nim index ce185c230..4b678352e 100644 --- a/beacon_chain/rpc/rest_debug_api.nim +++ b/beacon_chain/rpc/rest_debug_api.nim @@ -1,13 +1,13 @@ -import - std/sequtils, - presto, - chronicles, - ../version, ../beacon_node_common, - ./rest_utils +import std/sequtils +import presto, chronicles +import ".."/[version, beacon_node_common], + ".."/spec/forks, + "."/rest_utils logScope: topics = "rest_debug" proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) = + # https://ethereum.github.io/beacon-APIs/#/Debug/getState router.api(MethodGet, "/api/eth/v1/debug/beacon/states/{state_id}") do ( state_id: StateIdent) -> RestApiResponse: @@ -22,9 +22,40 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) = $bres.error()) bres.get() node.withStateForBlockSlot(bslot): - return RestApiResponse.jsonResponse(stateData.data.hbsPhase0.data) + case stateData.data.beaconStateFork + of BeaconStateFork.forkPhase0: + return RestApiResponse.jsonResponse(stateData.data.hbsPhase0.data) + of BeaconStateFork.forkAltair: + return RestApiResponse.jsonError(Http404, StateNotFoundError) return RestApiResponse.jsonError(Http500, InternalServerError) + # https://ethereum.github.io/beacon-APIs/#/Debug/getStateV2 + router.api(MethodGet, + "/api/eth/v2/debug/beacon/states/{state_id}") do ( + state_id: StateIdent) -> RestApiResponse: + let bslot = + block: + if state_id.isErr(): + return RestApiResponse.jsonError(Http400, InvalidStateIdValueError, + $state_id.error()) + let bres = node.getBlockSlot(state_id.get()) + if bres.isErr(): + return RestApiResponse.jsonError(Http404, StateNotFoundError, + $bres.error()) + bres.get() + node.withStateForBlockSlot(bslot): + case stateData.data.beaconStateFork + of BeaconStateFork.forkPhase0: + return RestApiResponse.jsonResponse( + (version: "phase0", data: stateData.data.hbsPhase0.data) + ) + of BeaconStateFork.forkAltair: + return RestApiResponse.jsonResponse( + (version: "altair", data: stateData.data.hbsAltair.data) + ) + return RestApiResponse.jsonError(Http500, InternalServerError) + + # https://ethereum.github.io/beacon-APIs/#/Debug/getDebugChainHeads router.api(MethodGet, "/api/eth/v1/debug/beacon/heads") do () -> RestApiResponse: return RestApiResponse.jsonResponse( @@ -41,6 +72,11 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) = "/eth/v1/debug/beacon/heads", "/api/eth/v1/debug/beacon/heads" ) + router.redirect( + MethodGet, + "/eth/v2/debug/beacon/heads", + "/api/eth/v2/debug/beacon/heads" + ) proc getDebugChainHeads*(): RestResponse[GetDebugChainHeadsResponse] {. rest, endpoint: "/eth/v1/debug/beacon/heads", diff --git a/beacon_chain/rpc/rest_event_api.nim b/beacon_chain/rpc/rest_event_api.nim index e33b44743..b1e877e58 100644 --- a/beacon_chain/rpc/rest_event_api.nim +++ b/beacon_chain/rpc/rest_event_api.nim @@ -49,6 +49,7 @@ proc validateEventTopics(events: seq[EventTopic]): Result[EventTopics, ok(res) proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) = + # https://ethereum.github.io/beacon-APIs/#/Events/eventstream router.api(MethodGet, "/api/eth/v1/events") do ( topics: seq[EventTopic]) -> RestApiResponse: # TODO (cheatfate): This call is not fully implemented yet, because there diff --git a/beacon_chain/rpc/rest_node_api.nim b/beacon_chain/rpc/rest_node_api.nim index 3f89985c8..3fef7b833 100644 --- a/beacon_chain/rpc/rest_node_api.nim +++ b/beacon_chain/rpc/rest_node_api.nim @@ -126,6 +126,7 @@ proc getP2PAddresses(node: BeaconNode): Option[seq[string]] = return some(addresses) proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) = + # https://ethereum.github.io/beacon-APIs/#/Node/getNetworkIdentity router.api(MethodGet, "/api/eth/v1/node/identity") do () -> RestApiResponse: let discoveryAddresses = block: @@ -156,6 +157,7 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) = ) ) + # https://ethereum.github.io/beacon-APIs/#/Node/getPeers router.api(MethodGet, "/api/eth/v1/node/peers") do ( state: seq[PeerStateKind], direction: seq[PeerDirectKind]) -> RestApiResponse: @@ -198,6 +200,7 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) = res.add(peer) return RestApiResponse.jsonResponseWMeta(res, (count: uint64(len(res)))) + # https://ethereum.github.io/beacon-APIs/#/Node/getPeerCount router.api(MethodGet, "/api/eth/v1/node/peer_count") do () -> RestApiResponse: var res: RestNodePeerCount for item in node.network.peers.values(): @@ -214,6 +217,7 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) = discard return RestApiResponse.jsonResponse(res) + # https://ethereum.github.io/beacon-APIs/#/Node/getPeer router.api(MethodGet, "/api/eth/v1/node/peers/{peer_id}") do ( peer_id: PeerID) -> RestApiResponse: let peer = @@ -237,14 +241,17 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) = ) ) + # https://ethereum.github.io/beacon-APIs/#/Node/getNodeVersion router.api(MethodGet, "/api/eth/v1/node/version") do () -> RestApiResponse: return RestApiResponse.jsonResponse( (version: "Nimbus/" & fullVersionStr) ) + # https://ethereum.github.io/beacon-APIs/#/Node/getSyncingStatus router.api(MethodGet, "/api/eth/v1/node/syncing") do () -> RestApiResponse: return RestApiResponse.jsonResponse(node.syncManager.getInfo()) + # https://ethereum.github.io/beacon-APIs/#/Node/getHealth router.api(MethodGet, "/api/eth/v1/node/health") do () -> RestApiResponse: # TODO: Add ability to detect node's issues and return 503 error according # to specification. diff --git a/beacon_chain/rpc/rest_validator_api.nim b/beacon_chain/rpc/rest_validator_api.nim index 32909b1b2..74c8e2ca8 100644 --- a/beacon_chain/rpc/rest_validator_api.nim +++ b/beacon_chain/rpc/rest_validator_api.nim @@ -11,7 +11,6 @@ import ../spec/datatypes/[phase0], ../beacon_node_common, ../networking/eth2_network, ../consensus_object_pools/[blockchain_dag, spec_cache, attestation_pool], - ../gossip_processing/gossip_validation, ../validators/validator_duties, ../spec/[forks, network], ./rest_utils @@ -19,7 +18,7 @@ import logScope: topics = "rest_validatorapi" proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = - # https://ethereum.github.io/eth2.0-APIs/#/Validator/getAttesterDuties + # https://ethereum.github.io/beacon-APIs/#/Validator/getAttesterDuties router.api(MethodPost, "/api/eth/v1/validator/duties/attester/{epoch}") do ( epoch: Epoch, contentBody: Option[ContentBody]) -> RestApiResponse: let indexList = @@ -98,7 +97,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = res return RestApiResponse.jsonResponseWRoot(duties, droot) - # https://ethereum.github.io/eth2.0-APIs/#/Validator/getProposerDuties + # https://ethereum.github.io/beacon-APIs/#/Validator/getProposerDuties router.api(MethodGet, "/api/eth/v1/validator/duties/proposer/{epoch}") do ( epoch: Epoch) -> RestApiResponse: let qepoch = @@ -142,7 +141,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = res return RestApiResponse.jsonResponseWRoot(duties, droot) - # https://ethereum.github.io/eth2.0-APIs/#/Validator/produceBlock + # https://ethereum.github.io/beacon-APIs/#/Validator/produceBlock router.api(MethodGet, "/api/eth/v1/validator/blocks/{slot}") do ( slot: Slot, randao_reveal: Option[ValidatorSig], graffiti: Option[GraffitiBytes]) -> RestApiResponse: @@ -204,6 +203,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = of BeaconBlockFork.Altair: return RestApiResponse.jsonError(Http400, BlockProduceError) + # https://ethereum.github.io/beacon-APIs/#/Validator/produceBlockV2 router.api(MethodGet, "/api/eth/v2/validator/blocks/{slot}") do ( slot: Slot, randao_reveal: Option[ValidatorSig], graffiti: Option[GraffitiBytes]) -> RestApiResponse: @@ -271,7 +271,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = (version: "altair", data: message.altairBlock.message) ) - # https://ethereum.github.io/eth2.0-APIs/#/Validator/produceAttestationData + # https://ethereum.github.io/beacon-APIs/#/Validator/produceAttestationData router.api(MethodGet, "/api/eth/v1/validator/attestation_data") do ( slot: Option[Slot], committee_index: Option[CommitteeIndex]) -> RestApiResponse: @@ -307,7 +307,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = makeAttestationData(epochRef, qhead.atSlot(qslot), qindex) return RestApiResponse.jsonResponse(adata) - # https://ethereum.github.io/eth2.0-APIs/#/Validator/getAggregatedAttestation + # https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestation router.api(MethodGet, "/api/eth/v1/validator/aggregate_attestation") do ( attestation_data_root: Option[Eth2Digest], slot: Option[Slot]) -> RestApiResponse: @@ -339,7 +339,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = res.get() return RestApiResponse.jsonResponse(attestation) - # https://ethereum.github.io/eth2.0-APIs/#/Validator/publishAggregateAndProofs + # https://ethereum.github.io/beacon-APIs/#/Validator/publishAggregateAndProofs router.api(MethodPost, "/api/eth/v1/validator/aggregate_and_proofs") do ( contentBody: Option[ContentBody]) -> RestApiResponse: let proofs = @@ -352,25 +352,28 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = InvalidAggregateAndProofObjectError, $dres.error()) dres.get() - - for item in proofs: - let wallTime = node.processor.getCurrentBeaconTime() - let res = await node.attestationPool.validateAggregate( - node.processor.batchCrypto, item, wallTime - ) - if res.isErr(): - return RestApiResponse.jsonError(Http400, - AggregateAndProofValidationError, - $res.error()) - node.network.broadcast( - getAggregateAndProofsTopic(node.dag.forkDigests.phase0), item) - notice "Aggregated attestation sent", - attestation = shortLog(item.message.aggregate), - signature = shortLog(item.signature) - + # Since our validation logic supports batch processing, we will submit all + # aggregated attestations for validation. + var pending = + block: + var res: seq[Future[SendResult]] + for proof in proofs: + res.add(node.sendAggregateAndProof(proof)) + res + await allFutures(pending) + for future in pending: + if future.done(): + let res = future.read() + if res.isErr(): + return RestApiResponse.jsonError(Http400, + AggregateAndProofValidationError, + $res.error()) + else: + return RestApiResponse.jsonError(Http500, + "Unexpected server failure, while sending aggregate and proof") return RestApiResponse.jsonMsgResponse(AggregateAndProofValidationSuccess) - # https://ethereum.github.io/eth2.0-APIs/#/Validator/prepareBeaconCommitteeSubnet + # https://ethereum.github.io/beacon-APIs/#/Validator/prepareBeaconCommitteeSubnet router.api(MethodPost, "/api/eth/v1/validator/beacon_committee_subscriptions") do ( contentBody: Option[ContentBody]) -> RestApiResponse: diff --git a/beacon_chain/rpc/rpc_beacon_api.nim b/beacon_chain/rpc/rpc_beacon_api.nim index ad75666c1..c5c450db1 100644 --- a/beacon_chain/rpc/rpc_beacon_api.nim +++ b/beacon_chain/rpc/rpc_beacon_api.nim @@ -16,7 +16,6 @@ import ../beacon_node_common, ../networking/eth2_network, ../validators/validator_duties, - ../gossip_processing/gossip_validation, ../consensus_object_pools/blockchain_dag, ../spec/[eth2_merkleization, forks, network], ../spec/datatypes/[phase0], @@ -470,7 +469,8 @@ proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {. rpcServer.rpc("post_v1_beacon_pool_attestations") do ( attestation: Attestation) -> bool: - return await node.sendAttestation(attestation) + let res = await node.sendAttestation(attestation) + return res.isOk() rpcServer.rpc("get_v1_beacon_pool_attester_slashings") do ( ) -> seq[AttesterSlashing]: @@ -485,14 +485,8 @@ proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {. rpcServer.rpc("post_v1_beacon_pool_attester_slashings") do ( slashing: AttesterSlashing) -> bool: - if isNil(node.exitPool): - raise newException(CatchableError, "Exit pool is not yet available!") - let validity = node.exitPool[].validateAttesterSlashing(slashing) - if validity.isOk: - node.network.sendAttesterSlashing(slashing) - else: - raise newException(CatchableError, $(validity.error[1])) - return true + let res = node.sendAttesterSlashing(slashing) + return res.isOk() rpcServer.rpc("get_v1_beacon_pool_proposer_slashings") do ( ) -> seq[ProposerSlashing]: @@ -507,14 +501,8 @@ proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {. rpcServer.rpc("post_v1_beacon_pool_proposer_slashings") do ( slashing: ProposerSlashing) -> bool: - if isNil(node.exitPool): - raise newException(CatchableError, "Exit pool is not yet available!") - let validity = node.exitPool[].validateProposerSlashing(slashing) - if validity.isOk: - node.network.sendProposerSlashing(slashing) - else: - raise newException(CatchableError, $(validity.error[1])) - return true + let res = node.sendProposerSlashing(slashing) + return res.isOk() rpcServer.rpc("get_v1_beacon_pool_voluntary_exits") do ( ) -> seq[SignedVoluntaryExit]: @@ -529,11 +517,5 @@ proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {. rpcServer.rpc("post_v1_beacon_pool_voluntary_exits") do ( exit: SignedVoluntaryExit) -> bool: - if isNil(node.exitPool): - raise newException(CatchableError, "Exit pool is not yet available!") - let validity = node.exitPool[].validateVoluntaryExit(exit) - if validity.isOk: - node.network.sendVoluntaryExit(exit) - else: - raise newException(CatchableError, $(validity.error[1])) - return true + let res = node.sendVoluntaryExit(exit) + return res.isOk() diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index 130d5c3d9..66d93dc26 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -59,85 +59,144 @@ type RestAttestationError | RestGenericError -proc jsonResponseWRoot*(t: typedesc[RestApiResponse], - data: auto, - dependent_root: Eth2Digest): RestApiResponse = - var stream = memoryOutput() - var writer = JsonWriter[RestJson].init(stream) - writer.beginRecord() - writer.writeField("dependent_root", dependent_root) - writer.writeField("data", data) - writer.endRecord() - RestApiResponse.response(stream.getOutput(seq[byte]), Http200, - "application/json") +{.push raises: [Defect].} -proc jsonResponse*(t: typedesc[RestApiResponse], - data: auto): RestApiResponse = - var stream = memoryOutput() - var writer = JsonWriter[RestJson].init(stream) - writer.beginRecord() - writer.writeField("data", data) - writer.endRecord() - RestApiResponse.response(stream.getOutput(seq[byte]), Http200, - "application/json") +proc prepareJsonResponse*(t: typedesc[RestApiResponse], d: auto): seq[byte] = + let res = + block: + var default: seq[byte] + try: + var stream = memoryOutput() + var writer = JsonWriter[RestJson].init(stream) + writer.beginRecord() + writer.writeField("data", d) + writer.endRecord() + stream.getOutput(seq[byte]) + except SerializationError: + default + except IOError: + default + res + +proc jsonResponseWRoot*(t: typedesc[RestApiResponse], data: auto, + dependent_root: Eth2Digest): RestApiResponse = + let res = + block: + var default: seq[byte] + try: + var stream = memoryOutput() + var writer = JsonWriter[RestJson].init(stream) + writer.beginRecord() + writer.writeField("dependent_root", dependent_root) + writer.writeField("data", data) + writer.endRecord() + stream.getOutput(seq[byte]) + except SerializationError: + default + except IOError: + default + RestApiResponse.response(res, Http200, "application/json") + +proc jsonResponse*(t: typedesc[RestApiResponse], data: auto): RestApiResponse = + let res = + block: + var default: seq[byte] + try: + var stream = memoryOutput() + var writer = JsonWriter[RestJson].init(stream) + writer.beginRecord() + writer.writeField("data", data) + writer.endRecord() + stream.getOutput(seq[byte]) + except SerializationError: + default + except IOError: + default + RestApiResponse.response(res, Http200, "application/json") proc jsonResponseWMeta*(t: typedesc[RestApiResponse], data: auto, meta: auto): RestApiResponse = - var stream = memoryOutput() - var writer = JsonWriter[RestJson].init(stream) - writer.beginRecord() - writer.writeField("data", data) - writer.writeField("meta", meta) - writer.endRecord() - RestApiResponse.response(stream.getOutput(seq[byte]), Http200, - "application/json") + let res = + block: + var default: seq[byte] + try: + var stream = memoryOutput() + var writer = JsonWriter[RestJson].init(stream) + writer.beginRecord() + writer.writeField("data", data) + writer.writeField("meta", meta) + writer.endRecord() + stream.getOutput(seq[byte]) + except SerializationError: + default + except IOError: + default + RestApiResponse.response(res, Http200, "application/json") proc jsonMsgResponse*(t: typedesc[RestApiResponse], msg: string = ""): RestApiResponse = let data = block: - var default: seq[string] - var stream = memoryOutput() - var writer = JsonWriter[RestJson].init(stream) - writer.beginRecord() - writer.writeField("code", "200") - writer.writeField("message", msg) - writer.writeField("stacktrace", default) - writer.endRecord() - stream.getOutput(seq[byte]) + var default: seq[byte] + try: + var defstrings: seq[string] + var stream = memoryOutput() + var writer = JsonWriter[RestJson].init(stream) + writer.beginRecord() + writer.writeField("code", "200") + writer.writeField("message", msg) + writer.writeField("stacktrace", defstrings) + writer.endRecord() + stream.getOutput(seq[byte]) + except SerializationError: + default + except IOError: + default RestApiResponse.response(data, Http200, "application/json") proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200, msg: string = ""): RestApiResponse = let data = block: - var default: seq[string] - var stream = memoryOutput() - var writer = JsonWriter[RestJson].init(stream) - writer.beginRecord() - writer.writeField("code", Base10.toString(uint64(status.toInt()))) - writer.writeField("message", msg) - writer.writeField("stacktrace", default) - writer.endRecord() - stream.getOutput(string) + var default: string + try: + var defstrings: seq[string] + var stream = memoryOutput() + var writer = JsonWriter[RestJson].init(stream) + writer.beginRecord() + writer.writeField("code", Base10.toString(uint64(status.toInt()))) + writer.writeField("message", msg) + writer.writeField("stacktrace", defstrings) + writer.endRecord() + stream.getOutput(string) + except SerializationError: + default + except IOError: + default RestApiResponse.error(status, data, "application/json") proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200, msg: string = "", stacktrace: string): RestApiResponse = let data = block: - var default: seq[string] - var stream = memoryOutput() - var writer = JsonWriter[RestJson].init(stream) - writer.beginRecord() - writer.writeField("code", Base10.toString(uint64(status.toInt()))) - writer.writeField("message", msg) - if len(stacktrace) > 0: - writer.writeField("stacktrace", [stacktrace]) - else: - writer.writeField("stacktrace", default) - writer.endRecord() - stream.getOutput(string) + var default: string + try: + var defstrings: seq[string] + var stream = memoryOutput() + var writer = JsonWriter[RestJson].init(stream) + writer.beginRecord() + writer.writeField("code", Base10.toString(uint64(status.toInt()))) + writer.writeField("message", msg) + if len(stacktrace) > 0: + writer.writeField("stacktrace", [stacktrace]) + else: + writer.writeField("stacktrace", defstrings) + writer.endRecord() + stream.getOutput(string) + except SerializationError: + default + except IOError: + default RestApiResponse.error(status, data, "application/json") proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200, @@ -145,14 +204,20 @@ proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200, stacktraces: openarray[string]): RestApiResponse = let data = block: - var stream = memoryOutput() - var writer = JsonWriter[RestJson].init(stream) - writer.beginRecord() - writer.writeField("code", Base10.toString(uint64(status.toInt()))) - writer.writeField("message", msg) - writer.writeField("stacktrace", stacktraces) - writer.endRecord() - stream.getOutput(string) + var default: string + try: + var stream = memoryOutput() + var writer = JsonWriter[RestJson].init(stream) + writer.beginRecord() + writer.writeField("code", Base10.toString(uint64(status.toInt()))) + writer.writeField("message", msg) + writer.writeField("stacktrace", stacktraces) + writer.endRecord() + stream.getOutput(string) + except SerializationError: + default + except IOError: + default RestApiResponse.error(status, data, "application/json") proc jsonErrorList*(t: typedesc[RestApiResponse], @@ -160,21 +225,28 @@ proc jsonErrorList*(t: typedesc[RestApiResponse], msg: string = "", failures: auto): RestApiResponse = let data = block: - var stream = memoryOutput() - var writer = JsonWriter[RestJson].init(stream) - writer.beginRecord() - writer.writeField("code", Base10.toString(uint64(status.toInt()))) - writer.writeField("message", msg) - writer.writeField("failures", failures) - writer.endRecord() - stream.getOutput(string) + var default: string + try: + var stream = memoryOutput() + var writer = JsonWriter[RestJson].init(stream) + writer.beginRecord() + writer.writeField("code", Base10.toString(uint64(status.toInt()))) + writer.writeField("message", msg) + writer.writeField("failures", failures) + writer.endRecord() + stream.getOutput(string) + except SerializationError: + default + except IOError: + default RestApiResponse.error(status, data, "application/json") template hexOriginal(data: openarray[byte]): string = "0x" & ncrutils.toHex(data, true) ## uint64 -proc writeValue*(w: var JsonWriter[RestJson], value: uint64) = +proc writeValue*(w: var JsonWriter[RestJson], value: uint64) {. + raises: [IOError, Defect].} = writeValue(w, Base10.toString(value)) proc readValue*(reader: var JsonReader[RestJson], value: var uint64) {. @@ -187,7 +259,8 @@ proc readValue*(reader: var JsonReader[RestJson], value: var uint64) {. reader.raiseUnexpectedValue($res.error()) ## byte -proc writeValue*(w: var JsonWriter[RestJson], value: byte) = +proc writeValue*(w: var JsonWriter[RestJson], value: byte) {. + raises: [IOError, Defect].} = var data: array[1, byte] data[0] = value writeValue(w, hexOriginal(data)) @@ -203,7 +276,8 @@ proc readValue*(reader: var JsonReader[RestJson], value: var byte) {. "byte value should be a valid hex string") ## DomainType -proc writeValue*(w: var JsonWriter[RestJson], value: DomainType) = +proc writeValue*(w: var JsonWriter[RestJson], value: DomainType) {. + raises: [IOError, Defect].} = writeValue(w, hexOriginal(uint32(value).toBytesLE())) proc readValue*(reader: var JsonReader[RestJson], value: var DomainType) {. @@ -353,11 +427,13 @@ proc writeValue*(writer: var JsonWriter[RestJson], value: BitSeq) {. writeValue(writer, hexOriginal(value.bytes())) ## BitList -proc readValue*(reader: var JsonReader[RestJson], value: var BitList) = +proc readValue*(reader: var JsonReader[RestJson], value: var BitList) {. + raises: [IOError, SerializationError, Defect].} = type T = type(value) value = T readValue(reader, BitSeq) -proc writeValue*(writer: var JsonWriter[RestJson], value: BitList) = +proc writeValue*(writer: var JsonWriter[RestJson], value: BitList) {. + raises: [IOError, Defect].} = writeValue(writer, BitSeq value) ## Eth2Digest @@ -489,13 +565,21 @@ RestJson.useCustomSerialization(phase0.BeaconState.justification_bits): writer.writeValue "0x" & toHex([value]) proc encodeBytes*[T: EncodeTypes](value: T, - contentType: string): RestResult[seq[byte]] = + contentType: string): RestResult[seq[byte]] = case contentType of "application/json": - var stream = memoryOutput() - var writer = JsonWriter[RestJson].init(stream) - writer.writeValue(value) - ok(stream.getOutput(seq[byte])) + let data = + block: + try: + var stream = memoryOutput() + var writer = JsonWriter[RestJson].init(stream) + writer.writeValue(value) + stream.getOutput(seq[byte]) + except IOError: + return err("Input/output error") + except SerializationError: + return err("Serialization error") + ok(data) else: err("Content-Type not supported") @@ -503,10 +587,18 @@ proc encodeBytes*[T: EncodeArrays](value: T, contentType: string): RestResult[seq[byte]] = case contentType of "application/json": - var stream = memoryOutput() - var writer = JsonWriter[RestJson].init(stream) - writer.writeArray(value) - ok(stream.getOutput(seq[byte])) + let data = + block: + try: + var stream = memoryOutput() + var writer = JsonWriter[RestJson].init(stream) + writer.writeArray(value) + stream.getOutput(seq[byte]) + except IOError: + return err("Input/output error") + except SerializationError: + return err("Serialization error") + ok(data) else: err("Content-Type not supported") diff --git a/beacon_chain/spec/eth2_apis/rest_types.nim b/beacon_chain/spec/eth2_apis/rest_types.nim index 818889e72..479d5be7b 100644 --- a/beacon_chain/spec/eth2_apis/rest_types.nim +++ b/beacon_chain/spec/eth2_apis/rest_types.nim @@ -277,7 +277,7 @@ type GetDebugChainHeadsResponse* = DataEnclosedObject[seq[RestChainHead]] GetDepositContractResponse* = DataEnclosedObject[RestDepositContract] GetEpochCommitteesResponse* = DataEnclosedObject[RestGenesis] - GetForkScheduleResponse* = DataEnclosedObject[Fork] + GetForkScheduleResponse* = DataEnclosedObject[seq[Fork]] GetGenesisResponse* = DataEnclosedObject[RestGenesis] GetNetworkIdentityResponse* = DataEnclosedObject[RestNetworkIdentity] GetPeerCountResponse* = DataMetaEnclosedObject[RestPeerCount] diff --git a/beacon_chain/spec/forks.nim b/beacon_chain/spec/forks.nim index 3322d23fc..61a58df84 100644 --- a/beacon_chain/spec/forks.nim +++ b/beacon_chain/spec/forks.nim @@ -297,3 +297,11 @@ proc nextForkEpochAtEpoch*(cfg: RuntimeConfig, epoch: Epoch): Epoch = cfg.ALTAIR_FORK_EPOCH else: FAR_FUTURE_EPOCH + +func getForkSchedule*(cfg: RuntimeConfig): array[2, 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. + [cfg.genesisFork(), cfg.altairFork()] diff --git a/beacon_chain/validator_client/api.nim b/beacon_chain/validator_client/api.nim index 64661b844..c5d118a11 100644 --- a/beacon_chain/validator_client/api.nim +++ b/beacon_chain/validator_client/api.nim @@ -343,9 +343,10 @@ template firstSuccessTimeout*(vc: ValidatorClientRef, respType: typedesc, RestBeaconNodeStatus.NotSynced, RestBeaconNodeStatus.Uninitalized} let offlineNodes = vc.beaconNodes.filterIt(it.status in offlineMask) + let onlineNodesCount = len(vc.beaconNodes) - len(offlineNodes) warn "No working beacon nodes available, refreshing nodes status", - online_nodes = len(onlineNodes), offline_nodes = len(offlineNodes) + online_nodes = onlineNodesCount, offline_nodes = len(offlineNodes) var checkFut = vc.checkNodes(offlineMask) @@ -475,6 +476,30 @@ proc getAttesterDuties*(vc: ValidatorClientRef, epoch: Epoch, raise newException(ValidatorApiError, "Unable to retrieve attester duties") +proc getForkSchedule*(vc: ValidatorClientRef): Future[seq[Fork]] {.async.} = + logScope: request = "getForkSchedule" + vc.firstSuccessTimeout(RestResponse[GetForkScheduleResponse], SlotDuration, + getForkSchedule(it)): + if apiResponse.isErr(): + debug "Unable to retrieve head state's fork", endpoint = node, + error = apiResponse.error() + RestBeaconNodeStatus.Offline + else: + let response = apiResponse.get() + case response.status + of 200: + debug "Received successfull response", endpoint = node + return response.data.data + of 500: + debug "Received internal error response", + response_code = response.status, endpoint = node + RestBeaconNodeStatus.Offline + else: + debug "Received unexpected error response", + response_code = response.status, endpoint = node + RestBeaconNodeStatus.Offline + raise newException(ValidatorApiError, "Unable to retrieve fork schedule") + proc getHeadStateFork*(vc: ValidatorClientRef): Future[Fork] {.async.} = logScope: request = "getHeadStateFork" let stateIdent = StateIdent.init(StateIdentType.Head) diff --git a/beacon_chain/validator_client/attestation_service.nim b/beacon_chain/validator_client/attestation_service.nim index 50b1f574f..8ae54ca02 100644 --- a/beacon_chain/validator_client/attestation_service.nim +++ b/beacon_chain/validator_client/attestation_service.nim @@ -27,6 +27,7 @@ proc serveAttestation(service: AttestationServiceRef, adata: AttestationData, let signingRoot = compute_attestation_root(fork, vc.beaconGenesis.genesis_validators_root, adata) + let attestationRoot = adata.hash_tree_root() let vindex = validator.index.get() let notSlashable = vc.attachedValidators.slashingProtection @@ -45,6 +46,11 @@ proc serveAttestation(service: AttestationServiceRef, adata: AttestationData, Natural(duty.data.validator_committee_index), fork, vc.beaconGenesis.genesis_validators_root) + debug "Sending attestation", attestation = shortLog(attestation), + validator = shortLog(validator), validator_index = vindex, + attestation_root = shortLog(attestationRoot), + delay = vc.getDelay(seconds(int64(SECONDS_PER_SLOT) div 3)) + let res = try: await vc.submitPoolAttestations(@[attestation]) @@ -63,19 +69,17 @@ proc serveAttestation(service: AttestationServiceRef, adata: AttestationData, return false let delay = vc.getDelay(seconds(int64(SECONDS_PER_SLOT) div 3)) - let indexInCommittee = duty.data.validator_committee_index if res: notice "Attestation published", attestation = shortLog(attestation), validator = shortLog(validator), validator_index = vindex, delay = delay, - indexInCommittee = indexInCommittee + attestation_root = attestationRoot else: warn "Attestation was not accepted by beacon node", attestation = shortLog(attestation), validator = shortLog(validator), - validator_index = vindex, delay = delay, - indexInCommittee = indexInCommittee + validator_index = vindex, delay = delay return res proc serveAggregateAndProof*(service: AttestationServiceRef, @@ -94,6 +98,13 @@ proc serveAggregateAndProof*(service: AttestationServiceRef, let aggregationSlot = proof.aggregate.data.slot let vindex = validator.index.get() + + debug "Sending aggregated attestation", + attestation = shortLog(signedProof.message.aggregate), + validator = shortLog(validator), validator_index = vindex, + aggregationSlot = aggregationSlot, + delay = vc.getDelay(seconds((int64(SECONDS_PER_SLOT) div 3) * 2)) + let res = try: await vc.publishAggregateAndProofs(@[signedProof]) @@ -277,24 +288,22 @@ proc publishAttestationsAndAggregates(service: AttestationServiceRef, committee_index: CommitteeIndex, duties: seq[DutyAndProof]) {.async.} = let vc = service.client - let aggregateTime = - # chronos.Duration substraction could not return negative value, in such - # case it will return `ZeroDuration`. - vc.beaconClock.durationToNextSlot() - seconds(int64(SECONDS_PER_SLOT) div 3) - # Waiting for blocks to be published before attesting. - # TODO (cheatfate): Here should be present timeout. let startTime = Moment.now() - await vc.waitForBlockPublished(slot) - let dur = Moment.now() - startTime - debug "Block proposal awaited", slot = slot, duration = dur + try: + let timeout = seconds(int64(SECONDS_PER_SLOT) div 3) # 4.seconds in mainnet + await vc.waitForBlockPublished(slot).wait(timeout) + let dur = Moment.now() - startTime + debug "Block proposal awaited", slot = slot, duration = dur + except AsyncTimeoutError: + let dur = Moment.now() - startTime + debug "Block was not produced in time", slot = slot, duration = dur block: let delay = vc.getDelay(seconds(int64(SECONDS_PER_SLOT) div 3)) debug "Producing attestations", delay = delay, slot = slot, committee_index = committee_index, duties_count = len(duties) - let ad = try: await service.produceAndPublishAttestations(slot, committee_index, duties) @@ -308,6 +317,10 @@ proc publishAttestationsAndAggregates(service: AttestationServiceRef, err_name = exc.name, err_msg = exc.msg return + let aggregateTime = + # chronos.Duration substraction could not return negative value, in such + # case it will return `ZeroDuration`. + vc.beaconClock.durationToNextSlot() - seconds(int64(SECONDS_PER_SLOT) div 3) if aggregateTime != ZeroDuration: await sleepAsync(aggregateTime) diff --git a/beacon_chain/validator_client/block_service.nim b/beacon_chain/validator_client/block_service.nim index 99791a794..174ae7128 100644 --- a/beacon_chain/validator_client/block_service.nim +++ b/beacon_chain/validator_client/block_service.nim @@ -53,6 +53,11 @@ proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot, let signedBlock = phase0.SignedBeaconBlock(message: beaconBlock, root: blockRoot, signature: signature) + + debug "Sending block", blck = shortLog(signedBlock.message), + signature = shortLog(signature), blockRoot = shortLog(blockRoot), + validator = shortLog(validator) + let res = try: await vc.publishBlock(signedBlock) @@ -69,15 +74,15 @@ proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot, return if res: notice "Block published", blck = shortLog(signedBlock.message), - blockRoot = shortLog(blockRoot), validator = shortLog(validator), - validator_index = validator.index.get() + blockRoot = shortLog(blockRoot), validator = shortLog(validator), + validator_index = validator.index.get() else: warn "Block was not accepted by beacon node", - blck = shortLog(signedBlock.message), - blockRoot = shortLog(blockRoot), - validator = shortLog(validator), - validator_index = validator.index.get(), - wall_slot = currentSlot + blck = shortLog(signedBlock.message), + blockRoot = shortLog(blockRoot), + validator = shortLog(validator), + validator_index = validator.index.get(), + wall_slot = currentSlot else: warn "Slashing protection activated for block proposal", blck = shortLog(beaconBlock), blockRoot = shortLog(blockRoot), diff --git a/beacon_chain/validator_client/fork_service.nim b/beacon_chain/validator_client/fork_service.nim index a099a0052..8e34b0882 100644 --- a/beacon_chain/validator_client/fork_service.nim +++ b/beacon_chain/validator_client/fork_service.nim @@ -1,23 +1,70 @@ -import common, api +import std/algorithm import chronicles +import common, api logScope: service = "fork_service" -proc pollForFork(vc: ValidatorClientRef) {.async.} = - let fork = - try: - await vc.getHeadStateFork() - except ValidatorApiError as exc: - error "Unable to retrieve head state's fork", reason = exc.msg - return - except CatchableError as exc: - error "Unexpected error occured while getting fork information", - err_name = exc.name, err_msg = exc.msg - return +proc validateForkSchedule(forks: openarray[Fork]): bool {.raises: [Defect].} = + # Check if `forks` list is linked list. + var current_version = forks[0].current_version + for index, item in forks.pairs(): + if index > 0: + if item.previous_version != current_version: + return false + else: + if item.previous_version != item.current_version: + return false + current_version = item.current_version + true - if vc.fork.isNone() or vc.fork.get() != fork: - vc.fork = some(fork) - notice "Fork update success", fork = fork +proc getCurrentFork(forks: openarray[Fork], + epoch: Epoch): Result[Fork, cstring] {.raises: [Defect].} = + proc cmp(x, y: Fork): int {.closure.} = + if uint64(x.epoch) == uint64(y.epoch): return 0 + if uint64(x.epoch) < uint64(y.epoch): return -1 + return 1 + + let sortedForks = sorted(forks, cmp) + if len(sortedForks) == 0: + return err("Empty fork schedule") + if not(validateForkSchedule(sortedForks)): + return err("Invalid fork schedule") + var res: Fork + for item in sortedForks: + res = item + if item.epoch > epoch: + break + ok(res) + +proc pollForFork(vc: ValidatorClientRef) {.async.} = + let sres = vc.getCurrentSlot() + if sres.isSome(): + let + currentSlot = sres.get() + currentEpoch = currentSlot.epoch() + + let forks = + try: + await vc.getForkSchedule() + except ValidatorApiError as exc: + error "Unable to retrieve fork schedule", reason = exc.msg + return + except CatchableError as exc: + error "Unexpected error occured while getting fork information", + err_name = exc.name, err_msg = exc.msg + return + + let fork = + block: + let res = getCurrentFork(forks, currentEpoch) + if res.isErr(): + error "Invalid fork schedule received", reason = res.error() + return + res.get() + + if vc.fork.isNone() or (vc.fork.get() != fork): + vc.fork = some(fork) + notice "Fork update succeeded", fork = fork proc waitForNextEpoch(service: ForkServiceRef) {.async.} = let vc = service.client diff --git a/beacon_chain/validators/validator_duties.nim b/beacon_chain/validators/validator_duties.nim index 97e30dbc8..2c50cde61 100644 --- a/beacon_chain/validators/validator_duties.nim +++ b/beacon_chain/validators/validator_duties.nim @@ -54,6 +54,10 @@ declarePublicGauge(attached_validator_balance_total, logScope: topics = "beacval" +type + SendResult* = Result[void, cstring] + SendBlockResult* = Result[bool, cstring] + proc findValidator(validators: auto, pubKey: ValidatorPubKey): Option[ValidatorIndex] = let idx = validators.findIt(it.pubKey == pubKey) @@ -170,7 +174,7 @@ proc sendAttestation*( return case ok of ValidationResult.Accept: - node.network.sendAttestation(subnet_id, attestation) + node.network.broadcastAttestation(subnet_id, attestation) beacon_attestations_sent.inc() true else: @@ -179,22 +183,6 @@ proc sendAttestation*( result = $ok false -proc sendAttestation*(node: BeaconNode, attestation: Attestation): Future[bool] = - # For the validator API, which doesn't supply the subnet id. - let attestationBlck = - node.dag.getRef(attestation.data.beacon_block_root) - if attestationBlck.isNil: - debug "Attempt to send attestation without corresponding block" - return - let - epochRef = node.dag.getEpochRef( - attestationBlck, attestation.data.target.epoch) - subnet_id = compute_subnet_for_attestation( - get_committee_count_per_slot(epochRef), attestation.data.slot, - attestation.data.index.CommitteeIndex) - - node.sendAttestation(attestation, subnet_id, checkSignature = true) - proc createAndSendAttestation(node: BeaconNode, fork: Fork, genesis_validators_root: Eth2Digest, @@ -228,9 +216,9 @@ proc createAndSendAttestation(node: BeaconNode, else: ($(wallTime - deadline), toFloatSeconds(wallTime - deadline)) - notice "Attestation sent", attestation = shortLog(attestation), - validator = shortLog(validator), delay = delayStr, - indexInCommittee = indexInCommittee + notice "Attestation sent", + attestation = shortLog(attestation), validator = shortLog(validator), + delay = delayStr beacon_attestation_sent_delay.observe(delaySecs) except CatchableError as exc: @@ -731,3 +719,105 @@ proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} = let finalizedEpochRef = node.dag.getFinalizedEpochRef() discard node.eth1Monitor.trackFinalizedState( finalizedEpochRef.eth1_data, finalizedEpochRef.eth1_deposit_index) + +proc sendAttestation*(node: BeaconNode, + attestation: Attestation): Future[SendResult] {.async.} = + # REST/JSON-RPC API helper procedure. + let attestationBlock = + block: + let res = node.dag.getRef(attestation.data.beacon_block_root) + if isNil(res): + debug "Attempt to send attestation without corresponding block", + attestation = shortLog(attestation) + return SendResult.err( + "Attempt to send attestation without corresponding block") + res + let + epochRef = node.dag.getEpochRef( + attestationBlock, attestation.data.target.epoch) + subnet_id = compute_subnet_for_attestation( + get_committee_count_per_slot(epochRef), attestation.data.slot, + attestation.data.index.CommitteeIndex) + res = await node.sendAttestation(attestation, subnet_id, + checkSignature = true) + if not(res): + return SendResult.err("Attestation failed validation") + return SendResult.ok() + +proc sendAggregateAndProof*(node: BeaconNode, + proof: SignedAggregateAndProof): Future[SendResult] {. + async.} = + # REST/JSON-RPC API helper procedure. + let res = await node.processor.aggregateValidator(proof) + case res + of ValidationResult.Accept: + node.network.broadcastAggregateAndProof(proof) + return SendResult.ok() + else: + notice "Aggregate and proof failed validation", + proof = shortLog(proof.message.aggregate), result = $res + return SendResult.err("Aggregate and proof failed validation") + +proc sendVoluntaryExit*(node: BeaconNode, + exit: SignedVoluntaryExit): SendResult = + # REST/JSON-RPC API helper procedure. + let res = node.processor[].voluntaryExitValidator(exit) + case res + of ValidationResult.Accept: + node.network.broadcastVoluntaryExit(exit) + ok() + else: + notice "Voluntary exit request failed validation", + exit = shortLog(exit.message), result = $res + err("Voluntary exit request failed validation") + +proc sendAttesterSlashing*(node: BeaconNode, + slashing: AttesterSlashing): SendResult = + # REST/JSON-RPC API helper procedure. + let res = node.processor[].attesterSlashingValidator(slashing) + case res + of ValidationResult.Accept: + node.network.broadcastAttesterSlashing(slashing) + ok() + else: + notice "Attester slashing request failed validation", + slashing = shortLog(slashing), result = $res + err("Attester slashing request failed validation") + +proc sendProposerSlashing*(node: BeaconNode, + slashing: ProposerSlashing): SendResult = + # REST/JSON-RPC API helper procedure. + let res = node.processor[].proposerSlashingValidator(slashing) + case res + of ValidationResult.Accept: + node.network.broadcastProposerSlashing(slashing) + else: + notice "Proposer slashing request failed validation", + slashing = shortLog(slashing), result = $res + return SendResult.err("Proposer slashing request failed validation") + +proc sendBeaconBlock*(node: BeaconNode, forked: ForkedSignedBeaconBlock + ): Future[SendBlockResult] {.async.} = + # REST/JSON-RPC API helper procedure. + let head = node.dag.head + if not(node.isSynced(head)): + return SendBlockResult.err("Beacon node is currently syncing") + if head.slot >= forked.slot(): + node.network.broadcastBeaconBlock(forked) + return SendBlockResult.ok(false) + let res = + case forked.kind + of BeaconBlockFork.Phase0: + await node.proposeSignedBlock(head, AttachedValidator(), + forked.phase0Block) + of BeaconBlockFork.Altair: + # TODO altair-transition + # await node.proposeSignedBlock(head, AttachedValidator(), + # forked.altairBlock) + head + if res == head: + # `res == head` means failure, in such case we need to broadcast block + # manually because of the specification. + node.network.broadcastBeaconBlock(forked) + return SendBlockResult.ok(false) + return SendBlockResult.ok(true)