diff --git a/.changelog/unreleased/improvements/2246-fix-liveness.md b/.changelog/unreleased/improvements/2246-fix-liveness.md new file mode 100644 index 0000000000..524c79aafb --- /dev/null +++ b/.changelog/unreleased/improvements/2246-fix-liveness.md @@ -0,0 +1,3 @@ +- Fix bug in client to allow for unjailing a validator + that was jailed for missing liveness requirements + ([\#2246](https://github.com/anoma/namada/pull/2246)) \ No newline at end of file diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 9b902e2ba0..e92a150958 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -5840,6 +5840,12 @@ where .collect::>(); for validator in &validators_to_jail { + let state_jail_epoch = validator_state_handle(validator) + .get(storage, jail_epoch, params)? + .expect("Validator should have a state for the jail epoch"); + if state_jail_epoch == ValidatorState::Jailed { + continue; + } tracing::info!( "Jailing validator {} starting in epoch {} for missing too many \ votes to ensure liveness", diff --git a/sdk/src/queries/vp/pos.rs b/sdk/src/queries/vp/pos.rs index 9414379b9a..5118676ce7 100644 --- a/sdk/src/queries/vp/pos.rs +++ b/sdk/src/queries/vp/pos.rs @@ -24,10 +24,10 @@ use namada_proof_of_stake::{ read_consensus_validator_set_addresses_with_stake, read_pos_params, read_total_stake, read_validator_description, read_validator_discord_handle, read_validator_email, - read_validator_max_commission_rate_change, read_validator_stake, - read_validator_website, unbond_handle, validator_commission_rate_handle, - validator_incoming_redelegations_handle, validator_slashes_handle, - validator_state_handle, + read_validator_last_slash_epoch, read_validator_max_commission_rate_change, + read_validator_stake, read_validator_website, unbond_handle, + validator_commission_rate_handle, validator_incoming_redelegations_handle, + validator_slashes_handle, validator_state_handle, }; use crate::queries::types::RequestCtx; @@ -57,6 +57,9 @@ router! {POS, ( "incoming_redelegation" / [src_validator: Address] / [delegator: Address] ) -> Option = validator_incoming_redelegation, + + ( "last_infraction_epoch" / [validator: Address] ) + -> Option = validator_last_infraction_epoch, }, ( "validator_set" ) = { @@ -291,6 +294,18 @@ where Ok(state) } +/// Get the validator state +fn validator_last_infraction_epoch( + ctx: RequestCtx<'_, D, H, V, T>, + validator: Address, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + read_validator_last_slash_epoch(ctx.wl_storage, &validator) +} + /// Get the total stake of a validator at the given epoch or current when /// `None`. The total stake is a sum of validator's self-bonds and delegations /// to their address. diff --git a/sdk/src/rpc.rs b/sdk/src/rpc.rs index 19adb4d365..6dc6850193 100644 --- a/sdk/src/rpc.rs +++ b/sdk/src/rpc.rs @@ -815,6 +815,19 @@ pub async fn query_bond( ) } +/// Query a validator's bonds for a given epoch +pub async fn query_last_infraction_epoch( + client: &C, + validator: &Address, +) -> Result, error::Error> { + convert_response::( + RPC.vp() + .pos() + .validator_last_infraction_epoch(client, validator) + .await, + ) +} + /// Query the accunt substorage space of an address pub async fn get_account_info( client: &C, diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index d9c2e2b31d..d99a9c6f44 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -897,23 +897,20 @@ pub async fn build_unjail_validator( } } - let last_slash_epoch_key = - namada_proof_of_stake::storage::validator_last_slash_key(validator); - let last_slash_epoch = rpc::query_storage_value::<_, Epoch>( - context.client(), - &last_slash_epoch_key, - ) - .await; + let last_slash_epoch = + rpc::query_last_infraction_epoch(context.client(), validator).await; match last_slash_epoch { - Ok(last_slash_epoch) => { + Ok(Some(last_slash_epoch)) => { + // Jailed due to slashing let eligible_epoch = last_slash_epoch + params.slash_processing_epoch_offset(); if current_epoch < eligible_epoch { edisplay_line!( context.io(), "The given validator address {} is currently frozen and \ - not yet eligible to be unjailed.", - &validator + will be eligible to be unjailed starting at epoch {}.", + &validator, + eligible_epoch ); if !tx_args.force { return Err(Error::from( @@ -924,22 +921,14 @@ pub async fn build_unjail_validator( } } } - Err(Error::Query( - QueryError::NoSuchKey(_) | QueryError::General(_), - )) => { - edisplay_line!( - context.io(), - "The given validator address {} is currently frozen and not \ - yet eligible to be unjailed.", - &validator - ); + Ok(None) => { + // Jailed due to liveness only. No checks needed. + } + Err(err) => { if !tx_args.force { - return Err(Error::from( - TxError::ValidatorFrozenFromUnjailing(validator.clone()), - )); + return Err(err); } } - Err(err) => return Err(err), } build(