penumbra_sdk_governance/action_handler/
validator_vote.rs1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use cnidarium::StateWrite;
4use penumbra_sdk_proto::{DomainType, StateWriteProto as _};
5
6use crate::component::StateWriteExt;
7use crate::event;
8use crate::{action_handler::ActionHandler, StateReadExt};
9use crate::{
10 proposal_state::Outcome,
11 proposal_state::State as ProposalState,
12 {ValidatorVote, ValidatorVoteBody, MAX_VALIDATOR_VOTE_REASON_LENGTH},
13};
14
15#[async_trait]
16impl ActionHandler for ValidatorVote {
17 type CheckStatelessContext = ();
18 async fn check_stateless(&self, _context: ()) -> Result<()> {
19 let ValidatorVote { body, auth_sig } = self;
20
21 let body_bytes = body.encode_to_vec();
23 body.governance_key
24 .0
25 .verify(&body_bytes, auth_sig)
26 .context("validator vote signature failed to verify")?;
27
28 if body.reason.0.len() > MAX_VALIDATOR_VOTE_REASON_LENGTH {
30 anyhow::bail!("validator vote reason is too long");
31 }
32
33 Ok(())
37 }
38
39 async fn check_and_execute<S: StateWrite>(&self, mut state: S) -> Result<()> {
40 let ValidatorVote {
41 auth_sig: _,
42 body:
43 ValidatorVoteBody {
44 proposal,
45 vote,
46 identity_key,
47 governance_key,
48 reason,
49 },
50 } = self;
51
52 state.check_proposal_votable(*proposal).await?;
53 state
54 .check_validator_active_at_proposal_start(*proposal, identity_key)
55 .await?;
56 state
57 .check_validator_has_not_voted(*proposal, identity_key)
58 .await?;
59 state
60 .check_governance_key_matches_validator(identity_key, governance_key)
61 .await?;
62
63 let proposal_state = state
64 .proposal_state(*proposal)
65 .await?
66 .expect("proposal missing state");
67
68 if proposal_state.is_withdrawn() {
74 tracing::debug!(validator_identity = %identity_key, proposal = %proposal, "cannot cast a vote for a withdrawn proposal");
75 return Ok(());
76 }
77
78 tracing::debug!(validator_identity = %identity_key, proposal = %proposal, "cast validator vote");
79 state.cast_validator_vote(*proposal, *identity_key, *vote, reason.clone());
80
81 let proposal_payload = state
85 .proposal_payload(*proposal)
86 .await?
87 .expect("proposal missing payload");
88
89 if proposal_payload.is_emergency() || proposal_payload.is_ibc_freeze() {
90 tracing::debug!(proposal = %proposal, "detected an emergency-tier proposal, checking pass conditions");
91 let tally = state.current_tally(*proposal).await?;
92 let total_voting_power = state
93 .total_voting_power_at_proposal_start(*proposal)
94 .await?;
95 let governance_params = state.get_governance_params().await?;
96 if tally.emergency_pass(total_voting_power, &governance_params) {
97 tracing::debug!(proposal = %proposal, "emergency pass condition met, trying to enact proposal");
99 match state.enact_proposal(*proposal, &proposal_payload).await? {
101 Ok(_) => tracing::debug!(proposal = %proposal, "emergency proposal enacted"),
102 Err(error) => {
103 tracing::error!(proposal = %proposal, %error, "error enacting emergency proposal")
104 }
105 }
106 state.put_proposal_state(
109 *proposal,
110 ProposalState::Finished {
111 outcome: Outcome::Passed,
112 },
113 );
114 }
115 }
116
117 let voting_power = state
119 .specific_validator_voting_power_at_proposal_start(*proposal, *identity_key)
120 .await?;
121 state.record_proto(event::validator_vote(self, voting_power));
122
123 Ok(())
124 }
125}