diff --git a/beacon_chain/conf.nim b/beacon_chain/conf.nim index 8c1cc75b4..58c0825e1 100644 --- a/beacon_chain/conf.nim +++ b/beacon_chain/conf.nim @@ -542,16 +542,16 @@ type name: "validator" desc: "Validator index or a public key of the exited validator" }: string - rpcUrlForExit* {. - desc: "URL of the beacon node JSON-RPC service" - defaultValue: parseUri("http://localhost:" & $defaultEth2RpcPort) - defaultValueDesc: "http://localhost:9190" - name: "rpc-url" }: Uri - exitAtEpoch* {. name: "epoch" desc: "The desired exit epoch" }: Option[uint64] + restUrlForExit* {. + desc: "URL of the beacon node REST service" + defaultValue: parseUri("http://localhost" & $DefaultEth2RestPort) + defaultValueDesc: "http://localhost:5052" + name: "rest-url" }: Uri + of BNStartUpCmd.record: case recordCmd* {.command.}: RecordCmd of RecordCmd.create: diff --git a/beacon_chain/nimbus_beacon_node.nim b/beacon_chain/nimbus_beacon_node.nim index 9c9127e48..fdde862b6 100644 --- a/beacon_chain/nimbus_beacon_node.nim +++ b/beacon_chain/nimbus_beacon_node.nim @@ -10,7 +10,6 @@ import # Standard library std/[math, os, osproc, random, sequtils, strformat, strutils, tables, times, terminal], - # Nimble packages stew/io2, spec/eth2_apis/eth2_rest_serialization, @@ -43,7 +42,8 @@ import ./consensus_object_pools/[ blockchain_dag, block_quarantine, block_clearance, attestation_pool, sync_committee_msg_pool, exit_pool, spec_cache], - ./eth1/eth1_monitor + ./eth1/eth1_monitor, + ./spec/eth2_apis/rest_beacon_calls from eth/common/eth_types import BlockHashOrNumber @@ -1520,41 +1520,73 @@ proc initStatusBar(node: BeaconNode) {.raises: [Defect, ValueError].} = asyncSpawn statusBarUpdatesPollingLoop() proc handleValidatorExitCommand(config: BeaconNodeConf) {.async.} = - let port = try: - let value = parseInt(config.rpcUrlForExit.port) - if value < Port.low.int or value > Port.high.int: - raise newException(ValueError, - "The port number must be between " & $Port.low & " and " & $Port.high) - Port value - except CatchableError as err: - fatal "Invalid port number", err = err.msg + let + client = RestClientRef.new( + try: + resolveTAddress($config.restUrlForExit.hostname & + ":" & + $config.restUrlForExit.port)[0] + except CatchableError as err: + fatal "Failed to resolve address", err = err.msg + quit 1) + + stateIdHead = StateIdent(kind: StateQueryKind.Named, + value: StateIdentType.Head) + blockIdentHead = BlockIdent(kind: BlockQueryKind.Named, + value: BlockIdentType.Head) + validatorIdent = ValidatorIdent.decodeString(config.exitedValidator) + + if validatorIdent.isErr(): + fatal "Incorrect validator index or key specified", + err = $validatorIdent.error() quit 1 - let rpcClient = newRpcHttpClient() - - try: - await connect(rpcClient, config.rpcUrlForExit.hostname, port, - secure = config.rpcUrlForExit.scheme in ["https", "wss"]) - except CatchableError as err: - fatal "Failed to connect to the beacon node RPC service", err = err.msg - quit 1 - - let (validator, validatorIdx, _, _) = try: - await rpcClient.get_v1_beacon_states_stateId_validators_validatorId( - "head", config.exitedValidator) + let restValidator = try: + let response = await client.getStateValidatorPlain(stateIdHead, + validatorIdent.get()) + if response.status == 200: + let validator = decodeBytes(GetStateValidatorResponse, + response.data, + response.contentType) + if validator.isErr(): + raise newException(RestError, $validator.error) + validator.get().data + else: + raiseGenericError(response) except CatchableError as err: fatal "Failed to obtain information for validator", err = err.msg quit 1 + let + validator = restValidator.validator + validatorIdx = restValidator.index.uint64 + + let genesis = try: + let response = await client.getGenesisPlain() + if response.status == 200: + let genesis = decodeBytes(GetGenesisResponse, + response.data, + response.contentType) + if genesis.isErr(): + raise newException(RestError, $genesis.error) + genesis.get().data + else: + raiseGenericError(response) + except CatchableError as err: + fatal "Failed to obtain the genesis validators root of the network", + err = err.msg + quit 1 + let exitAtEpoch = if config.exitAtEpoch.isSome: Epoch config.exitAtEpoch.get else: - let headSlot = try: - await rpcClient.getBeaconHead() - except CatchableError as err: - fatal "Failed to obtain the current head slot", err = err.msg - quit 1 - headSlot.epoch + let + genesisTime = genesis.genesis_time + beaconClock = BeaconClock.init(genesisTime) + time = getTime() + slot = beaconClock.toSlot(time).slot + epoch = slot.uint64 div 32 + Epoch epoch let validatorsDir = config.validatorsDir @@ -1578,18 +1610,23 @@ proc handleValidatorExitCommand(config: BeaconNodeConf) {.async.} = quit 1 let fork = try: - await rpcClient.get_v1_beacon_states_fork("head") + let response = await client.getStateForkPlain(stateIdHead) + if response.status == 200: + let fork = decodeBytes(GetStateForkResponse, + response.data, + response.contentType) + if fork.isErr(): + raise newException(RestError, $fork.error) + fork.get().data + else: + raiseGenericError(response) except CatchableError as err: - fatal "Failed to obtain the fork id of the head state", err = err.msg - quit 1 - - let genesisValidatorsRoot = try: - (await rpcClient.get_v1_beacon_genesis()).genesis_validators_root - except CatchableError as err: - fatal "Failed to obtain the genesis validators root of the network", + fatal "Failed to obtain the fork id of the head state", err = err.msg quit 1 + let genesisValidatorsRoot = genesis.genesis_validators_root + var signedExit = SignedVoluntaryExit( message: VoluntaryExit( epoch: exitAtEpoch, @@ -1648,14 +1685,32 @@ proc handleValidatorExitCommand(config: BeaconNodeConf) {.async.} = if choice == "q": quit 0 elif choice == confirmation: - let success = await rpcClient.post_v1_beacon_pool_voluntary_exits(signedExit) + let + response = await client.submitPoolVoluntaryExit(signedExit) + success = response.status == 200 if success: echo "Successfully published voluntary exit for validator " & $validatorIdx & "(" & validatorKeyAsStr[0..9] & ")." quit 0 else: - echo "The voluntary exit was not submitted successfully. Please try again." + let responseError = try: + Json.decode(response.data, RestGenericError) + except CatchableError as err: + fatal "Failed to decode invalid error server response on `submitPoolVoluntaryExit` request", + err = err.msg + quit 1 + + let + responseMessage = responseError.message + responseStacktraces = responseError.stacktraces + + echo "The voluntary exit was not submitted successfully." + echo responseMessage & ":" + for el in responseStacktraces.get(): + echo el + echoP "Please try again." quit 1 + except CatchableError as err: fatal "Failed to send the signed exit message to the beacon node RPC", err = err.msg diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index 7146f0d9f..8f31747f4 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -72,8 +72,10 @@ type GetBlockV2Response | GetKeystoresResponse | GetStateV2Response | + GetStateForkResponse | ProduceBlockResponseV2 | RestAttestationError | + RestValidator | RestGenericError | Web3SignerErrorResponse | Web3SignerKeysResponse | diff --git a/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim b/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim index c30c01551..6a89cca91 100644 --- a/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_beacon_calls.nim @@ -24,6 +24,11 @@ proc getGenesis*(): RestResponse[GetGenesisResponse] {. meth: MethodGet.} ## https://ethereum.github.io/beacon-APIs/#/Beacon/getGenesis +proc getGenesisPlain*(): RestPlainResponse {. + rest, endpoint: "/eth/v1/beacon/genesis", + meth: MethodGet.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/getGenesis + proc getStateRoot*(state_id: StateIdent): RestResponse[GetStateRootResponse] {. rest, endpoint: "/eth/v1/beacon/states/{state_id}/root", meth: MethodGet.} @@ -34,6 +39,11 @@ proc getStateFork*(state_id: StateIdent): RestResponse[GetStateForkResponse] {. meth: MethodGet.} ## https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFork +proc getStateForkPlain*(state_id: StateIdent): RestPlainResponse {. + rest, endpoint: "/eth/v1/beacon/states/{state_id}/fork", + meth: MethodGet.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFork + proc getStateFinalityCheckpoints*(state_id: StateIdent ): RestResponse[GetStateFinalityCheckpointsResponse] {. rest, endpoint: "/eth/v1/beacon/states/{state_id}/finality_checkpoints", @@ -55,6 +65,15 @@ proc getStateValidator*(state_id: StateIdent, meth: MethodGet.} ## https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidator +proc getStateValidatorPlain*(state_id: StateIdent, + validator_id: ValidatorIdent + ): RestPlainResponse {. + rest, + endpoint: "/eth/v1/beacon/states/{state_id}/validators/{validator_id}", + meth: MethodGet.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidator + ## + proc getStateValidatorBalances*(state_id: StateIdent ): RestResponse[GetStateValidatorBalancesResponse] {. rest, endpoint: "/eth/v1/beacon/states/{state_id}/validator_balances", @@ -100,7 +119,7 @@ proc getBlockPlain*(block_id: BlockIdent): RestPlainResponse {. meth: MethodGet.} ## https://ethereum.github.io/beacon-APIs/#/Beacon/getBlock -proc raiseGenericError(resp: RestPlainResponse) +proc raiseGenericError*(resp: RestPlainResponse) {.noreturn, raises: [RestError, Defect].} = let error = block: diff --git a/beacon_chain/spec/eth2_apis/rest_types.nim b/beacon_chain/spec/eth2_apis/rest_types.nim index bb32d6e47..3d6274aad 100644 --- a/beacon_chain/spec/eth2_apis/rest_types.nim +++ b/beacon_chain/spec/eth2_apis/rest_types.nim @@ -500,6 +500,7 @@ type root*: Eth2Digest # Types based on the OAPI yaml file - used in responses to requests + GetBeaconHeadResponse* = DataEnclosedObject[Slot] GetAggregatedAttestationResponse* = DataEnclosedObject[Attestation] GetAttesterDutiesResponse* = DataRootEnclosedObject[seq[RestAttesterDuty]] GetBlockAttestationsResponse* = DataEnclosedObject[seq[Attestation]] diff --git a/beacon_chain/spec/state_transition_block.nim b/beacon_chain/spec/state_transition_block.nim index 45ca9f0c0..2554319d1 100644 --- a/beacon_chain/spec/state_transition_block.nim +++ b/beacon_chain/spec/state_transition_block.nim @@ -333,37 +333,37 @@ proc check_voluntary_exit*( # Not in spec. Check that validator_index is in range if voluntary_exit.validator_index >= state.validators.lenu64: - return err("Exit: invalid validator index") + return err("Invalid validator index") let validator = unsafeAddr state.validators.asSeq()[voluntary_exit.validator_index] # Verify the validator is active if not is_active_validator(validator[], get_current_epoch(state)): - return err("Exit: validator not active") + return err("Validator not active") # Verify exit has not been initiated if validator[].exit_epoch != FAR_FUTURE_EPOCH: - return err("Exit: validator has exited") + return err("Validator has exited") # Exits must specify an epoch when they become valid; they are not valid # before then if not (get_current_epoch(state) >= voluntary_exit.epoch): - return err("Exit: exit epoch not passed") + return err("Exit epoch not passed") # Verify the validator has been active long enough if not (get_current_epoch(state) >= validator[].activation_epoch + cfg.SHARD_COMMITTEE_PERIOD): - return err("Exit: not in validator set long enough") + return err("Not in validator set long enough") # Verify signature if skipBlsValidation notin flags: if not verify_voluntary_exit_signature( state.fork, state.genesis_validators_root, voluntary_exit, validator[].pubkey, signed_voluntary_exit.signature): - return err("Exit: invalid signature") + return err("Invalid signature") # Initiate exit - debug "Exit: checking voluntary exit (validator_leaving)", + debug "Checking voluntary exit (validator_leaving)", index = voluntary_exit.validator_index, num_validators = state.validators.len, epoch = voluntary_exit.epoch,