penumbra_governance/action_handler/
validator_vote.rsuse anyhow::{Context, Result};
use async_trait::async_trait;
use cnidarium::StateWrite;
use penumbra_proto::{DomainType, StateWriteProto as _};
use crate::component::StateWriteExt;
use crate::event;
use crate::{action_handler::ActionHandler, StateReadExt};
use crate::{
proposal_state::Outcome,
proposal_state::State as ProposalState,
{ValidatorVote, ValidatorVoteBody, MAX_VALIDATOR_VOTE_REASON_LENGTH},
};
#[async_trait]
impl ActionHandler for ValidatorVote {
type CheckStatelessContext = ();
async fn check_stateless(&self, _context: ()) -> Result<()> {
let ValidatorVote { body, auth_sig } = self;
let body_bytes = body.encode_to_vec();
body.governance_key
.0
.verify(&body_bytes, auth_sig)
.context("validator vote signature failed to verify")?;
if body.reason.0.len() > MAX_VALIDATOR_VOTE_REASON_LENGTH {
anyhow::bail!("validator vote reason is too long");
}
Ok(())
}
async fn check_and_execute<S: StateWrite>(&self, mut state: S) -> Result<()> {
let ValidatorVote {
auth_sig: _,
body:
ValidatorVoteBody {
proposal,
vote,
identity_key,
governance_key,
reason,
},
} = self;
state.check_proposal_votable(*proposal).await?;
state
.check_validator_active_at_proposal_start(*proposal, identity_key)
.await?;
state
.check_validator_has_not_voted(*proposal, identity_key)
.await?;
state
.check_governance_key_matches_validator(identity_key, governance_key)
.await?;
let proposal_state = state
.proposal_state(*proposal)
.await?
.expect("proposal missing state");
if proposal_state.is_withdrawn() {
tracing::debug!(validator_identity = %identity_key, proposal = %proposal, "cannot cast a vote for a withdrawn proposal");
return Ok(());
}
tracing::debug!(validator_identity = %identity_key, proposal = %proposal, "cast validator vote");
state.cast_validator_vote(*proposal, *identity_key, *vote, reason.clone());
let proposal_payload = state
.proposal_payload(*proposal)
.await?
.expect("proposal missing payload");
if proposal_payload.is_emergency() || proposal_payload.is_ibc_freeze() {
tracing::debug!(proposal = %proposal, "detected an emergency-tier proposal, checking pass conditions");
let tally = state.current_tally(*proposal).await?;
let total_voting_power = state
.total_voting_power_at_proposal_start(*proposal)
.await?;
let governance_params = state.get_governance_params().await?;
if tally.emergency_pass(total_voting_power, &governance_params) {
tracing::debug!(proposal = %proposal, "emergency pass condition met, trying to enact proposal");
match state.enact_proposal(*proposal, &proposal_payload).await? {
Ok(_) => tracing::debug!(proposal = %proposal, "emergency proposal enacted"),
Err(error) => {
tracing::error!(proposal = %proposal, %error, "error enacting emergency proposal")
}
}
state.put_proposal_state(
*proposal,
ProposalState::Finished {
outcome: Outcome::Passed,
},
);
}
}
let voting_power = state
.specific_validator_voting_power_at_proposal_start(*proposal, *identity_key)
.await?;
state.record_proto(event::validator_vote(self, voting_power));
Ok(())
}
}