---
tags: exit, research
---
# Voluntary Exit (research)
## CLI
````
bazel run //validator:validator -- accounts voluntary-exit --help
NAME:
validator accounts voluntary-exit -
USAGE:
validator accounts voluntary-exit [command options] [arguments...]
DESCRIPTION:
Performs a voluntary exit on selected accounts
OPTIONS:
--wallet-dir value Path to a wallet directory on-disk for Prysm validator accounts (default: "/home/mezin/.eth2validators/prysm-wallet-v2")
--wallet-password-file value Path to a plain-text, .txt file containing your wallet password
--account-password-file value Path to a plain-text, .txt file containing a password for a validator account
--public-keys value Comma-separated list of public key hex strings to specify on which validator accounts to perform a voluntary exit
--beacon-rpc-provider value Beacon node RPC provider endpoint (default: "127.0.0.1:4000")
--grpc-max-msg-size value Integer to define max recieve message call size (default: 15900000)
--tls-cert value Certificate for secure gRPC. Pass this and the tls-key flag in order to use gRPC securely.
--grpc-headers value A comma separated list of key value pairs to pass as gRPC headers for all gRPC calls. Example: --grpc-headers=key=value
--grpc-retries value Number of attempts to retry gRPC requests (default: 5)
--grpc-retry-delay value The amount of time between gRPC retry requests. (default: 1s)
--exit-all Exit all validators. This will still require the staker to confirm a userprompt for the action (default: false)
--mainnet Run on Ethereum Beacon Chain Main Net. This is the default and can be omitted. (default: true)
--pyrmont This defines the flag through which we can run on the Pyrmont Multiclient Testnet (default: false)
--prater Run Prysm configured for the Prater test network (default: false)
--accept-terms-of-use Accept Terms and Conditions (for non-interactive environments) (default: false)
--help, -h show help (default: false)
````
````
bazel run //validator:validator -- accounts voluntary-exit --wallet-dir=/home/mezin/go/src/tesseract/.data/node-0/wallet-dir-memo --wallet-password-file=/home/mezin/go/src/tesseract/.data/node-0/password.txt
[2023-01-31 20:31:57] WARN flags: Running on Ethereum Consensus Mainnet
[Selected account] 0 | previously-distinct-sparrow | 0x8d633e8189d3
Done with selections
Are you sure you want to perform a voluntary exit on 1 account? (0x8d633e8189d3) Y/N:
y
===============IMPORTANT===============
Withdrawing funds is not possible in Phase 0 of the system. Please navigate to the following website and make sure you understand the current implications of a voluntary exit before making the final decision:
https://docs.prylabs.network/docs/wallet/exiting-a-validator/#withdrawal-delay-warning
If you still want to continue with the voluntary exit, please input a phrase found at the end of the page from the above URL:
Exit my validator
[2023-01-31 21:11:35] WARN validator: You are using an insecure gRPC connection. If you are running your beacon node and validator on the same machines, you can ignore this message. If you want to know how to enable secure connections, see: https://docs.prylabs.network/docs/prysm-usage/secure-grpc
[2023-01-31 21:11:35] WARN accounts: Could not perform voluntary exit for account 0x8c2fc2bdf1a5: failed to propose voluntary exit: rpc error: code = InvalidArgument desc = validator has not been active long enough to exit: 4 of 256 epochs. Validator will be eligible for exit at epoch 256
[2023-01-31 21:11:35] INFO accounts: No successful voluntary exits
````
### success
И после перенастройки конфига успешно отработало
````go
ShardCommitteePeriod types.Epoch `yaml:"SHARD_COMMITTEE_PERIOD" spec:"true"`
// ShardCommitteePeriod is the minimum amount of
// epochs a validator must participate before exiting.
````
````
/home/mezin/go/src/tesseract/config/params
mainnet_config.go
ShardCommitteePeriod: 256,
````
````
[2023-02-01 14:41:36] WARN flags: Running on Ethereum Consensus Mainnet
[Selected account] 0 | sincerely-loving-chigger | 0x8c2fc2bdf1a5
[Selected account] 0 | sincerely-loving-chigger | 0x8c2fc2bdf1a5
Done with selections
Are you sure you want to perform a voluntary exit on 1 account? (0x8c2fc2bdf1a5) Y/N:
y
===============IMPORTANT===============
Withdrawing funds is not possible in Phase 0 of the system. Please navigate to the following website and make sure you understand the current implications of a voluntary exit before making the final decision:
https://docs.prylabs.network/docs/wallet/exiting-a-validator/#withdrawal-delay-warning
If you still want to continue with the voluntary exit, please input a phrase found at the end of the page from the above URL:
Exit my validator
[2023-02-01 14:58:50] WARN validator: You are using an insecure gRPC connection. If you are running your beacon node and validator on the same machines, you can ignore this message. If you want to know how to enable secure connections, see: https://docs.prylabs.network/docs/prysm-usage/secure-grpc
[2023-02-01 14:58:50] INFO accounts: Voluntary exit was successful for the accounts listed. URLs where you can track each validator's exit:
https://beaconcha.in/validator/8c2fc2bdf1a5b519abf28c1297982d7f22c6a3506cb28ebcc4f9ffc3091348aa5260f2db34a4b68f79a99c476b3ad038
publicKeys=0x8c2fc2bdf1a5
````
До запуска процедуры:
````json
{
"index": "90",
"balance": "32000019182209",
"status": "active_ongoing",
"validator": {
"pubkey": "0x8c2fc2bdf1a5b519abf28c1297982d7f22c6a3506cb28ebcc4f9ffc3091348aa5260f2db34a4b68f79a99c476b3ad038",
"withdrawal_credentials": "0x010000000000000000000000e43bb1b64fc7068d313d24d01d8ccca785b22c72",
"effective_balance": "32000000000000",
"slashed": false,
"activation_eligibility_epoch": "0",
"activation_epoch": "0",
"exit_epoch": "18446744073709551615",
"withdrawable_epoch": "18446744073709551615"
}
}
````
и после:
````json
{
"data": {
"index": "90",
"balance": "32000546143994",
"status": "active_exiting",
"validator": {
"pubkey": "0x8c2fc2bdf1a5b519abf28c1297982d7f22c6a3506cb28ebcc4f9ffc3091348aa5260f2db34a4b68f79a99c476b3ad038",
"withdrawal_credentials": "0x010000000000000000000000e43bb1b64fc7068d313d24d01d8ccca785b22c72",
"effective_balance": "32000000000000",
"slashed": false,
"activation_eligibility_epoch": "0",
"activation_epoch": "0",
"exit_epoch": "26",
"^^^^^^^^^^": "^^",
"withdrawable_epoch": "282",
"^^^^^^^^^^^^^^^^^^": "^^^"
}
},
"execution_optimistic": true
},
````
после 26 эпохи:
````json
{
"data": {
"index": "90",
"balance": "32000626027307",
"status": "exited_unslashed",
"validator": {
"pubkey": "0x8c2fc2bdf1a5b519abf28c1297982d7f22c6a3506cb28ebcc4f9ffc3091348aa5260f2db34a4b68f79a99c476b3ad038",
"withdrawal_credentials": "0x010000000000000000000000e43bb1b64fc7068d313d24d01d8ccca785b22c72",
"effective_balance": "32000000000000",
"slashed": false,
"activation_eligibility_epoch": "0",
"activation_epoch": "0",
"exit_epoch": "26",
"withdrawable_epoch": "282"
}
},
"execution_optimistic": true
},
````
## init exit (stacks)
### AccountsHandlerServer
````go
tesseract/proto/prysm/v1alpha1/validator-client
web_api.pb.gw.go
RegisterAccountsHandlerServer
resp, md, err := local_request_Accounts_VoluntaryExit_0(rctx, inboundMarshaler, server, req, pathParams)
/home/mezin/go/src/tesseract/proto/prysm/v1alpha1/validator-client
web_api.pb.gw.go
local_request_Accounts_VoluntaryExit_0
msg, err := server.VoluntaryExit(ctx, &protoReq)
/home/mezin/go/src/tesseract/validator/rpc
accounts.go
VoluntaryExit
rawExitedKeys, _, err := accounts.PerformVoluntaryExit(ctx, cfg)
...
/home/mezin/go/src/tesseract/validator/rpc
accounts.go
VoluntaryExit
rawExitedKeys, _, err := accounts.PerformVoluntaryExit(ctx, cfg)
````
### AccountsServer
````go
/home/mezin/go/src/tesseract/proto/prysm/v1alpha1/validator-client
web_api.pb.go
RegisterAccountsServer
s.RegisterService(&_Accounts_serviceDesc, srv)
var _Accounts_serviceDesc = grpc.ServiceDesc{
ServiceName: "ethereum.validator.accounts.v2.Accounts",
Methods: []grpc.MethodDesc{
{
MethodName: "ListAccounts",
Handler: _Accounts_ListAccounts_Handler,
},
{
MethodName: "BackupAccounts",
Handler: _Accounts_BackupAccounts_Handler,
},
{
MethodName: "DeleteAccounts",
Handler: _Accounts_DeleteAccounts_Handler,
},
{
MethodName: "VoluntaryExit",
Handler: _Accounts_VoluntaryExit_Handler,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
},
},
}
/home/mezin/go/src/tesseract/proto/prysm/v1alpha1/validator-client
web_api.pb.go
_Accounts_VoluntaryExit_Handler
return srv.(AccountsServer).VoluntaryExit(ctx, in)
return srv.(AccountsServer).VoluntaryExit(ctx, req.(*VoluntaryExitRequest))
/home/mezin/go/src/tesseract/validator/rpc
accounts.go
VoluntaryExit
rawExitedKeys, _, err := accounts.PerformVoluntaryExit(ctx, cfg)
````
### ExitAccountsCli
````go
/home/mezin/go/src/tesseract/validator/accounts
accounts_exit.go
ExitAccountsCli
rawExitedKeys, trimmedExitedKeys, err := PerformVoluntaryExit(cliCtx.Context, cfg)
````
## PerformVoluntaryExit
````go
// PerformVoluntaryExit uses gRPC clients to submit a voluntary exit message to a beacon node.
func PerformVoluntaryExit(
ctx context.Context, cfg PerformExitCfg,
) (rawExitedKeys [][]byte, formattedExitedKeys []string, err error) {
for i, key := range cfg.RawPubKeys {
if err := client.ProposeExit(ctx, cfg.ValidatorClient, cfg.NodeClient, cfg.Keymanager.Sign, key); err != nil { ... }
}
...
}
// ProposeExit performs a voluntary exit on a validator.
// The exit is signed by the validator before being sent to the beacon node for broadcasting.
func ProposeExit(
ctx context.Context,
validatorClient ethpb.BeaconNodeValidatorClient,
nodeClient ethpb.NodeClient,
signer signingFunc,
pubKey []byte,
) error {
indexResponse, err := validatorClient.ValidatorIndex(ctx, ðpb.ValidatorIndexRequest{PublicKey: pubKey})
...
genesisResponse, err := nodeClient.GetGenesis(ctx, &emptypb.Empty{})
...
totalSecondsPassed := prysmTime.Now().Unix() - genesisResponse.GenesisTime.Seconds
currentEpoch := types.Epoch(uint64(totalSecondsPassed) / uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot)))
exit := ðpb.VoluntaryExit{Epoch: currentEpoch, ValidatorIndex: indexResponse.Index}
sig, err := signVoluntaryExit(ctx, validatorClient, signer, pubKey, exit)
...
signedExit := ðpb.SignedVoluntaryExit{Exit: exit, Signature: sig}
exitResp, err := validatorClient.ProposeExit(ctx, signedExit)
...
return nil
}
````
## Epoch handling
````go
InitiateValidatorExit
/home/mezin/go/src/tesseract/beacon-chain/core/blocks
exit.go
ProcessVoluntaryExits
beaconState, err = v.InitiateValidatorExit(ctx, beaconState, exit.Exit.ValidatorIndex)
^^^^^^^^^^^^^^^^^^^^^^^
/home/mezin/go/src/tesseract/beacon-chain/core/epoch
epoch_processing.go
ProcessRegistryUpdates
state, err = validators.InitiateValidatorExit(ctx, state, types.ValidatorIndex(idx))
/home/mezin/go/src/tesseract/beacon-chain/core/validators
validator.go
SlashValidator
s, err := InitiateValidatorExit(ctx, s, slashedIdx)
````
### InitiateValidatorExit
````go
...
// # Set validator exit epoch and withdrawable epoch
// validator.exit_epoch = exit_queue_epoch
// validator.withdrawable_epoch = Epoch(validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY)
func InitiateValidatorExit(ctx context.Context, s state.BeaconState, idx types.ValidatorIndex) (state.BeaconState, error) {
...
var exitEpochs []types.Epoch
err = s.ReadFromEveryValidator(func(idx int, val state.ReadOnlyValidator) error {
if val.ExitEpoch() != params.BeaconConfig().FarFutureEpoch {
exitEpochs = append(exitEpochs, val.ExitEpoch())
}
return nil
})
...
exitEpochs = append(exitEpochs, helpers.ActivationExitEpoch(time.CurrentEpoch(s)))
// Obtain the exit queue epoch as the maximum number in the exit epochs array.
exitQueueEpoch := types.Epoch(0)
for _, i := range exitEpochs {
if exitQueueEpoch < i {
exitQueueEpoch = i
}
}
// We use the exit queue churn to determine if we have passed a churn limit.
exitQueueChurn := uint64(0)
err = s.ReadFromEveryValidator(func(idx int, val state.ReadOnlyValidator) error {
if val.ExitEpoch() == exitQueueEpoch {
exitQueueChurn++
}
return nil
})
if err != nil {
return nil, err
}
activeValidatorCount, err := helpers.ActiveValidatorCount(ctx, s, time.CurrentEpoch(s))
if err != nil {
return nil, errors.Wrap(err, "could not get active validator count")
}
churn, err := helpers.ValidatorChurnLimit(activeValidatorCount)
if err != nil {
return nil, errors.Wrap(err, "could not get churn limit")
}
if exitQueueChurn >= churn {
exitQueueEpoch++
}
validator.ExitEpoch = exitQueueEpoch
validator.WithdrawableEpoch = exitQueueEpoch + params.BeaconConfig().MinValidatorWithdrawabilityDelay
if err := s.UpdateValidatorAtIndex(idx, validator); err != nil {
return nil, err
}
return s, nil
}
````