VC+BN: Validator voluntary exits through the Keymanager API (#5020)
* Initial commit. * Address review comments.
This commit is contained in:
parent
c0e5c26da1
commit
927180f36f
|
@ -666,6 +666,12 @@ proc init*(T: type BeaconNode,
|
|||
withState(dag.headState):
|
||||
getValidator(forkyState().data.validators.asSeq(), pubkey)
|
||||
|
||||
proc getForkForEpoch(epoch: Epoch): Opt[Fork] =
|
||||
Opt.some(dag.forkAtEpoch(epoch))
|
||||
|
||||
proc getGenesisRoot(): Eth2Digest =
|
||||
getStateField(dag.headState, genesis_validators_root)
|
||||
|
||||
let
|
||||
slashingProtectionDB =
|
||||
SlashingProtectionDB.init(
|
||||
|
@ -685,7 +691,9 @@ proc init*(T: type BeaconNode,
|
|||
config.defaultFeeRecipient,
|
||||
config.suggestedGasLimit,
|
||||
getValidatorAndIdx,
|
||||
getBeaconTime)
|
||||
getBeaconTime,
|
||||
getForkForEpoch,
|
||||
getGenesisRoot)
|
||||
else: nil
|
||||
|
||||
stateTtlCache =
|
||||
|
|
|
@ -310,6 +310,15 @@ proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {.async.} =
|
|||
let
|
||||
keymanagerInitResult = initKeymanagerServer(vc.config, nil)
|
||||
|
||||
proc getForkForEpoch(epoch: Epoch): Opt[Fork] =
|
||||
if len(vc.forks) > 0:
|
||||
Opt.some(vc.forkAtEpoch(epoch))
|
||||
else:
|
||||
Opt.none(Fork)
|
||||
|
||||
proc getGenesisRoot(): Eth2Digest =
|
||||
vc.beaconGenesis.genesis_validators_root
|
||||
|
||||
try:
|
||||
vc.fallbackService = await FallbackServiceRef.init(vc)
|
||||
vc.forkService = await ForkServiceRef.init(vc)
|
||||
|
@ -329,7 +338,10 @@ proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {.async.} =
|
|||
vc.config.defaultFeeRecipient,
|
||||
vc.config.suggestedGasLimit,
|
||||
nil,
|
||||
vc.beaconClock.getBeaconTimeFn)
|
||||
vc.beaconClock.getBeaconTimeFn,
|
||||
getForkForEpoch,
|
||||
getGenesisRoot
|
||||
)
|
||||
|
||||
except CatchableError as exc:
|
||||
warn "Unexpected error encountered while initializing",
|
||||
|
|
|
@ -115,6 +115,8 @@ const
|
|||
"Invalid subscription request object(s)"
|
||||
ValidatorNotFoundError* =
|
||||
"Could not find validator"
|
||||
ValidatorIndexMissingError* =
|
||||
"Validator missing index value"
|
||||
ValidatorStatusNotFoundError* =
|
||||
"Could not obtain validator's status"
|
||||
TooHighValidatorIndexValueError* =
|
||||
|
@ -234,3 +236,5 @@ const
|
|||
"The given merkle proof is invalid"
|
||||
InvalidMerkleProofIndexError* =
|
||||
"The given merkle proof index is invalid"
|
||||
FailedToObtainForkError* =
|
||||
"Failed to obtain fork information"
|
||||
|
|
|
@ -23,8 +23,8 @@ func validateKeymanagerApiQueries*(key: string, value: string): int =
|
|||
# There are no queries to validate
|
||||
return 0
|
||||
|
||||
proc listLocalValidators*(validatorPool: ValidatorPool): seq[KeystoreInfo]
|
||||
{.raises: [Defect].} =
|
||||
proc listLocalValidators*(validatorPool: ValidatorPool): seq[KeystoreInfo] {.
|
||||
raises: [Defect].} =
|
||||
var validators: seq[KeystoreInfo]
|
||||
for item in validatorPool:
|
||||
if item.kind == ValidatorKind.Local:
|
||||
|
@ -35,8 +35,9 @@ proc listLocalValidators*(validatorPool: ValidatorPool): seq[KeystoreInfo]
|
|||
)
|
||||
validators
|
||||
|
||||
proc listRemoteValidators*(validatorPool: ValidatorPool): seq[RemoteKeystoreInfo]
|
||||
{.raises: [Defect].} =
|
||||
proc listRemoteValidators*(
|
||||
validatorPool: ValidatorPool): seq[RemoteKeystoreInfo] {.
|
||||
raises: [Defect].} =
|
||||
var validators: seq[RemoteKeystoreInfo]
|
||||
for item in validatorPool:
|
||||
if item.kind == ValidatorKind.Remote and item.data.remotes.len == 1:
|
||||
|
@ -46,8 +47,9 @@ proc listRemoteValidators*(validatorPool: ValidatorPool): seq[RemoteKeystoreInfo
|
|||
)
|
||||
validators
|
||||
|
||||
proc listRemoteDistributedValidators*(validatorPool: ValidatorPool): seq[DistributedKeystoreInfo]
|
||||
{.raises: [Defect].} =
|
||||
proc listRemoteDistributedValidators*(
|
||||
validatorPool: ValidatorPool): seq[DistributedKeystoreInfo] {.
|
||||
raises: [Defect].} =
|
||||
var validators: seq[DistributedKeystoreInfo]
|
||||
for item in validatorPool:
|
||||
if item.kind == ValidatorKind.Remote and item.data.remotes.len > 1:
|
||||
|
@ -75,8 +77,9 @@ proc keymanagerApiError(status: HttpCode, msg: string): RestApiResponse =
|
|||
default
|
||||
RestApiResponse.error(status, data, "application/json")
|
||||
|
||||
proc checkAuthorization*(request: HttpRequestRef,
|
||||
host: KeymanagerHost): Result[void, AuthorizationError] =
|
||||
proc checkAuthorization*(
|
||||
request: HttpRequestRef,
|
||||
host: KeymanagerHost): Result[void, AuthorizationError] =
|
||||
let authorizations = request.headers.getList("authorization")
|
||||
if authorizations.len > 0:
|
||||
for authHeader in authorizations:
|
||||
|
@ -522,3 +525,54 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) =
|
|||
response.data.add handleRemoveValidatorReq(host, key)
|
||||
|
||||
return RestApiResponse.jsonResponsePlain(response)
|
||||
|
||||
# https://ethereum.github.io/keymanager-APIs/?urls.primaryName=dev#/Voluntary%20Exit/signVoluntaryExit
|
||||
router.api(MethodPost, "/eth/v1/validator/{pubkey}/voluntary_exit") do (
|
||||
pubkey: ValidatorPubKey, epoch: Option[Epoch],
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
|
||||
let authStatus = checkAuthorization(request, host)
|
||||
if authStatus.isErr():
|
||||
return authErrorResponse(authStatus.error)
|
||||
|
||||
let
|
||||
qpubkey = pubkey.valueOr:
|
||||
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
|
||||
qepoch =
|
||||
if epoch.isSome():
|
||||
let res = epoch.get()
|
||||
if res.isErr():
|
||||
return keymanagerApiError(Http400, InvalidEpochValueError)
|
||||
res.get()
|
||||
else:
|
||||
host.getBeaconTimeFn().slotOrZero().epoch()
|
||||
validator =
|
||||
block:
|
||||
let res = host.validatorPool[].getValidator(qpubkey).valueOr:
|
||||
return keymanagerApiError(Http404, ValidatorNotFoundError)
|
||||
if res.index.isNone():
|
||||
return keymanagerApiError(Http404, ValidatorIndexMissingError)
|
||||
res
|
||||
voluntaryExit =
|
||||
VoluntaryExit(epoch: qepoch,
|
||||
validator_index: uint64(validator.index.get()))
|
||||
fork = host.getForkFn(qepoch).valueOr:
|
||||
return keymanagerApiError(Http500, FailedToObtainForkError)
|
||||
signature =
|
||||
try:
|
||||
let res = await validator.getValidatorExitSignature(
|
||||
fork, host.getGenesisFn(), voluntaryExit)
|
||||
if res.isErr():
|
||||
return keymanagerApiError(Http500, res.error())
|
||||
res.get()
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
error "An unexpected error occurred while signing validator exit",
|
||||
err_name = exc.name, err_msg = exc.msg
|
||||
return keymanagerApiError(Http500, $exc.msg)
|
||||
response = SignedVoluntaryExit(
|
||||
message: voluntaryExit,
|
||||
signature: signature
|
||||
)
|
||||
return RestApiResponse.jsonResponse(response)
|
||||
|
|
|
@ -69,6 +69,11 @@ type
|
|||
proc (pubkey: ValidatorPubKey): Opt[ValidatorAndIndex]
|
||||
{.raises: [Defect], gcsafe.}
|
||||
|
||||
GetForkFn* =
|
||||
proc (epoch: Epoch): Opt[Fork] {.raises: [Defect], gcsafe.}
|
||||
GetGenesisFn* =
|
||||
proc (): Eth2Digest {.raises: [Defect], gcsafe.}
|
||||
|
||||
KeymanagerHost* = object
|
||||
validatorPool*: ref ValidatorPool
|
||||
rng*: ref HmacDrbgContext
|
||||
|
@ -79,6 +84,8 @@ type
|
|||
defaultGasLimit*: uint64
|
||||
getValidatorAndIdxFn*: ValidatorPubKeyToDataFn
|
||||
getBeaconTimeFn*: GetBeaconTimeFn
|
||||
getForkFn*: GetForkFn
|
||||
getGenesisFn*: GetGenesisFn
|
||||
|
||||
MultipleKeystoresDecryptor* = object
|
||||
previouslyUsedPassword*: string
|
||||
|
@ -104,7 +111,9 @@ func init*(T: type KeymanagerHost,
|
|||
defaultFeeRecipient: Opt[Eth1Address],
|
||||
defaultGasLimit: uint64,
|
||||
getValidatorAndIdxFn: ValidatorPubKeyToDataFn,
|
||||
getBeaconTimeFn: GetBeaconTimeFn): T =
|
||||
getBeaconTimeFn: GetBeaconTimeFn,
|
||||
getForkFn: GetForkFn,
|
||||
getGenesisFn: GetGenesisFn): T =
|
||||
T(validatorPool: validatorPool,
|
||||
rng: rng,
|
||||
keymanagerToken: keymanagerToken,
|
||||
|
@ -113,7 +122,9 @@ func init*(T: type KeymanagerHost,
|
|||
defaultFeeRecipient: defaultFeeRecipient,
|
||||
defaultGasLimit: defaultGasLimit,
|
||||
getValidatorAndIdxFn: getValidatorAndIdxFn,
|
||||
getBeaconTimeFn: getBeaconTimeFn)
|
||||
getBeaconTimeFn: getBeaconTimeFn,
|
||||
getForkFn: getForkFn,
|
||||
getGenesisFn: getGenesisFn)
|
||||
|
||||
proc echoP*(msg: string) =
|
||||
## Prints a paragraph aligned to 80 columns
|
||||
|
|
Loading…
Reference in New Issue