Make `handleValidatorExitCommand` work with `REST API`
This commit is contained in:
parent
d076e1a11b
commit
efbd939108
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
quit 1
|
||||
|
||||
let rpcClient = newRpcHttpClient()
|
||||
|
||||
let
|
||||
client = RestClientRef.new(
|
||||
try:
|
||||
await connect(rpcClient, config.rpcUrlForExit.hostname, port,
|
||||
secure = config.rpcUrlForExit.scheme in ["https", "wss"])
|
||||
resolveTAddress($config.restUrlForExit.hostname &
|
||||
":" &
|
||||
$config.restUrlForExit.port)[0]
|
||||
except CatchableError as err:
|
||||
fatal "Failed to connect to the beacon node RPC service", err = err.msg
|
||||
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 (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
|
||||
|
|
|
@ -72,8 +72,10 @@ type
|
|||
GetBlockV2Response |
|
||||
GetKeystoresResponse |
|
||||
GetStateV2Response |
|
||||
GetStateForkResponse |
|
||||
ProduceBlockResponseV2 |
|
||||
RestAttestationError |
|
||||
RestValidator |
|
||||
RestGenericError |
|
||||
Web3SignerErrorResponse |
|
||||
Web3SignerKeysResponse |
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue