Make `handleValidatorExitCommand` work with `REST API`

This commit is contained in:
Emil 2022-01-25 12:07:15 +02:00 committed by zah
parent d076e1a11b
commit efbd939108
6 changed files with 129 additions and 52 deletions

View File

@ -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:

View File

@ -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

View File

@ -72,8 +72,10 @@ type
GetBlockV2Response |
GetKeystoresResponse |
GetStateV2Response |
GetStateForkResponse |
ProduceBlockResponseV2 |
RestAttestationError |
RestValidator |
RestGenericError |
Web3SignerErrorResponse |
Web3SignerKeysResponse |

View File

@ -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:

View File

@ -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]]

View File

@ -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,