--- 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, &ethpb.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 := &ethpb.VoluntaryExit{Epoch: currentEpoch, ValidatorIndex: indexResponse.Index} sig, err := signVoluntaryExit(ctx, validatorClient, signer, pubKey, exit) ... signedExit := &ethpb.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 } ````