VC+BN: Validator voluntary exits through the Keymanager API (#5020)

* Initial commit.

* Address review comments.
This commit is contained in:
Eugene Kabanov 2023-06-14 09:46:01 +03:00 committed by GitHub
parent c0e5c26da1
commit 927180f36f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 101 additions and 12 deletions

View File

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

View File

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

View File

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

View File

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

View File

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