penumbra_sdk_governance/
component.rs1use std::sync::Arc;
2
3use crate::{event, genesis};
4use anyhow::{Context, Result};
5use async_trait::async_trait;
6use cnidarium::StateWrite;
7use penumbra_sdk_proto::StateWriteProto as _;
8use tendermint::v0_37::abci;
9use tracing::instrument;
10
11use cnidarium_component::Component;
12
13use crate::{
14 proposal_state::{
15 Outcome as ProposalOutcome, State as ProposalState, Withdrawn as ProposalWithdrawn,
16 },
17 tally,
18};
19
20mod view;
21
22pub mod rpc;
23
24pub use view::StateReadExt;
25pub use view::StateWriteExt;
26
27use penumbra_sdk_sct::component::clock::EpochRead;
28
29pub struct Governance {}
30
31#[async_trait]
32impl Component for Governance {
33 type AppState = genesis::Content;
34
35 #[instrument(name = "governance", skip(state, app_state))]
36 async fn init_chain<S: StateWrite>(mut state: S, app_state: Option<&Self::AppState>) {
37 match app_state {
38 Some(genesis) => {
39 state.put_governance_params(genesis.governance_params.clone());
40 state.init_proposal_counter();
43 }
44 None => {}
45 }
46 }
47
48 #[instrument(name = "governance", skip(_state, _begin_block))]
49 async fn begin_block<S: StateWrite + 'static>(
50 _state: &mut Arc<S>,
51 _begin_block: &abci::request::BeginBlock,
52 ) {
53 }
54
55 #[instrument(name = "governance", skip(state, _end_block))]
56 async fn end_block<S: StateWrite + 'static>(
57 state: &mut Arc<S>,
58 _end_block: &abci::request::EndBlock,
59 ) {
60 let mut state = Arc::get_mut(state).expect("state should be unique");
61 enact_all_passed_proposals(&mut state)
65 .await
66 .expect("enacting proposals should never fail");
67 }
68
69 #[instrument(name = "governance", skip(state))]
70 async fn end_epoch<S: StateWrite + 'static>(state: &mut Arc<S>) -> Result<()> {
71 let state = Arc::get_mut(state).expect("state should be unique");
72 state.tally_delegator_votes(None).await?;
73 Ok(())
74 }
75}
76
77#[instrument(skip(state))]
78pub async fn enact_all_passed_proposals<S: StateWrite>(mut state: S) -> Result<()> {
79 for proposal_id in state
81 .unfinished_proposals()
82 .await
83 .context("can get unfinished proposals")?
84 {
85 let proposal_ready = state
87 .get_block_height()
88 .await
89 .expect("block height must be set")
90 >= state
91 .proposal_voting_end(proposal_id)
92 .await?
93 .context("proposal has voting end")?;
94
95 if !proposal_ready {
96 continue;
97 }
98
99 state.tally_delegator_votes(Some(proposal_id)).await?;
101
102 let current_state = state
103 .proposal_state(proposal_id)
104 .await?
105 .context("proposal has id")?;
106
107 let outcome = match current_state {
108 ProposalState::Voting => {
109 let outcome = state.current_tally(proposal_id).await?.outcome(
112 state
113 .total_voting_power_at_proposal_start(proposal_id)
114 .await?,
115 &state.get_governance_params().await?,
116 );
117
118 match outcome {
122 tally::Outcome::Pass => {
123 let payload = state
128 .proposal_payload(proposal_id)
129 .await?
130 .context("proposal has payload")?;
131 match state.enact_proposal(proposal_id, &payload).await? {
132 Ok(()) => {
133 tracing::info!(proposal = %proposal_id, "proposal passed and enacted successfully");
134 }
135 Err(error) => {
136 tracing::warn!(proposal = %proposal_id, %error, "proposal passed but failed to enact");
137 }
138 };
139
140 let proposal =
141 state
142 .proposal_definition(proposal_id)
143 .await?
144 .ok_or_else(|| {
145 anyhow::anyhow!("proposal {} does not exist", proposal_id)
146 })?;
147 state.record_proto(event::proposal_passed(&proposal));
148 }
149 tally::Outcome::Fail => {
150 tracing::info!(proposal = %proposal_id, "proposal failed");
151
152 let proposal =
153 state
154 .proposal_definition(proposal_id)
155 .await?
156 .ok_or_else(|| {
157 anyhow::anyhow!("proposal {} does not exist", proposal_id)
158 })?;
159 state.record_proto(event::proposal_failed(&proposal));
160 }
161 tally::Outcome::Slash => {
162 tracing::info!(proposal = %proposal_id, "proposal slashed");
163
164 let proposal =
165 state
166 .proposal_definition(proposal_id)
167 .await?
168 .ok_or_else(|| {
169 anyhow::anyhow!("proposal {} does not exist", proposal_id)
170 })?;
171 state.record_proto(event::proposal_slashed(&proposal));
172 }
173 }
174
175 outcome.into()
176 }
177 ProposalState::Withdrawn { reason } => {
178 tracing::info!(proposal = %proposal_id, reason = ?reason, "proposal concluded after being withdrawn");
179 ProposalOutcome::Failed {
180 withdrawn: ProposalWithdrawn::WithReason { reason },
181 }
182 }
183 ProposalState::Finished { outcome: _ } => {
184 anyhow::bail!("proposal {proposal_id} is already finished, and should have been removed from the active set");
185 }
186 ProposalState::Claimed { outcome: _ } => {
187 anyhow::bail!("proposal {proposal_id} is already claimed, and should have been removed from the active set");
188 }
189 };
190
191 state.put_proposal_state(proposal_id, ProposalState::Finished { outcome });
193 }
194
195 Ok(())
196}