pindexer/stake/
slashings.rs

1use anyhow::{anyhow, Result};
2use cometindex::{
3    async_trait,
4    index::{EventBatch, EventBatchContext},
5    sqlx, AppView, ContextualizedEvent, PgTransaction,
6};
7
8use penumbra_sdk_proto::{core::component::stake::v1 as pb, event::ProtoEvent};
9use penumbra_sdk_stake::IdentityKey;
10
11#[derive(Debug)]
12pub struct Slashings {}
13
14impl Slashings {
15    async fn index_event(
16        &self,
17        dbtx: &mut PgTransaction<'_>,
18        event: ContextualizedEvent<'_>,
19    ) -> Result<(), anyhow::Error> {
20        let pe = match pb::EventSlashingPenaltyApplied::from_event(event.as_ref()) {
21            Ok(pe) => pe,
22            Err(_) => return Ok(()),
23        };
24        let ik = IdentityKey::try_from(
25            pe.identity_key
26                .ok_or_else(|| anyhow!("missing ik in event"))?,
27        )?;
28
29        let height = event.block_height;
30        let epoch_index = pe.epoch_index;
31
32        let penalty_json = serde_json::to_string(
33            &pe.new_penalty
34                .ok_or_else(|| anyhow!("missing new_penalty"))?,
35        )?;
36
37        sqlx::query(
38            "INSERT INTO stake_slashings (height, ik, epoch_index, penalty) 
39             VALUES ($1, $2, $3, $4)",
40        )
41        .bind(height as i64)
42        .bind(ik.to_string())
43        .bind(epoch_index as i64)
44        .bind(penalty_json)
45        .execute(dbtx.as_mut())
46        .await?;
47
48        Ok(())
49    }
50}
51
52#[async_trait]
53impl AppView for Slashings {
54    async fn init_chain(
55        &self,
56        dbtx: &mut PgTransaction,
57        _app_state: &serde_json::Value,
58    ) -> Result<(), anyhow::Error> {
59        sqlx::query(
60            "CREATE TABLE stake_slashings (
61                id SERIAL PRIMARY KEY,
62                height BIGINT NOT NULL,
63                ik TEXT NOT NULL,
64                epoch_index BIGINT NOT NULL,
65                penalty TEXT NOT NULL
66            );",
67        )
68        .execute(dbtx.as_mut())
69        .await?;
70
71        sqlx::query("CREATE INDEX idx_stake_slashings_height ON stake_slashings(height);")
72            .execute(dbtx.as_mut())
73            .await?;
74
75        sqlx::query("CREATE INDEX idx_stake_slashings_ik ON stake_slashings(ik);")
76            .execute(dbtx.as_mut())
77            .await?;
78
79        sqlx::query("CREATE INDEX idx_stake_slashings_height_ik ON stake_slashings(height, ik);")
80            .execute(dbtx.as_mut())
81            .await?;
82
83        Ok(())
84    }
85
86    fn name(&self) -> String {
87        "stake/slashings".to_string()
88    }
89
90    async fn index_batch(
91        &self,
92        dbtx: &mut PgTransaction,
93        batch: EventBatch,
94        _ctx: EventBatchContext,
95    ) -> Result<(), anyhow::Error> {
96        for event in batch.events() {
97            self.index_event(dbtx, event).await?;
98        }
99        Ok(())
100    }
101}