Make `handleValidatorExitCommand` work with `REST API`
This commit is contained in:
parent
d076e1a11b
commit
efbd939108
|
@ -542,16 +542,16 @@ type
|
||||||
name: "validator"
|
name: "validator"
|
||||||
desc: "Validator index or a public key of the exited validator" }: string
|
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* {.
|
exitAtEpoch* {.
|
||||||
name: "epoch"
|
name: "epoch"
|
||||||
desc: "The desired exit epoch" }: Option[uint64]
|
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:
|
of BNStartUpCmd.record:
|
||||||
case recordCmd* {.command.}: RecordCmd
|
case recordCmd* {.command.}: RecordCmd
|
||||||
of RecordCmd.create:
|
of RecordCmd.create:
|
||||||
|
|
|
@ -10,7 +10,6 @@ import
|
||||||
# Standard library
|
# Standard library
|
||||||
std/[math, os, osproc, random, sequtils, strformat, strutils,
|
std/[math, os, osproc, random, sequtils, strformat, strutils,
|
||||||
tables, times, terminal],
|
tables, times, terminal],
|
||||||
|
|
||||||
# Nimble packages
|
# Nimble packages
|
||||||
stew/io2,
|
stew/io2,
|
||||||
spec/eth2_apis/eth2_rest_serialization,
|
spec/eth2_apis/eth2_rest_serialization,
|
||||||
|
@ -43,7 +42,8 @@ import
|
||||||
./consensus_object_pools/[
|
./consensus_object_pools/[
|
||||||
blockchain_dag, block_quarantine, block_clearance, attestation_pool,
|
blockchain_dag, block_quarantine, block_clearance, attestation_pool,
|
||||||
sync_committee_msg_pool, exit_pool, spec_cache],
|
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
|
from eth/common/eth_types import BlockHashOrNumber
|
||||||
|
|
||||||
|
@ -1520,41 +1520,73 @@ proc initStatusBar(node: BeaconNode) {.raises: [Defect, ValueError].} =
|
||||||
asyncSpawn statusBarUpdatesPollingLoop()
|
asyncSpawn statusBarUpdatesPollingLoop()
|
||||||
|
|
||||||
proc handleValidatorExitCommand(config: BeaconNodeConf) {.async.} =
|
proc handleValidatorExitCommand(config: BeaconNodeConf) {.async.} =
|
||||||
let port = try:
|
let
|
||||||
let value = parseInt(config.rpcUrlForExit.port)
|
client = RestClientRef.new(
|
||||||
if value < Port.low.int or value > Port.high.int:
|
try:
|
||||||
raise newException(ValueError,
|
resolveTAddress($config.restUrlForExit.hostname &
|
||||||
"The port number must be between " & $Port.low & " and " & $Port.high)
|
":" &
|
||||||
Port value
|
$config.restUrlForExit.port)[0]
|
||||||
except CatchableError as err:
|
except CatchableError as err:
|
||||||
fatal "Invalid port number", 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
|
quit 1
|
||||||
|
|
||||||
let rpcClient = newRpcHttpClient()
|
let restValidator = try:
|
||||||
|
let response = await client.getStateValidatorPlain(stateIdHead,
|
||||||
try:
|
validatorIdent.get())
|
||||||
await connect(rpcClient, config.rpcUrlForExit.hostname, port,
|
if response.status == 200:
|
||||||
secure = config.rpcUrlForExit.scheme in ["https", "wss"])
|
let validator = decodeBytes(GetStateValidatorResponse,
|
||||||
except CatchableError as err:
|
response.data,
|
||||||
fatal "Failed to connect to the beacon node RPC service", err = err.msg
|
response.contentType)
|
||||||
quit 1
|
if validator.isErr():
|
||||||
|
raise newException(RestError, $validator.error)
|
||||||
let (validator, validatorIdx, _, _) = try:
|
validator.get().data
|
||||||
await rpcClient.get_v1_beacon_states_stateId_validators_validatorId(
|
else:
|
||||||
"head", config.exitedValidator)
|
raiseGenericError(response)
|
||||||
except CatchableError as err:
|
except CatchableError as err:
|
||||||
fatal "Failed to obtain information for validator", err = err.msg
|
fatal "Failed to obtain information for validator", err = err.msg
|
||||||
quit 1
|
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:
|
let exitAtEpoch = if config.exitAtEpoch.isSome:
|
||||||
Epoch config.exitAtEpoch.get
|
Epoch config.exitAtEpoch.get
|
||||||
else:
|
else:
|
||||||
let headSlot = try:
|
let
|
||||||
await rpcClient.getBeaconHead()
|
genesisTime = genesis.genesis_time
|
||||||
except CatchableError as err:
|
beaconClock = BeaconClock.init(genesisTime)
|
||||||
fatal "Failed to obtain the current head slot", err = err.msg
|
time = getTime()
|
||||||
quit 1
|
slot = beaconClock.toSlot(time).slot
|
||||||
headSlot.epoch
|
epoch = slot.uint64 div 32
|
||||||
|
Epoch epoch
|
||||||
|
|
||||||
let
|
let
|
||||||
validatorsDir = config.validatorsDir
|
validatorsDir = config.validatorsDir
|
||||||
|
@ -1578,18 +1610,23 @@ proc handleValidatorExitCommand(config: BeaconNodeConf) {.async.} =
|
||||||
quit 1
|
quit 1
|
||||||
|
|
||||||
let fork = try:
|
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:
|
except CatchableError as err:
|
||||||
fatal "Failed to obtain the fork id of the head state", err = err.msg
|
fatal "Failed to obtain the fork id of the head state",
|
||||||
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",
|
|
||||||
err = err.msg
|
err = err.msg
|
||||||
quit 1
|
quit 1
|
||||||
|
|
||||||
|
let genesisValidatorsRoot = genesis.genesis_validators_root
|
||||||
|
|
||||||
var signedExit = SignedVoluntaryExit(
|
var signedExit = SignedVoluntaryExit(
|
||||||
message: VoluntaryExit(
|
message: VoluntaryExit(
|
||||||
epoch: exitAtEpoch,
|
epoch: exitAtEpoch,
|
||||||
|
@ -1648,14 +1685,32 @@ proc handleValidatorExitCommand(config: BeaconNodeConf) {.async.} =
|
||||||
if choice == "q":
|
if choice == "q":
|
||||||
quit 0
|
quit 0
|
||||||
elif choice == confirmation:
|
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:
|
if success:
|
||||||
echo "Successfully published voluntary exit for validator " &
|
echo "Successfully published voluntary exit for validator " &
|
||||||
$validatorIdx & "(" & validatorKeyAsStr[0..9] & ")."
|
$validatorIdx & "(" & validatorKeyAsStr[0..9] & ")."
|
||||||
quit 0
|
quit 0
|
||||||
else:
|
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
|
quit 1
|
||||||
|
|
||||||
except CatchableError as err:
|
except CatchableError as err:
|
||||||
fatal "Failed to send the signed exit message to the beacon node RPC",
|
fatal "Failed to send the signed exit message to the beacon node RPC",
|
||||||
err = err.msg
|
err = err.msg
|
||||||
|
|
|
@ -72,8 +72,10 @@ type
|
||||||
GetBlockV2Response |
|
GetBlockV2Response |
|
||||||
GetKeystoresResponse |
|
GetKeystoresResponse |
|
||||||
GetStateV2Response |
|
GetStateV2Response |
|
||||||
|
GetStateForkResponse |
|
||||||
ProduceBlockResponseV2 |
|
ProduceBlockResponseV2 |
|
||||||
RestAttestationError |
|
RestAttestationError |
|
||||||
|
RestValidator |
|
||||||
RestGenericError |
|
RestGenericError |
|
||||||
Web3SignerErrorResponse |
|
Web3SignerErrorResponse |
|
||||||
Web3SignerKeysResponse |
|
Web3SignerKeysResponse |
|
||||||
|
|
|
@ -24,6 +24,11 @@ proc getGenesis*(): RestResponse[GetGenesisResponse] {.
|
||||||
meth: MethodGet.}
|
meth: MethodGet.}
|
||||||
## https://ethereum.github.io/beacon-APIs/#/Beacon/getGenesis
|
## 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] {.
|
proc getStateRoot*(state_id: StateIdent): RestResponse[GetStateRootResponse] {.
|
||||||
rest, endpoint: "/eth/v1/beacon/states/{state_id}/root",
|
rest, endpoint: "/eth/v1/beacon/states/{state_id}/root",
|
||||||
meth: MethodGet.}
|
meth: MethodGet.}
|
||||||
|
@ -34,6 +39,11 @@ proc getStateFork*(state_id: StateIdent): RestResponse[GetStateForkResponse] {.
|
||||||
meth: MethodGet.}
|
meth: MethodGet.}
|
||||||
## https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFork
|
## 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
|
proc getStateFinalityCheckpoints*(state_id: StateIdent
|
||||||
): RestResponse[GetStateFinalityCheckpointsResponse] {.
|
): RestResponse[GetStateFinalityCheckpointsResponse] {.
|
||||||
rest, endpoint: "/eth/v1/beacon/states/{state_id}/finality_checkpoints",
|
rest, endpoint: "/eth/v1/beacon/states/{state_id}/finality_checkpoints",
|
||||||
|
@ -55,6 +65,15 @@ proc getStateValidator*(state_id: StateIdent,
|
||||||
meth: MethodGet.}
|
meth: MethodGet.}
|
||||||
## https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidator
|
## 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
|
proc getStateValidatorBalances*(state_id: StateIdent
|
||||||
): RestResponse[GetStateValidatorBalancesResponse] {.
|
): RestResponse[GetStateValidatorBalancesResponse] {.
|
||||||
rest, endpoint: "/eth/v1/beacon/states/{state_id}/validator_balances",
|
rest, endpoint: "/eth/v1/beacon/states/{state_id}/validator_balances",
|
||||||
|
@ -100,7 +119,7 @@ proc getBlockPlain*(block_id: BlockIdent): RestPlainResponse {.
|
||||||
meth: MethodGet.}
|
meth: MethodGet.}
|
||||||
## https://ethereum.github.io/beacon-APIs/#/Beacon/getBlock
|
## https://ethereum.github.io/beacon-APIs/#/Beacon/getBlock
|
||||||
|
|
||||||
proc raiseGenericError(resp: RestPlainResponse)
|
proc raiseGenericError*(resp: RestPlainResponse)
|
||||||
{.noreturn, raises: [RestError, Defect].} =
|
{.noreturn, raises: [RestError, Defect].} =
|
||||||
let error =
|
let error =
|
||||||
block:
|
block:
|
||||||
|
|
|
@ -500,6 +500,7 @@ type
|
||||||
root*: Eth2Digest
|
root*: Eth2Digest
|
||||||
|
|
||||||
# Types based on the OAPI yaml file - used in responses to requests
|
# Types based on the OAPI yaml file - used in responses to requests
|
||||||
|
GetBeaconHeadResponse* = DataEnclosedObject[Slot]
|
||||||
GetAggregatedAttestationResponse* = DataEnclosedObject[Attestation]
|
GetAggregatedAttestationResponse* = DataEnclosedObject[Attestation]
|
||||||
GetAttesterDutiesResponse* = DataRootEnclosedObject[seq[RestAttesterDuty]]
|
GetAttesterDutiesResponse* = DataRootEnclosedObject[seq[RestAttesterDuty]]
|
||||||
GetBlockAttestationsResponse* = DataEnclosedObject[seq[Attestation]]
|
GetBlockAttestationsResponse* = DataEnclosedObject[seq[Attestation]]
|
||||||
|
|
|
@ -333,37 +333,37 @@ proc check_voluntary_exit*(
|
||||||
|
|
||||||
# Not in spec. Check that validator_index is in range
|
# Not in spec. Check that validator_index is in range
|
||||||
if voluntary_exit.validator_index >= state.validators.lenu64:
|
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]
|
let validator = unsafeAddr state.validators.asSeq()[voluntary_exit.validator_index]
|
||||||
|
|
||||||
# Verify the validator is active
|
# Verify the validator is active
|
||||||
if not is_active_validator(validator[], get_current_epoch(state)):
|
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
|
# Verify exit has not been initiated
|
||||||
if validator[].exit_epoch != FAR_FUTURE_EPOCH:
|
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
|
# Exits must specify an epoch when they become valid; they are not valid
|
||||||
# before then
|
# before then
|
||||||
if not (get_current_epoch(state) >= voluntary_exit.epoch):
|
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
|
# Verify the validator has been active long enough
|
||||||
if not (get_current_epoch(state) >= validator[].activation_epoch +
|
if not (get_current_epoch(state) >= validator[].activation_epoch +
|
||||||
cfg.SHARD_COMMITTEE_PERIOD):
|
cfg.SHARD_COMMITTEE_PERIOD):
|
||||||
return err("Exit: not in validator set long enough")
|
return err("Not in validator set long enough")
|
||||||
|
|
||||||
# Verify signature
|
# Verify signature
|
||||||
if skipBlsValidation notin flags:
|
if skipBlsValidation notin flags:
|
||||||
if not verify_voluntary_exit_signature(
|
if not verify_voluntary_exit_signature(
|
||||||
state.fork, state.genesis_validators_root, voluntary_exit,
|
state.fork, state.genesis_validators_root, voluntary_exit,
|
||||||
validator[].pubkey, signed_voluntary_exit.signature):
|
validator[].pubkey, signed_voluntary_exit.signature):
|
||||||
return err("Exit: invalid signature")
|
return err("Invalid signature")
|
||||||
|
|
||||||
# Initiate exit
|
# Initiate exit
|
||||||
debug "Exit: checking voluntary exit (validator_leaving)",
|
debug "Checking voluntary exit (validator_leaving)",
|
||||||
index = voluntary_exit.validator_index,
|
index = voluntary_exit.validator_index,
|
||||||
num_validators = state.validators.len,
|
num_validators = state.validators.len,
|
||||||
epoch = voluntary_exit.epoch,
|
epoch = voluntary_exit.epoch,
|
||||||
|
|
Loading…
Reference in New Issue