11use clap:: { Arg , ArgAction , ArgMatches , Command } ;
22use eth2:: lighthouse_vc:: types:: SingleKeystoreResponse ;
3- use eth2:: SensitiveUrl ;
3+ use eth2:: types:: { ConfigAndPreset , StateId , ValidatorId , ValidatorStatus } ;
4+ use eth2:: { BeaconNodeHttpClient , SensitiveUrl , Timeouts } ;
45use serde:: { Deserialize , Serialize } ;
56use std:: path:: PathBuf ;
7+ use std:: time:: Duration ;
8+ use types:: { ChainSpec , EthSpec , PublicKeyBytes } ;
69
10+ use crate :: exit_validators:: get_current_epoch;
711use crate :: { common:: vc_http_client, DumpConfig } ;
812
913pub const CMD : & str = "list" ;
1014pub const VC_URL_FLAG : & str = "vc-url" ;
1115pub const VC_TOKEN_FLAG : & str = "vc-token" ;
16+ pub const BEACON_URL_FLAG : & str = "beacon-node" ;
17+ pub const VALIDATOR_FLAG : & str = "validators" ;
1218
1319pub fn cli_app ( ) -> Command {
1420 Command :: new ( CMD )
@@ -31,47 +37,177 @@ pub fn cli_app() -> Command {
3137 . action ( ArgAction :: Set )
3238 . display_order ( 0 ) ,
3339 )
40+ . arg (
41+ Arg :: new ( BEACON_URL_FLAG )
42+ . long ( BEACON_URL_FLAG )
43+ . value_name ( "NETWORK_ADDRESS" )
44+ . help (
45+ "Address to a beacon node HTTP API. When supplied, \
46+ the status of validators (with regard to voluntary exit) \
47+ will be displayed. This flag is to be used together with \
48+ the --validators flag.",
49+ )
50+ . action ( ArgAction :: Set )
51+ . display_order ( 0 )
52+ . requires ( VALIDATOR_FLAG ) ,
53+ )
54+ . arg (
55+ Arg :: new ( VALIDATOR_FLAG )
56+ . long ( VALIDATOR_FLAG )
57+ . value_name ( "STRING" )
58+ . help (
59+ "Comma-separated list of validators (pubkey) to display status for. \
60+ To display the status for all validators, use the keyword \" all\" . \
61+ This flag is to be used together with the --beacon-node flag.",
62+ )
63+ . action ( ArgAction :: Set )
64+ . display_order ( 0 )
65+ . requires ( BEACON_URL_FLAG ) ,
66+ )
3467}
3568
3669#[ derive( Clone , PartialEq , Debug , Serialize , Deserialize ) ]
3770pub struct ListConfig {
3871 pub vc_url : SensitiveUrl ,
3972 pub vc_token_path : PathBuf ,
73+ pub beacon_url : Option < SensitiveUrl > ,
74+ pub validators_to_display : Vec < PublicKeyBytes > ,
4075}
4176
4277impl ListConfig {
4378 fn from_cli ( matches : & ArgMatches ) -> Result < Self , String > {
79+ let validators_to_display_str =
80+ clap_utils:: parse_optional :: < String > ( matches, VALIDATOR_FLAG ) ?;
81+
82+ // Keyword "all" to exit all validators, vector to be created later
83+ let validators_to_display = match validators_to_display_str {
84+ Some ( str) => {
85+ if str. trim ( ) == "all" {
86+ Vec :: new ( )
87+ } else {
88+ str. split ( ',' )
89+ . map ( |s| s. trim ( ) . parse ( ) )
90+ . collect :: < Result < Vec < PublicKeyBytes > , _ > > ( ) ?
91+ }
92+ }
93+ None => Vec :: new ( ) ,
94+ } ;
95+
4496 Ok ( Self {
4597 vc_token_path : clap_utils:: parse_required ( matches, VC_TOKEN_FLAG ) ?,
4698 vc_url : clap_utils:: parse_required ( matches, VC_URL_FLAG ) ?,
99+ beacon_url : clap_utils:: parse_optional ( matches, BEACON_URL_FLAG ) ?,
100+ validators_to_display,
47101 } )
48102 }
49103}
50104
51- pub async fn cli_run ( matches : & ArgMatches , dump_config : DumpConfig ) -> Result < ( ) , String > {
105+ pub async fn cli_run < E : EthSpec > (
106+ matches : & ArgMatches ,
107+ dump_config : DumpConfig ,
108+ ) -> Result < ( ) , String > {
52109 let config = ListConfig :: from_cli ( matches) ?;
53110 if dump_config. should_exit_early ( & config) ? {
54111 Ok ( ( ) )
55112 } else {
56- run ( config) . await ?;
113+ run :: < E > ( config) . await ?;
57114 Ok ( ( ) )
58115 }
59116}
60117
61- async fn run ( config : ListConfig ) -> Result < Vec < SingleKeystoreResponse > , String > {
118+ async fn run < E : EthSpec > ( config : ListConfig ) -> Result < Vec < SingleKeystoreResponse > , String > {
62119 let ListConfig {
63120 vc_url,
64121 vc_token_path,
122+ beacon_url,
123+ mut validators_to_display,
65124 } = config;
66125
67126 let ( _, validators) = vc_http_client ( vc_url. clone ( ) , & vc_token_path) . await ?;
68127
69128 println ! ( "List of validators ({}):" , validators. len( ) ) ;
70129
71- for validator in & validators {
72- println ! ( "{}" , validator . validating_pubkey) ;
130+ if validators_to_display . is_empty ( ) {
131+ validators_to_display = validators . iter ( ) . map ( |v| v . validating_pubkey ) . collect ( ) ;
73132 }
74133
134+ if let Some ( ref beacon_url) = beacon_url {
135+ for validator in & validators_to_display {
136+ let beacon_node = BeaconNodeHttpClient :: new (
137+ SensitiveUrl :: parse ( beacon_url. as_ref ( ) )
138+ . map_err ( |e| format ! ( "Failed to parse beacon http server: {:?}" , e) ) ?,
139+ Timeouts :: set_all ( Duration :: from_secs ( 12 ) ) ,
140+ ) ;
141+
142+ let validator_data = beacon_node
143+ . get_beacon_states_validator_id ( StateId :: Head , & ValidatorId :: PublicKey ( * validator) )
144+ . await
145+ . map_err ( |e| format ! ( "Failed to get updated validator details: {:?}" , e) ) ?
146+ . ok_or_else ( || {
147+ format ! ( "Validator {} is not present in the beacon state" , validator)
148+ } ) ?
149+ . data ;
150+
151+ match validator_data. status {
152+ ValidatorStatus :: ActiveExiting => {
153+ let exit_epoch = validator_data. validator . exit_epoch ;
154+ let withdrawal_epoch = validator_data. validator . withdrawable_epoch ;
155+
156+ let genesis_data = beacon_node
157+ . get_beacon_genesis ( )
158+ . await
159+ . map_err ( |e| format ! ( "Failed to get genesis data: {}" , e) ) ?
160+ . data ;
161+
162+ let config_and_preset = beacon_node
163+ . get_config_spec :: < ConfigAndPreset > ( )
164+ . await
165+ . map_err ( |e| format ! ( "Failed to get config spec: {}" , e) ) ?
166+ . data ;
167+
168+ let spec = ChainSpec :: from_config :: < E > ( config_and_preset. config ( ) )
169+ . ok_or ( "Failed to create chain spec" ) ?;
170+
171+ let current_epoch = get_current_epoch :: < E > ( genesis_data. genesis_time , & spec)
172+ . ok_or ( "Failed to get current epoch. Please check your system time" ) ?;
173+
174+ eprintln ! (
175+ "Voluntary exit for validator {} has been accepted into the beacon chain. \
176+ Note that the voluntary exit is subject chain finalization. \
177+ Before the chain has finalized, there is a low \
178+ probability that the exit may be reverted.",
179+ validator
180+ ) ;
181+ eprintln ! (
182+ "Current epoch: {}, Exit epoch: {}, Withdrawable epoch: {}" ,
183+ current_epoch, exit_epoch, withdrawal_epoch
184+ ) ;
185+ eprintln ! ( "Please keep your validator running till exit epoch" ) ;
186+ eprintln ! (
187+ "Exit epoch in approximately {} secs" ,
188+ ( exit_epoch - current_epoch) * spec. seconds_per_slot * E :: slots_per_epoch( )
189+ ) ;
190+ }
191+ ValidatorStatus :: ExitedSlashed | ValidatorStatus :: ExitedUnslashed => {
192+ eprintln ! (
193+ "Validator {} has exited at epoch: {}" ,
194+ validator, validator_data. validator. exit_epoch
195+ ) ;
196+ }
197+ _ => {
198+ eprintln ! (
199+ "Validator {} has not initiated voluntary exit or the voluntary exit \
200+ is yet to be accepted into the beacon chain. Validator status is: {}",
201+ validator, validator_data. status
202+ )
203+ }
204+ }
205+ }
206+ } else {
207+ for validator in & validators {
208+ println ! ( "{}" , validator. validating_pubkey) ;
209+ }
210+ }
75211 Ok ( validators)
76212}
77213
0 commit comments