From f3ecf6d5806e9b99de9fa98e0e0b85076840bef0 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Fri, 5 Apr 2024 11:04:11 -0400 Subject: [PATCH 1/3] =?UTF-8?q?tests(app):=20=F0=9F=94=B6=20add=20genesis?= =?UTF-8?q?=20content=20setters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this is some common glue code in integration tests for the staking component. in order to smooth the inspection of component logic that runs at epoch boundaries, the defaults for `AppState`'s inner settings for e.g. epoch duration are overriden to shorter values. as is, this requres stating object literals that are somewhat verbose, even with `..T::default()` elision. this commit adds some small `with_` helpers to allow test cases to manipulate these settings succinctly. --- crates/core/app/src/genesis.rs | 24 ++++++++++++++++ ..._can_define_and_delegate_to_a_validator.rs | 15 ++++------ .../app_can_undelegate_from_a_validator.rs | 28 +++++++------------ ..._uptime_for_validators_only_once_active.rs | 15 ++++------ 4 files changed, 46 insertions(+), 36 deletions(-) diff --git a/crates/core/app/src/genesis.rs b/crates/core/app/src/genesis.rs index 85380713cb..942cd80331 100644 --- a/crates/core/app/src/genesis.rs +++ b/crates/core/app/src/genesis.rs @@ -164,6 +164,30 @@ impl DomainType for AppState { type Proto = pb::GenesisAppState; } +impl Content { + pub fn with_epoch_duration(self, epoch_duration: u64) -> Self { + Self { + sct_content: penumbra_sct::genesis::Content { + sct_params: penumbra_sct::params::SctParameters { epoch_duration }, + }, + ..self + } + } + + pub fn with_unbonding_delay(self, unbonding_delay: u64) -> Self { + Self { + stake_content: penumbra_stake::genesis::Content { + stake_params: penumbra_stake::params::StakeParameters { + unbonding_delay, + ..self.stake_content.stake_params + }, + ..self.stake_content + }, + ..self + } + } +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/core/app/tests/app_can_define_and_delegate_to_a_validator.rs b/crates/core/app/tests/app_can_define_and_delegate_to_a_validator.rs index 4d59dfab20..e4996bf7b0 100644 --- a/crates/core/app/tests/app_can_define_and_delegate_to_a_validator.rs +++ b/crates/core/app/tests/app_can_define_and_delegate_to_a_validator.rs @@ -5,7 +5,10 @@ use { anyhow::{anyhow, Context}, cnidarium::TempStorage, decaf377_rdsa::{SigningKey, SpendAuth, VerificationKey}, - penumbra_app::{genesis::AppState, server::consensus::Consensus}, + penumbra_app::{ + genesis::{self, AppState}, + server::consensus::Consensus, + }, penumbra_keys::test_keys, penumbra_mock_client::MockClient, penumbra_mock_consensus::TestNode, @@ -31,14 +34,8 @@ async fn app_can_define_and_delegate_to_a_validator() -> anyhow::Result<()> { let storage = TempStorage::new().await?; // Configure an AppState with slightly shorter epochs than usual. - let app_state = AppState::Content(penumbra_app::genesis::Content { - sct_content: penumbra_sct::genesis::Content { - sct_params: penumbra_sct::params::SctParameters { - epoch_duration: EPOCH_DURATION, - }, - }, - ..Default::default() - }); + let app_state = + AppState::Content(genesis::Content::default().with_epoch_duration(EPOCH_DURATION)); // Start the test node. let mut node = { diff --git a/crates/core/app/tests/app_can_undelegate_from_a_validator.rs b/crates/core/app/tests/app_can_undelegate_from_a_validator.rs index de61fd1132..b4f8beb3e9 100644 --- a/crates/core/app/tests/app_can_undelegate_from_a_validator.rs +++ b/crates/core/app/tests/app_can_undelegate_from_a_validator.rs @@ -1,5 +1,3 @@ -use penumbra_num::fixpoint::U128x128; - mod common; use { @@ -7,10 +5,14 @@ use { anyhow::anyhow, ark_ff::UniformRand, cnidarium::TempStorage, - penumbra_app::{genesis::AppState, server::consensus::Consensus}, + penumbra_app::{ + genesis::{self, AppState}, + server::consensus::Consensus, + }, penumbra_keys::test_keys, penumbra_mock_client::MockClient, penumbra_mock_consensus::TestNode, + penumbra_num::fixpoint::U128x128, penumbra_proto::DomainType, penumbra_sct::component::clock::EpochRead as _, penumbra_stake::{ @@ -58,21 +60,11 @@ async fn app_can_undelegate_from_a_validator() -> anyhow::Result<()> { }; // Configure an AppState with slightly shorter epochs than usual. - let app_state = AppState::Content(penumbra_app::genesis::Content { - sct_content: penumbra_sct::genesis::Content { - sct_params: penumbra_sct::params::SctParameters { - epoch_duration: EPOCH_DURATION, - }, - }, - stake_content: penumbra_stake::genesis::Content { - stake_params: penumbra_stake::params::StakeParameters { - unbonding_delay: UNBONDING_DELAY, - ..Default::default() - }, - ..Default::default() - }, - ..Default::default() - }); + let app_state = AppState::Content( + genesis::Content::default() + .with_epoch_duration(EPOCH_DURATION) + .with_unbonding_delay(UNBONDING_DELAY), + ); // Start the test node. let mut node = { diff --git a/crates/core/app/tests/app_tracks_uptime_for_validators_only_once_active.rs b/crates/core/app/tests/app_tracks_uptime_for_validators_only_once_active.rs index de687e553c..c72bd5f54f 100644 --- a/crates/core/app/tests/app_tracks_uptime_for_validators_only_once_active.rs +++ b/crates/core/app/tests/app_tracks_uptime_for_validators_only_once_active.rs @@ -4,7 +4,10 @@ use { self::common::BuilderExt, cnidarium::TempStorage, decaf377_rdsa::{SigningKey, SpendAuth, VerificationKey}, - penumbra_app::{genesis::AppState, server::consensus::Consensus}, + penumbra_app::{ + genesis::{self, AppState}, + server::consensus::Consensus, + }, penumbra_keys::test_keys, penumbra_mock_client::MockClient, penumbra_mock_consensus::TestNode, @@ -31,14 +34,8 @@ async fn app_tracks_uptime_for_validators_only_once_active() -> anyhow::Result<( let storage = TempStorage::new().await?; // Configure an AppState with slightly shorter epochs than usual. - let app_state = AppState::Content(penumbra_app::genesis::Content { - sct_content: penumbra_sct::genesis::Content { - sct_params: penumbra_sct::params::SctParameters { - epoch_duration: EPOCH_DURATION, - }, - }, - ..Default::default() - }); + let app_state = + AppState::Content(genesis::Content::default().with_epoch_duration(EPOCH_DURATION)); // Start the test node. let mut node = { From bf8f4d14253878a38bdd1589fb15d414f4745094 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Mon, 8 Apr 2024 09:34:06 -0400 Subject: [PATCH 2/3] =?UTF-8?q?tests(app):=20=F0=9F=8F=83=20add=20fast=20f?= =?UTF-8?q?orward=20to=20next=20epoch=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this adds an extension method that can be used to fast forward to the next epoch. this is a very common part of test logic that exercises the staking component. this allows test cases to invoke an extension method to jump ahead, without needing to import the clock component and write a loop inspecting the current epoch after creating and executing an empty block. --- ..._can_define_and_delegate_to_a_validator.rs | 27 +++------- .../app_can_undelegate_from_a_validator.rs | 13 ++--- ..._uptime_for_validators_only_once_active.rs | 51 +++++-------------- crates/core/app/tests/common/mod.rs | 43 ++++++++++++++++ 4 files changed, 66 insertions(+), 68 deletions(-) diff --git a/crates/core/app/tests/app_can_define_and_delegate_to_a_validator.rs b/crates/core/app/tests/app_can_define_and_delegate_to_a_validator.rs index e4996bf7b0..fa44dc55af 100644 --- a/crates/core/app/tests/app_can_define_and_delegate_to_a_validator.rs +++ b/crates/core/app/tests/app_can_define_and_delegate_to_a_validator.rs @@ -1,8 +1,6 @@ -mod common; - use { - self::common::BuilderExt, - anyhow::{anyhow, Context}, + self::common::{BuilderExt, TestNodeExt}, + anyhow::anyhow, cnidarium::TempStorage, decaf377_rdsa::{SigningKey, SpendAuth, VerificationKey}, penumbra_app::{ @@ -22,6 +20,8 @@ use { tracing::{error_span, info, Instrument}, }; +mod common; + /// The length of the [`penumbra_sct`] epoch. /// /// This test relies on many epochs turning over, so we will work with a shorter epoch duration. @@ -55,10 +55,7 @@ async fn app_can_define_and_delegate_to_a_validator() -> anyhow::Result<()> { // Fast forward to the next epoch. let snapshot_start = storage.latest_snapshot(); - node.fast_forward(EPOCH_DURATION) - .instrument(error_span!("fast forwarding test node to second epoch")) - .await - .context("fast forwarding {EPOCH_LENGTH} blocks")?; + node.fast_forward_to_next_epoch(&storage).await?; let snapshot_end = storage.latest_snapshot(); // Retrieve the validator definition from the latest snapshot. @@ -337,12 +334,7 @@ async fn app_can_define_and_delegate_to_a_validator() -> anyhow::Result<()> { }; // Fast forward to the next epoch. - node.fast_forward(EPOCH_DURATION) - .instrument(error_span!( - "fast forwarding test node to epoch after delegation" - )) - .await - .context("fast forwarding {EPOCH_LENGTH} blocks")?; + node.fast_forward_to_next_epoch(&storage).await?; let post_delegate_next_epoch_snapshot = storage.latest_snapshot(); // Show that now, after an epoch and with a delegation, the validator is marked active. @@ -488,12 +480,7 @@ async fn app_can_define_and_delegate_to_a_validator() -> anyhow::Result<()> { }); // Fast forward to the next epoch. - node.fast_forward(EPOCH_DURATION) - .instrument(error_span!( - "fast forwarding test node to epoch after undelegation" - )) - .await - .context("fast forwarding {EPOCH_LENGTH} blocks")?; + node.fast_forward_to_next_epoch(&storage).await?; let post_undelegate_next_epoch_snapshot = storage.latest_snapshot(); // Show that after undelegating, the validator is no longer marked active. diff --git a/crates/core/app/tests/app_can_undelegate_from_a_validator.rs b/crates/core/app/tests/app_can_undelegate_from_a_validator.rs index b4f8beb3e9..a7f972b6e4 100644 --- a/crates/core/app/tests/app_can_undelegate_from_a_validator.rs +++ b/crates/core/app/tests/app_can_undelegate_from_a_validator.rs @@ -1,7 +1,5 @@ -mod common; - use { - self::common::BuilderExt, + self::common::{BuilderExt, TestNodeExt}, anyhow::anyhow, ark_ff::UniformRand, cnidarium::TempStorage, @@ -27,6 +25,8 @@ use { tracing::{error_span, info, Instrument}, }; +mod common; + /// The length of the [`penumbra_sct`] epoch. /// /// This test relies on many epochs turning over, so we will work with a shorter epoch duration. @@ -195,12 +195,7 @@ async fn app_can_undelegate_from_a_validator() -> anyhow::Result<()> { } // Fast forward to the next epoch. - { - let start = get_latest_epoch().await.index; - while get_latest_epoch().await.index < start { - node.block().execute().await?; - } - } + node.fast_forward_to_next_epoch(&storage).await?; // Build a transaction that will now undelegate from the validator. let undelegate_rate = storage diff --git a/crates/core/app/tests/app_tracks_uptime_for_validators_only_once_active.rs b/crates/core/app/tests/app_tracks_uptime_for_validators_only_once_active.rs index c72bd5f54f..6418dc2220 100644 --- a/crates/core/app/tests/app_tracks_uptime_for_validators_only_once_active.rs +++ b/crates/core/app/tests/app_tracks_uptime_for_validators_only_once_active.rs @@ -1,7 +1,5 @@ -mod common; - use { - self::common::BuilderExt, + self::common::{BuilderExt, TestNodeExt}, cnidarium::TempStorage, decaf377_rdsa::{SigningKey, SpendAuth, VerificationKey}, penumbra_app::{ @@ -22,6 +20,8 @@ use { tracing::{error_span, info, Instrument}, }; +mod common; + #[tokio::test] async fn app_tracks_uptime_for_validators_only_once_active() -> anyhow::Result<()> { /// The length of the [`penumbra_sct`] epoch. @@ -234,19 +234,10 @@ async fn app_tracks_uptime_for_validators_only_once_active() -> anyhow::Result<( } // Fast forward to the next epoch. + node.fast_forward_to_next_epoch(&storage).await?; + + // The new validator should now be in the consensus set. { - let get_epoch = || async { storage.latest_snapshot().get_current_epoch().await }; - let start = get_epoch() - .await? - .tap(|start| tracing::info!(?start, "fast forwarding to next epoch")); - let next = loop { - node.block().execute().await?; - let current = get_epoch().await?; - if current != start { - break current; - } - }; - tracing::info!(?start, ?next, "finished fast forwarding to next epoch"); assert_eq!( get_latest_consensus_set().await.len(), 2, @@ -363,19 +354,10 @@ async fn app_tracks_uptime_for_validators_only_once_active() -> anyhow::Result<( ); // Fast forward to the next epoch. + node.fast_forward_to_next_epoch(&storage).await?; + + // The validator should no longer be part of the consensus set. { - let get_epoch = || async { storage.latest_snapshot().get_current_epoch().await }; - let start = get_epoch() - .await? - .tap(|start| tracing::info!(?start, "fast forwarding to next epoch")); - let next = loop { - node.block().execute().await?; - let current = get_epoch().await?; - if current != start { - break current; - } - }; - tracing::info!(?start, ?next, "finished fast forwarding to next epoch"); assert_eq!( get_latest_consensus_set().await.len(), 1, @@ -403,19 +385,10 @@ async fn app_tracks_uptime_for_validators_only_once_active() -> anyhow::Result<( } // Fast forward to the next epoch. + node.fast_forward_to_next_epoch(&storage).await?; + + // There should only be one validator in the consensus set. { - let get_epoch = || async { storage.latest_snapshot().get_current_epoch().await }; - let start = get_epoch() - .await? - .tap(|start| tracing::info!(?start, "fast forwarding to next epoch")); - let next = loop { - node.block().execute().await?; - let current = get_epoch().await?; - if current != start { - break current; - } - }; - tracing::info!(?start, ?next, "finished fast forwarding to next epoch"); assert_eq!( get_latest_consensus_set().await.len(), 1, diff --git a/crates/core/app/tests/common/mod.rs b/crates/core/app/tests/common/mod.rs index 38f9af1d74..043d3d39f0 100644 --- a/crates/core/app/tests/common/mod.rs +++ b/crates/core/app/tests/common/mod.rs @@ -89,3 +89,46 @@ impl TempStorageExt for TempStorage { self.apply_genesis(Default::default()).await } } + +#[async_trait] +pub trait TestNodeExt: Sized { + async fn fast_forward_to_next_epoch( + &mut self, + storage: &TempStorage, + ) -> anyhow::Result; +} + +#[async_trait] +impl TestNodeExt for TestNode +where + C: tower::Service< + tendermint::v0_37::abci::ConsensusRequest, + Response = tendermint::v0_37::abci::ConsensusResponse, + Error = tower::BoxError, + > + Send + + Clone + + 'static, + C::Future: Send + 'static, + C::Error: Sized, +{ + async fn fast_forward_to_next_epoch( + &mut self, + storage: &TempStorage, + ) -> Result { + use {penumbra_sct::component::clock::EpochRead, tap::Tap}; + + let get_epoch = || async { storage.latest_snapshot().get_current_epoch().await }; + let start = get_epoch() + .await? + .tap(|start| tracing::info!(?start, "fast forwarding to next epoch")); + + loop { + self.block().execute().await?; + let current = get_epoch().await?; + if current != start { + tracing::debug!(end = ?current, ?start, "reached next epoch"); + return Ok(current); + } + } + } +} From 1c5b90a2395c0518b91a1fb8776a4f64ffd320a7 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Mon, 8 Apr 2024 11:11:55 -0400 Subject: [PATCH 3/3] =?UTF-8?q?tests(app):=20=F0=9F=94=91=20use=20`validat?= =?UTF-8?q?or=5Fidentity=5Fkeys`=20accessor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this adds a `ValidatorDataRead::validator_identity_keys()` trait method. frequently, test logic needs to keep track of validators by their identity keys. this led to a bit of common glue code that would get the latest snapshot, the set of all validator definitions, assert that only one validator is present, and then take the identity key of that single validator. now we can just get the keys, bind that single entry to a variable, or return an error if something went wrong. --- ..._can_define_and_delegate_to_a_validator.rs | 15 +++++-------- .../app_can_undelegate_from_a_validator.rs | 17 +++++--------- ...me_for_genesis_validator_missing_blocks.rs | 18 +++++---------- ...me_for_genesis_validator_signing_blocks.rs | 22 +++++++------------ ..._uptime_for_validators_only_once_active.rs | 21 ++++++------------ .../validator_handler/validator_store.rs | 9 ++++++++ 6 files changed, 40 insertions(+), 62 deletions(-) diff --git a/crates/core/app/tests/app_can_define_and_delegate_to_a_validator.rs b/crates/core/app/tests/app_can_define_and_delegate_to_a_validator.rs index fa44dc55af..a3b26cfa52 100644 --- a/crates/core/app/tests/app_can_define_and_delegate_to_a_validator.rs +++ b/crates/core/app/tests/app_can_define_and_delegate_to_a_validator.rs @@ -59,17 +59,12 @@ async fn app_can_define_and_delegate_to_a_validator() -> anyhow::Result<()> { let snapshot_end = storage.latest_snapshot(); // Retrieve the validator definition from the latest snapshot. - let existing_validator = match snapshot_end - .validator_definitions() - .tap(|_| info!("getting validator definitions")) + let [existing_validator_id] = storage + .latest_snapshot() + .validator_identity_keys() .await? - .as_slice() - { - [v] => v.clone(), - unexpected => panic!("there should be one validator, got: {unexpected:?}"), - }; - - let existing_validator_id = existing_validator.identity_key; + .try_into() + .map_err(|keys| anyhow::anyhow!("expected one key, got: {keys:?}"))?; // Check that we are now in a new epoch. { diff --git a/crates/core/app/tests/app_can_undelegate_from_a_validator.rs b/crates/core/app/tests/app_can_undelegate_from_a_validator.rs index a7f972b6e4..1f7f2c7573 100644 --- a/crates/core/app/tests/app_can_undelegate_from_a_validator.rs +++ b/crates/core/app/tests/app_can_undelegate_from_a_validator.rs @@ -13,10 +13,7 @@ use { penumbra_num::fixpoint::U128x128, penumbra_proto::DomainType, penumbra_sct::component::clock::EpochRead as _, - penumbra_stake::{ - component::validator_handler::ValidatorDataRead as _, validator::Validator, - UndelegateClaimPlan, - }, + penumbra_stake::{component::validator_handler::ValidatorDataRead as _, UndelegateClaimPlan}, penumbra_transaction::{ memo::MemoPlaintext, plan::MemoPlan, TransactionParameters, TransactionPlan, }, @@ -77,16 +74,12 @@ async fn app_can_undelegate_from_a_validator() -> anyhow::Result<()> { }?; // Retrieve the validator definition from the latest snapshot. - let Validator { identity_key, .. } = match storage + let [identity_key] = storage .latest_snapshot() - .validator_definitions() - .tap(|_| info!("getting validator definitions")) + .validator_identity_keys() .await? - .as_slice() - { - [v] => v.clone(), - unexpected => panic!("there should be one validator, got: {unexpected:?}"), - }; // ..and note the asset id for delegation tokens tied to this validator. + .try_into() + .map_err(|keys| anyhow::anyhow!("expected one key, got: {keys:?}"))?; let delegate_token_id = penumbra_stake::DelegationToken::new(identity_key).id(); // Sync the mock client, using the test wallet's spend key, to the latest snapshot. diff --git a/crates/core/app/tests/app_tracks_uptime_for_genesis_validator_missing_blocks.rs b/crates/core/app/tests/app_tracks_uptime_for_genesis_validator_missing_blocks.rs index e5a76d2e41..126888c365 100644 --- a/crates/core/app/tests/app_tracks_uptime_for_genesis_validator_missing_blocks.rs +++ b/crates/core/app/tests/app_tracks_uptime_for_genesis_validator_missing_blocks.rs @@ -6,11 +6,9 @@ use { cnidarium::TempStorage, penumbra_app::{genesis::AppState, server::consensus::Consensus}, penumbra_mock_consensus::TestNode, - penumbra_stake::{ - component::validator_handler::validator_store::ValidatorDataRead, validator::Validator, - }, + penumbra_stake::component::validator_handler::validator_store::ValidatorDataRead, tap::Tap, - tracing::{error_span, info, trace, Instrument}, + tracing::{error_span, trace, Instrument}, }; #[tokio::test] @@ -31,16 +29,12 @@ async fn app_tracks_uptime_for_genesis_validator_missing_blocks() -> anyhow::Res }?; // Retrieve the validator definition from the latest snapshot. - let Validator { identity_key, .. } = match storage + let [identity_key] = storage .latest_snapshot() - .validator_definitions() - .tap(|_| info!("getting validator definitions")) + .validator_identity_keys() .await? - .as_slice() - { - [v] => v.clone(), - unexpected => panic!("there should be one validator, got: {unexpected:?}"), - }; + .try_into() + .map_err(|keys| anyhow::anyhow!("expected one key, got: {keys:?}"))?; let get_uptime = || async { storage .latest_snapshot() diff --git a/crates/core/app/tests/app_tracks_uptime_for_genesis_validator_signing_blocks.rs b/crates/core/app/tests/app_tracks_uptime_for_genesis_validator_signing_blocks.rs index 7d0e3ba9c4..8c9be9a2d2 100644 --- a/crates/core/app/tests/app_tracks_uptime_for_genesis_validator_signing_blocks.rs +++ b/crates/core/app/tests/app_tracks_uptime_for_genesis_validator_signing_blocks.rs @@ -1,18 +1,16 @@ -mod common; - use { self::common::BuilderExt, anyhow::Context, cnidarium::TempStorage, penumbra_app::{genesis::AppState, server::consensus::Consensus}, penumbra_mock_consensus::TestNode, - penumbra_stake::{ - component::validator_handler::validator_store::ValidatorDataRead, validator::Validator, - }, + penumbra_stake::component::validator_handler::validator_store::ValidatorDataRead, tap::Tap, - tracing::{error_span, info, Instrument}, + tracing::{error_span, Instrument}, }; +mod common; + #[tokio::test] async fn app_tracks_uptime_for_genesis_validator_missing_blocks() -> anyhow::Result<()> { // Install a test logger, acquire some temporary storage, and start the test node. @@ -31,16 +29,12 @@ async fn app_tracks_uptime_for_genesis_validator_missing_blocks() -> anyhow::Res }?; // Retrieve the validator definition from the latest snapshot. - let Validator { identity_key, .. } = match storage + let [identity_key] = storage .latest_snapshot() - .validator_definitions() - .tap(|_| info!("getting validator definitions")) + .validator_identity_keys() .await? - .as_slice() - { - [v] => v.clone(), - unexpected => panic!("there should be one validator, got: {unexpected:?}"), - }; + .try_into() + .map_err(|keys| anyhow::anyhow!("expected one key, got: {keys:?}"))?; let get_uptime = || async { storage .latest_snapshot() diff --git a/crates/core/app/tests/app_tracks_uptime_for_validators_only_once_active.rs b/crates/core/app/tests/app_tracks_uptime_for_validators_only_once_active.rs index 6418dc2220..8a4553e786 100644 --- a/crates/core/app/tests/app_tracks_uptime_for_validators_only_once_active.rs +++ b/crates/core/app/tests/app_tracks_uptime_for_validators_only_once_active.rs @@ -17,7 +17,7 @@ use { }, rand_core::OsRng, tap::Tap, - tracing::{error_span, info, Instrument}, + tracing::{error_span, Instrument}, }; mod common; @@ -70,19 +70,12 @@ async fn app_tracks_uptime_for_validators_only_once_active() -> anyhow::Result<( }; // Get the identity key of the genesis validator, before we go further. - // Retrieve the validator definition from the latest snapshot. - let existing_validator_id = { - use penumbra_stake::component::validator_handler::validator_store::ValidatorDataRead; - let validators = &storage - .latest_snapshot() - .validator_definitions() - .tap(|_| info!("getting validator definitions")) - .await?; - match validators.as_slice() { - [Validator { identity_key, .. }] => *identity_key, - unexpected => panic!("there should be one validator, got: {unexpected:?}"), - } - }; + let [existing_validator_id] = storage + .latest_snapshot() + .validator_identity_keys() + .await? + .try_into() + .map_err(|keys| anyhow::anyhow!("expected one key, got: {keys:?}"))?; // To define a validator, we need to define two keypairs: an identity key // for the Penumbra application and a consensus key for cometbft. diff --git a/crates/core/component/stake/src/component/validator_handler/validator_store.rs b/crates/core/component/stake/src/component/validator_handler/validator_store.rs index 89abf01686..3f30895d12 100644 --- a/crates/core/component/stake/src/component/validator_handler/validator_store.rs +++ b/crates/core/component/stake/src/component/validator_handler/validator_store.rs @@ -235,6 +235,15 @@ pub trait ValidatorDataRead: StateRead { .try_collect() .await } + + /// Returns a list of **all** known validators identity keys. + async fn validator_identity_keys(&self) -> Result> { + self.prefix(state_key::validators::definitions::prefix()) + .map_ok(|(_key, validator)| validator) + .map_ok(|validator: Validator| validator.identity_key) + .try_collect() + .await + } } impl ValidatorDataRead for T {}