Skip to content

Commit

Permalink
mock-consensus: 🌱 single genesis validator (work-in-progress)
Browse files Browse the repository at this point in the history
  • Loading branch information
cratelyn committed Feb 28, 2024
1 parent 628a9b6 commit 36f7604
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 27 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/core/app/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ pub struct App {
}

impl App {
/// Constructs a new application, using the provided [`Snapshot`].
pub async fn new(snapshot: Snapshot) -> Result<Self> {
tracing::debug!("initializing App instance");

Expand Down
78 changes: 63 additions & 15 deletions crates/core/app/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Self, Self::Error>;
}

impl BuilderExt for penumbra_mock_consensus::builder::Builder {
type Error = anyhow::Error;
fn with_penumbra_auto_app_state(self, app_state: AppState) -> Result<Self, Self::Error> {
// 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::<Vec<_>>()
.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))
}
}
1 change: 1 addition & 0 deletions crates/core/component/stake/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
20 changes: 14 additions & 6 deletions crates/core/component/stake/src/component/stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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 {
Expand All @@ -61,17 +64,22 @@ 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 {
let value = allocation.value();
*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)
Expand Down
6 changes: 6 additions & 0 deletions crates/test/mock-consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
44 changes: 38 additions & 6 deletions crates/test/mock-consensus/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Bytes>,
pub app_state: Option<Bytes>,
pub validators: Vec<pb::Validator>,
}

impl TestNode<()> {
Expand All @@ -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<SpendAuth> = 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.
Expand Down
1 change: 1 addition & 0 deletions crates/test/mock-consensus/src/builder/init_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ impl Builder {

let Self {
app_state: Some(app_state),
validators: _,
} = self
else {
bail!("builder was not fully initialized")
Expand Down

0 comments on commit 36f7604

Please sign in to comment.