From 36f760431fd08f4a40a2ba26623407bcd949efcc Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Tue, 27 Feb 2024 14:18:43 -0500 Subject: [PATCH] =?UTF-8?q?mock-consensus:=20=F0=9F=8C=B1=20single=20genes?= =?UTF-8?q?is=20validator=20(work-in-progress)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 7 ++ crates/core/app/src/app/mod.rs | 1 + crates/core/app/tests/common/mod.rs | 78 +++++++++++++++---- crates/core/component/stake/Cargo.toml | 1 + .../component/stake/src/component/stake.rs | 20 +++-- crates/test/mock-consensus/Cargo.toml | 6 ++ crates/test/mock-consensus/src/builder.rs | 44 +++++++++-- .../mock-consensus/src/builder/init_chain.rs | 1 + 8 files changed, 131 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 298d2177c5..0cf86496b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5273,6 +5273,12 @@ version = "0.68.0" dependencies = [ "anyhow", "bytes", + "decaf377-rdsa", + "ed25519-consensus", + "penumbra-keys", + "penumbra-proto", + "rand 0.8.5", + "rand_core 0.6.4", "serde_json", "tap", "tendermint", @@ -5532,6 +5538,7 @@ dependencies = [ "serde_unit_struct", "serde_with", "sha2 0.10.8", + "tap", "tendermint", "tokio", "tonic", diff --git a/crates/core/app/src/app/mod.rs b/crates/core/app/src/app/mod.rs index 94771dbd47..e8c8bc74a8 100644 --- a/crates/core/app/src/app/mod.rs +++ b/crates/core/app/src/app/mod.rs @@ -56,6 +56,7 @@ pub struct App { } impl App { + /// Constructs a new application, using the provided [`Snapshot`]. pub async fn new(snapshot: Snapshot) -> Result { tracing::debug!("initializing App instance"); diff --git a/crates/core/app/tests/common/mod.rs b/crates/core/app/tests/common/mod.rs index f400c349fb..91798d7d16 100644 --- a/crates/core/app/tests/common/mod.rs +++ b/crates/core/app/tests/common/mod.rs @@ -3,15 +3,19 @@ // NB: Allow dead code, these are in fact shared by files in `tests/`. #![allow(dead_code)] -use async_trait::async_trait; -use cnidarium::TempStorage; -use penumbra_app::{ - app::App, - server::consensus::{Consensus, ConsensusService}, +use { + async_trait::async_trait, + cnidarium::TempStorage, + penumbra_app::{ + app::App, + server::consensus::{Consensus, ConsensusService}, + }, + penumbra_genesis::AppState, + penumbra_mock_consensus::TestNode, + std::ops::Deref, + tap::Tap, + tracing::{trace, warn}, }; -use penumbra_genesis::AppState; -use penumbra_mock_consensus::TestNode; -use std::ops::Deref; // Installs a tracing subscriber to log events until the returned guard is dropped. pub fn set_tracing_subscriber() -> tracing::subscriber::DefaultGuard { @@ -83,19 +87,63 @@ impl TempStorageExt for TempStorage { /// Penumbra-specific extensions to the mock consensus builder. pub trait BuilderExt: Sized { + /// The error thrown by [`with_penumbra_auto_app_state`] type Error; + /// Add the provided Penumbra [`AppState`] to the builder. + /// + /// This will inject any configured validators into the state before serializing it into bytes. fn with_penumbra_auto_app_state(self, app_state: AppState) -> Result; } impl BuilderExt for penumbra_mock_consensus::builder::Builder { type Error = anyhow::Error; fn with_penumbra_auto_app_state(self, app_state: AppState) -> Result { - // what to do here? - // - read out list of abci/comet validators from the builder, - // - define a penumbra validator for each one - // - inject that into the penumbra app state - // - serialize to json and then call `with_app_state_bytes` - let app_state = serde_json::to_vec(&app_state)?; - Ok(self.app_state(app_state)) + use penumbra_proto::penumbra::core::component::stake::v1 as pb; + + // Take the list of genesis validators from the builder... + let validators = self + .validators + .iter() + .inspect( + |pb::Validator { + name, + enabled, + sequence_number, + .. + }| { + // ...log the name of each... + trace!(%name, %enabled, %sequence_number, "injecting validator into app state") + }, + ) + .cloned() + .collect::>() + .tap(|v| { + // ...or print a warning if there are not any validators. + if v.is_empty() { + warn!("`with_penumbra_auto_app_state` was called but builder has no validators") + } + }); + + // Add the validators to the app state. + let app_state: AppState = match app_state { + AppState::Checkpoint(_) => anyhow::bail!("checkpoint app state isn't supported"), + AppState::Content(mut content) => { + // Inject the builder's validators into the staking component's genesis state. + std::mem::replace( + &mut content.stake_content.validators, + validators + ) + .tap(|overwritten| { + // Log a warning if this overwrote any validators already in the app state. + warn!(?overwritten, "`with_penumbra_auto_app_state` overwrote validators in the given AppState") + }); + AppState::Content(content) + } + }; + + // Serialize the app state into bytes, and add it to the builder. + serde_json::to_vec(&app_state) + .map_err(Self::Error::from) + .map(|s| self.app_state(s)) } } diff --git a/crates/core/component/stake/Cargo.toml b/crates/core/component/stake/Cargo.toml index 71b20e7a9c..e33ba6cd2c 100644 --- a/crates/core/component/stake/Cargo.toml +++ b/crates/core/component/stake/Cargo.toml @@ -73,6 +73,7 @@ serde = {workspace = true, features = ["derive"]} serde_unit_struct = {workspace = true} serde_with = {workspace = true} sha2 = {workspace = true} +tap = {workspace = true} tendermint = {workspace = true, default-features = true} tokio = {workspace = true, features = ["full", "tracing"], optional = true} tonic = {workspace = true, optional = true} diff --git a/crates/core/component/stake/src/component/stake.rs b/crates/core/component/stake/src/component/stake.rs index 94815e67de..798bd268ef 100644 --- a/crates/core/component/stake/src/component/stake.rs +++ b/crates/core/component/stake/src/component/stake.rs @@ -19,10 +19,11 @@ use sha2::{Digest, Sha256}; use std::pin::Pin; use std::str::FromStr; use std::{collections::BTreeMap, sync::Arc}; +use tap::Tap; use tendermint::v0_37::abci; use tendermint::validator::Update; use tendermint::{block, PublicKey}; -use tracing::instrument; +use tracing::{instrument, trace}; use crate::component::epoch_handler::EpochHandler; use crate::component::validator_handler::{ValidatorDataRead, ValidatorManager}; @@ -48,11 +49,13 @@ impl Component for Staking { let starting_height = state .get_block_height() .await - .expect("should be able to get initial block height"); + .expect("should be able to get initial block height") + .tap(|height| trace!(%height,"found initial block height")); let starting_epoch = state .get_epoch_by_height(starting_height) .await - .expect("should be able to get initial epoch"); + .expect("should be able to get initial epoch") + .tap(|epoch| trace!(?epoch, "found initial epoch")); let epoch_index = starting_epoch.index; let genesis_base_rate = BaseRateData { @@ -61,6 +64,7 @@ impl Component for Staking { base_exchange_rate: 1_0000_0000u128.into(), }; state.set_base_rate(genesis_base_rate.clone()); + trace!(?genesis_base_rate, "set base rate"); let mut genesis_allocations = BTreeMap::<_, Amount>::new(); for allocation in &sp_genesis.allocations { @@ -68,10 +72,14 @@ impl Component for Staking { *genesis_allocations.entry(value.asset_id).or_default() += value.amount; } - for validator in &staking_genesis.validators { + trace!("processing genesis validators"); + for (i, validator) in staking_genesis.validators.iter().enumerate() { // Parse the proto into a domain type. - let validator = Validator::try_from(validator.clone()) - .expect("should be able to parse genesis validator"); + let validator: Validator = Validator::try_from(validator.clone()) + .expect("should be able to parse genesis validator") + .tap(|Validator { name, enabled, .. }| + trace!(%i, %name, %enabled, "parsed genesis validator") + ); state .add_genesis_validator(&genesis_allocations, &genesis_base_rate, validator) diff --git a/crates/test/mock-consensus/Cargo.toml b/crates/test/mock-consensus/Cargo.toml index 5bbdd37521..d471fdefeb 100644 --- a/crates/test/mock-consensus/Cargo.toml +++ b/crates/test/mock-consensus/Cargo.toml @@ -10,6 +10,12 @@ license.workspace = true [dependencies] anyhow = { workspace = true } bytes = { workspace = true } +decaf377-rdsa = { workspace = true } +ed25519-consensus = { workspace = true } +penumbra-proto = { workspace = true } +penumbra-keys = { workspace = true } +rand = {workspace = true} +rand_core = {workspace = true} serde_json = { workspace = true } tap = { workspace = true } tendermint = { workspace = true } diff --git a/crates/test/mock-consensus/src/builder.rs b/crates/test/mock-consensus/src/builder.rs index 7d90e11c1f..8dfd11a189 100644 --- a/crates/test/mock-consensus/src/builder.rs +++ b/crates/test/mock-consensus/src/builder.rs @@ -5,12 +5,16 @@ /// Most importantly, defines [`Builder::init_chain()`]. mod init_chain; -use {crate::TestNode, bytes::Bytes}; +use { + crate::TestNode, bytes::Bytes, penumbra_proto::penumbra::core::component::stake::v1 as pb, + rand_core::OsRng, +}; /// A buider, used to prepare and instantiate a new [`TestNode`]. #[derive(Default)] pub struct Builder { - app_state: Option, + pub app_state: Option, + pub validators: Vec, } impl TestNode<()> { @@ -21,12 +25,40 @@ impl TestNode<()> { } impl Builder { - // TODO: add other convenience methods for validator config? - /// Creates a single validator with a randomly generated key. pub fn single_validator(self) -> Self { - // this does not do anything yet - self + // XXX ------------------------------------------------------------------- + use decaf377_rdsa::{SigningKey, SpendAuth, VerificationKey}; + use penumbra_keys::keys::{SpendKey, SpendKeyBytes}; + use penumbra_proto::core::keys::v1::IdentityKey; + use rand::Rng; + + // Generate an identity key for the validator. + let identity_key = { + let seed = SpendKeyBytes(OsRng.gen()); + let spend_key = SpendKey::from(seed); + let verification_key: VerificationKey = spend_key.spend_auth_key().into(); + let ik = verification_key.to_bytes().to_vec(); + Some(penumbra_proto::penumbra::core::keys::v1::IdentityKey { ik }) + }; + // XXX ------------------------------------------------------------------- + + let validator = pb::Validator { + identity_key, + consensus_key: Vec::default(), + name: String::default(), + website: String::default(), + description: String::default(), + enabled: true, + funding_streams: Vec::default(), + sequence_number: 0, + governance_key: Option::default(), + }; + + Self { + validators: vec![validator], + ..self + } } /// Sets the `app_state_bytes` to send the ABCI application upon chain initialization. diff --git a/crates/test/mock-consensus/src/builder/init_chain.rs b/crates/test/mock-consensus/src/builder/init_chain.rs index b60a7793fd..93bbc23e97 100644 --- a/crates/test/mock-consensus/src/builder/init_chain.rs +++ b/crates/test/mock-consensus/src/builder/init_chain.rs @@ -32,6 +32,7 @@ impl Builder { let Self { app_state: Some(app_state), + validators: _, } = self else { bail!("builder was not fully initialized")