penumbra_governance/
component.rsuse std::sync::Arc;
use crate::{event, genesis};
use anyhow::{Context, Result};
use async_trait::async_trait;
use cnidarium::StateWrite;
use penumbra_proto::StateWriteProto as _;
use tendermint::v0_37::abci;
use tracing::instrument;
use cnidarium_component::Component;
use crate::{
proposal_state::{
Outcome as ProposalOutcome, State as ProposalState, Withdrawn as ProposalWithdrawn,
},
tally,
};
mod view;
pub mod rpc;
pub use view::StateReadExt;
pub use view::StateWriteExt;
use penumbra_sct::component::clock::EpochRead;
pub struct Governance {}
#[async_trait]
impl Component for Governance {
type AppState = genesis::Content;
#[instrument(name = "governance", skip(state, app_state))]
async fn init_chain<S: StateWrite>(mut state: S, app_state: Option<&Self::AppState>) {
match app_state {
Some(genesis) => {
state.put_governance_params(genesis.governance_params.clone());
state.init_proposal_counter();
}
None => {}
}
}
#[instrument(name = "governance", skip(_state, _begin_block))]
async fn begin_block<S: StateWrite + 'static>(
_state: &mut Arc<S>,
_begin_block: &abci::request::BeginBlock,
) {
}
#[instrument(name = "governance", skip(state, _end_block))]
async fn end_block<S: StateWrite + 'static>(
state: &mut Arc<S>,
_end_block: &abci::request::EndBlock,
) {
let mut state = Arc::get_mut(state).expect("state should be unique");
enact_all_passed_proposals(&mut state)
.await
.expect("enacting proposals should never fail");
}
#[instrument(name = "governance", skip(state))]
async fn end_epoch<S: StateWrite + 'static>(state: &mut Arc<S>) -> Result<()> {
let state = Arc::get_mut(state).expect("state should be unique");
state.tally_delegator_votes(None).await?;
Ok(())
}
}
#[instrument(skip(state))]
pub async fn enact_all_passed_proposals<S: StateWrite>(mut state: S) -> Result<()> {
for proposal_id in state
.unfinished_proposals()
.await
.context("can get unfinished proposals")?
{
let proposal_ready = state
.get_block_height()
.await
.expect("block height must be set")
>= state
.proposal_voting_end(proposal_id)
.await?
.context("proposal has voting end")?;
if !proposal_ready {
continue;
}
state.tally_delegator_votes(Some(proposal_id)).await?;
let current_state = state
.proposal_state(proposal_id)
.await?
.context("proposal has id")?;
let outcome = match current_state {
ProposalState::Voting => {
let outcome = state.current_tally(proposal_id).await?.outcome(
state
.total_voting_power_at_proposal_start(proposal_id)
.await?,
&state.get_governance_params().await?,
);
match outcome {
tally::Outcome::Pass => {
let payload = state
.proposal_payload(proposal_id)
.await?
.context("proposal has payload")?;
match state.enact_proposal(proposal_id, &payload).await? {
Ok(()) => {
tracing::info!(proposal = %proposal_id, "proposal passed and enacted successfully");
}
Err(error) => {
tracing::warn!(proposal = %proposal_id, %error, "proposal passed but failed to enact");
}
};
let proposal =
state
.proposal_definition(proposal_id)
.await?
.ok_or_else(|| {
anyhow::anyhow!("proposal {} does not exist", proposal_id)
})?;
state.record_proto(event::proposal_passed(&proposal));
}
tally::Outcome::Fail => {
tracing::info!(proposal = %proposal_id, "proposal failed");
let proposal =
state
.proposal_definition(proposal_id)
.await?
.ok_or_else(|| {
anyhow::anyhow!("proposal {} does not exist", proposal_id)
})?;
state.record_proto(event::proposal_failed(&proposal));
}
tally::Outcome::Slash => {
tracing::info!(proposal = %proposal_id, "proposal slashed");
let proposal =
state
.proposal_definition(proposal_id)
.await?
.ok_or_else(|| {
anyhow::anyhow!("proposal {} does not exist", proposal_id)
})?;
state.record_proto(event::proposal_slashed(&proposal));
}
}
outcome.into()
}
ProposalState::Withdrawn { reason } => {
tracing::info!(proposal = %proposal_id, reason = ?reason, "proposal concluded after being withdrawn");
ProposalOutcome::Failed {
withdrawn: ProposalWithdrawn::WithReason { reason },
}
}
ProposalState::Finished { outcome: _ } => {
anyhow::bail!("proposal {proposal_id} is already finished, and should have been removed from the active set");
}
ProposalState::Claimed { outcome: _ } => {
anyhow::bail!("proposal {proposal_id} is already claimed, and should have been removed from the active set");
}
};
state.put_proposal_state(proposal_id, ProposalState::Finished { outcome });
}
Ok(())
}