From 3f6b5b6ce67ee26dc96a2cddd9f43c9a826f9d23 Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Mon, 7 Oct 2024 17:09:47 -0700 Subject: [PATCH] pindexer: add a nicer error when a checkpoint genesis is used This adds an informative error explaining that we need the *original* genesis file, before any upgrades, and why. Closes #4880 --- crates/bin/pindexer/src/lib.rs | 1 + crates/bin/pindexer/src/parsing.rs | 27 +++++++++++++++++++ .../bin/pindexer/src/stake/validator_set.rs | 20 +++++--------- crates/bin/pindexer/src/supply.rs | 16 +++++------ 4 files changed, 41 insertions(+), 23 deletions(-) create mode 100644 crates/bin/pindexer/src/parsing.rs diff --git a/crates/bin/pindexer/src/lib.rs b/crates/bin/pindexer/src/lib.rs index 42e319d134..353aaaf0b2 100644 --- a/crates/bin/pindexer/src/lib.rs +++ b/crates/bin/pindexer/src/lib.rs @@ -5,6 +5,7 @@ pub use indexer_ext::IndexerExt; pub mod block; pub mod dex; pub mod ibc; +mod parsing; pub mod shielded_pool; mod sql; pub mod stake; diff --git a/crates/bin/pindexer/src/parsing.rs b/crates/bin/pindexer/src/parsing.rs new file mode 100644 index 0000000000..51bd104059 --- /dev/null +++ b/crates/bin/pindexer/src/parsing.rs @@ -0,0 +1,27 @@ +use anyhow::{anyhow, Context as _}; +use penumbra_app::genesis::{AppState, Content}; +use serde_json::Value; + +const GENESIS_NO_CONTENT_ERROR: &'static str = r#" +Error: using an upgrade genesis file instead of an initial genesis file. + +This genesis file only contains a checkpoint hash of the state, +rather than information about how the initial state of the chain was initialized, +at the very first genesis. + +Make sure that you're using the very first genesis file, before any upgrades. +"#; + +/// Attempt to parse content from a value. +/// +/// This is useful to get the initial chain state for app views. +/// +/// This has a nice error message, so you should use this. +pub fn parse_content(data: Value) -> anyhow::Result { + let app_state: AppState = serde_json::from_value(data) + .context("error decoding app_state json: make sure that this is a penumbra genesis file")?; + let content = app_state + .content() + .ok_or(anyhow!(GENESIS_NO_CONTENT_ERROR))?; + Ok(content.clone()) +} diff --git a/crates/bin/pindexer/src/stake/validator_set.rs b/crates/bin/pindexer/src/stake/validator_set.rs index fd0abf344c..bc4dc59a18 100644 --- a/crates/bin/pindexer/src/stake/validator_set.rs +++ b/crates/bin/pindexer/src/stake/validator_set.rs @@ -1,9 +1,9 @@ use std::collections::BTreeMap; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; use cometindex::{async_trait, sqlx, AppView, ContextualizedEvent, PgPool, PgTransaction}; -use penumbra_app::genesis::AppState; +use penumbra_app::genesis::Content; use penumbra_asset::asset; use penumbra_num::Amount; use penumbra_proto::{core::component::stake::v1 as pb, event::ProtoEvent}; @@ -12,6 +12,8 @@ use penumbra_stake::{ IdentityKey, }; +use crate::parsing::parse_content; + #[derive(Debug)] pub struct ValidatorSet {} @@ -45,10 +47,7 @@ impl AppView for ValidatorSet { .execute(dbtx.as_mut()) .await?; - let app_state: penumbra_app::genesis::AppState = - serde_json::from_value(app_state.clone()).context("error decoding app_state json")?; - - add_genesis_validators(dbtx, &app_state).await?; + add_genesis_validators(dbtx, &parse_content(app_state.clone())?).await?; Ok(()) } @@ -147,14 +146,7 @@ impl AppView for ValidatorSet { } } -async fn add_genesis_validators<'a>( - dbtx: &mut PgTransaction<'a>, - app_state: &AppState, -) -> Result<()> { - let content = app_state - .content() - .ok_or_else(|| anyhow::anyhow!("cannot initialize indexer from checkpoint genesis"))?; - +async fn add_genesis_validators<'a>(dbtx: &mut PgTransaction<'a>, content: &Content) -> Result<()> { // Given a genesis validator, we need to figure out its delegations at // genesis by getting its delegation token then summing up all the allocations. // Build up a table of the total allocations first. diff --git a/crates/bin/pindexer/src/supply.rs b/crates/bin/pindexer/src/supply.rs index ff51f7adb7..35c7545890 100644 --- a/crates/bin/pindexer/src/supply.rs +++ b/crates/bin/pindexer/src/supply.rs @@ -1,8 +1,8 @@ use std::collections::{BTreeMap, HashSet}; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; use cometindex::{async_trait, sqlx, AppView, ContextualizedEvent, PgTransaction}; -use penumbra_app::genesis::{AppState, Content}; +use penumbra_app::genesis::Content; use penumbra_asset::{asset, STAKING_TOKEN_ASSET_ID}; use penumbra_num::Amount; use penumbra_proto::{ @@ -16,6 +16,8 @@ use penumbra_stake::{rate::RateData, validator::Validator, IdentityKey}; use sqlx::{PgPool, Postgres, Transaction}; use std::iter; +use crate::parsing::parse_content; + mod unstaked_supply { //! This module handles updates around the unstaked supply. use anyhow::Result; @@ -820,7 +822,7 @@ impl<'a> TryFrom<&'a ContextualizedEvent> for Event { /// Add the initial native token supply. async fn add_genesis_native_token_allocation_supply<'a>( dbtx: &mut PgTransaction<'a>, - app_state: &AppState, + content: &Content, ) -> Result<()> { fn content_mints(content: &Content) -> BTreeMap { let community_pool_mint = iter::once(( @@ -843,9 +845,6 @@ async fn add_genesis_native_token_allocation_supply<'a>( out } - let content = app_state - .content() - .ok_or_else(|| anyhow::anyhow!("cannot initialized indexer from checkpoint genesis"))?; let mints = content_mints(content); let unstaked_mint = u64::try_from( @@ -911,9 +910,8 @@ impl AppView for Component { // decode the initial supply from the genesis // initial app state is not recomputed from events, because events are not emitted in init_chain. // instead, the indexer directly parses the genesis. - let app_state: penumbra_app::genesis::AppState = - serde_json::from_value(app_state.clone()).context("error decoding app_state json")?; - add_genesis_native_token_allocation_supply(dbtx, &app_state).await?; + add_genesis_native_token_allocation_supply(dbtx, &parse_content(app_state.clone())?) + .await?; Ok(()) }