Skip to content

Commit

Permalink
tests(app): 🔗 send InitChain request to test node
Browse files Browse the repository at this point in the history
Co-Authored-By: Henry de Valence <[email protected]>
  • Loading branch information
cratelyn and hdevalence committed Feb 13, 2024
1 parent cf57d1e commit c85e3ea
Show file tree
Hide file tree
Showing 13 changed files with 238 additions and 57 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -226,5 +226,5 @@ tower = {version = "0.4.0"}
tower-http = {version = "0.4"}
tower-service = {version = "0.3.2"}
tracing = {version = "0.1"}
tracing-subscriber = {version = "0.3.17"}
tracing-subscriber = {version = "0.3.17", features = ["env-filter"]}
url = {version = "2.2"}
6 changes: 6 additions & 0 deletions crates/cnidarium/src/storage/temp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ impl Deref for TempStorage {
}
}

impl AsRef<Storage> for TempStorage {
fn as_ref(&self) -> &Storage {
&self.inner
}
}

impl TempStorage {
pub async fn new() -> anyhow::Result<Self> {
let dir = tempfile::tempdir()?;
Expand Down
1 change: 1 addition & 0 deletions crates/core/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ decaf377 = {workspace = true, default-features = true}
decaf377-rdsa = {workspace = true}
jmt = {workspace = true}
tokio = {workspace = true, features = ["full", "tracing"]}
tokio-util = {workspace = true}
async-trait = {workspace = true}
tonic = {workspace = true}
futures = {workspace = true}
Expand Down
5 changes: 1 addition & 4 deletions crates/core/app/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@ pub fn new(
req.create_span()
}))
.layer(EventIndexLayer::index_all())
.service(tower_actor::Actor::new(10, |queue: _| {
let storage = storage.clone();
async move { Consensus::new(storage.clone(), queue).await?.run().await }
}));
.service(Consensus::new(storage.clone()));
let mempool = tower::ServiceBuilder::new()
.layer(request_span::layer(|req: &MempoolRequest| {
use penumbra_tower_trace::v037::RequestExt;
Expand Down
17 changes: 16 additions & 1 deletion crates/core/app/src/server/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use tendermint::v0_37::abci::{
request, response, ConsensusRequest as Request, ConsensusResponse as Response,
};
use tokio::sync::mpsc;
use tower::BoxError;
use tower_actor::Message;
use tracing::Instrument;

Expand All @@ -29,7 +30,21 @@ fn trace_events(events: &[Event]) {
}

impl Consensus {
pub async fn new(
const QUEUE_SIZE: usize = 10;

pub fn new(storage: Storage) -> tower_actor::Actor<Request, Response, BoxError> {
tower_actor::Actor::new(Self::QUEUE_SIZE, |queue: _| {
let storage = storage.clone();
async move {
Consensus::new_inner(storage.clone(), queue)
.await?
.run()
.await
}
})
}

async fn new_inner(
storage: Storage,
queue: mpsc::Receiver<Message<Request, Response, tower::BoxError>>,
) -> Result<Self> {
Expand Down
23 changes: 23 additions & 0 deletions crates/core/app/tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
//! Shared integration testing facilities.
// 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;
use penumbra_genesis::AppState;
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 {
use tracing_subscriber::filter::EnvFilter;

let filter = "debug,penumbra_app=trace,penumbra_mock_consensus=trace";
let filter = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new(filter))
.expect("should have a valid filter directive");

let subscriber = tracing_subscriber::fmt()
.with_env_filter(filter)
.pretty()
.with_test_writer()
.finish();

tracing::subscriber::set_default(subscriber)
}

#[async_trait]
pub trait TempStorageExt: Sized {
async fn apply_genesis(self, genesis: AppState) -> anyhow::Result<Self>;
Expand Down
33 changes: 22 additions & 11 deletions crates/core/app/tests/mock_consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,32 @@
// Note: these should eventually replace the existing test cases. mock consensus tests are placed
// here while the engine is still in development. See #3588.

use {cnidarium::TempStorage, penumbra_mock_consensus::TestNode};
mod common;

#[tokio::test]
#[should_panic]
#[allow(unreachable_code, unused)]
async fn an_app_with_mock_consensus_can_be_instantiated() {
let storage = TempStorage::new().await.unwrap();
use cnidarium::TempStorage;
use penumbra_app::server::consensus::Consensus;

// TODO(kate): bind this to an in-memory channel/writer instead.
let addr: std::net::SocketAddr = todo!();
let abci_server = penumbra_app::server::new(todo!()).listen_tcp(addr);
#[tokio::test]
async fn mock_consensus_can_send_a_failing_init_chain_request() -> anyhow::Result<()> {
// Install a test logger, and acquire some temporary storage.
let guard = common::set_tracing_subscriber();
let storage = TempStorage::new().await?;

let _engine = TestNode::<()>::builder()
// Instantiate the consensus service, and start the test node.
use penumbra_mock_consensus::TestNode;
let consensus = Consensus::new(storage.as_ref().clone());
let engine = TestNode::builder()
.single_validator()
.app_state(() /*genesis::AppState::default()*/)
.init_chain(abci_server)
.init_chain(consensus)
.await;

// NB: we don't expect this to succeed... yet.
assert!(engine.is_err(), "init_chain does not return an Ok(()) yet");

// Free our temporary storage.
drop(storage);
drop(guard);

Ok(())
}
6 changes: 6 additions & 0 deletions crates/test/mock-consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ homepage.workspace = true
license.workspace = true

[dependencies]
anyhow = { workspace = true }
bytes = { workspace = true }
tendermint = { workspace = true }
tower = { workspace = true, features = ["full"] }
tracing = { workspace = true }
tap = "1.0.1"
7 changes: 7 additions & 0 deletions crates/test/mock-consensus/src/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// TODO: see #3792.

use crate::TestNode;

struct _Builder<'e, C> {
engine: &'e mut TestNode<C>,
}
38 changes: 38 additions & 0 deletions crates/test/mock-consensus/src/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! [`Builder`] interfaces, for creating new [`TestNode`]s.
/// [`Builder`] interfaces for chain initialization.
///
/// Most importantly, defines [`Builder::init_chain()`].
mod init_chain;

use crate::TestNode;

/// A buider, used to prepare and instantiate a new [`TestNode`].
pub struct Builder;

impl TestNode<()> {
/// Returns a new [`Builder`].
pub fn builder() -> Builder {
Builder
}
}

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
}

pub fn app_state(self, _: ()) -> Self {
// this does not do anything yet
self
}

pub fn app_state_bytes(self, _: Vec<u8>) -> Self {
// this does not do anything yet
self
}
}
94 changes: 94 additions & 0 deletions crates/test/mock-consensus/src/builder/init_chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use {
super::*,
anyhow::{anyhow, bail},
std::time,
tap::TapFallible,
tendermint::{
block,
consensus::{
self,
params::{AbciParams, ValidatorParams, VersionParams},
},
evidence,
v0_37::abci::{ConsensusRequest, ConsensusResponse},
},
tower::{BoxError, Service, ServiceExt},
tracing::{debug, error},
};

impl Builder {
/// Consumes this builder, using the provided consensus service.
pub async fn init_chain<C>(self, mut consensus: C) -> Result<TestNode<C>, anyhow::Error>
where
C: Service<ConsensusRequest, Response = ConsensusResponse, Error = BoxError>
+ Send
+ Clone
+ 'static,
C::Future: Send + 'static,
C::Error: Sized,
{
use tendermint::v0_37::abci::response;

let request = Self::init_chain_request();
let service = consensus
.ready()
.await
.tap_err(|error| error!(?error, "failed waiting for consensus service"))
.map_err(|_| anyhow!("failed waiting for consensus service"))?;

let response::InitChain { app_hash, .. } = match service
.call(request)
.await
.tap_ok(|resp| debug!(?resp, "received response from consensus service"))
.tap_err(|error| error!(?error, "consensus service returned error"))
.map_err(|_| anyhow!("consensus service returned error"))?
{
ConsensusResponse::InitChain(resp) => resp,
response => {
error!(?response, "unexpected InitChain response");
bail!("unexpected InitChain response");
}
};

Ok(TestNode {
consensus,
last_app_hash: app_hash.as_bytes().to_owned(),
})
}

fn init_chain_request() -> ConsensusRequest {
use tendermint::v0_37::abci::request::InitChain;
let consensus_params = Self::consensus_params();
let app_state_bytes = bytes::Bytes::new();
ConsensusRequest::InitChain(InitChain {
time: tendermint::Time::now(),
chain_id: "test".to_string(), // XXX const here?
consensus_params,
validators: vec![],
app_state_bytes,
initial_height: 1_u32.into(),
})
}

fn consensus_params() -> consensus::Params {
consensus::Params {
block: block::Size {
max_bytes: 1,
max_gas: 1,
time_iota_ms: 1,
},
evidence: evidence::Params {
max_age_num_blocks: 1,
max_age_duration: evidence::Duration(time::Duration::from_secs(1)),
max_bytes: 1,
},
validator: ValidatorParams {
pub_key_types: vec![],
},
version: Some(VersionParams { app: 1 }),
abci: AbciParams {
vote_extensions_enable_height: None,
},
}
}
}
54 changes: 14 additions & 40 deletions crates/test/mock-consensus/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,19 @@
pub struct TestNode<S> {
#[allow(dead_code)]
abci_server: S,
_last_app_hash: Vec<u8>,
//! `penumbra-mock-consensus` is a library for testing consensus-driven applications.
//
// see penumbra-zone/penumbra#3588.

mod block;
mod builder;

// TODO(kate): this is a temporary allowance while we set the test node up.
#[allow(dead_code)]
pub struct TestNode<C> {
consensus: C,
last_app_hash: Vec<u8>,
}

pub mod block {
use crate::TestNode;

struct _Builder<'e, C> {
engine: &'e mut TestNode<C>,
}
}

pub struct Builder;

impl<A> TestNode<A> {
pub fn builder() -> Builder {
Builder
}
}

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 {
impl<C> TestNode<C> {
pub fn next_block() -> tendermint::Block {
todo!();
}

pub fn app_state(self, _: ()) -> Self {
todo!()
}

pub fn app_state_bytes(self, _: Vec<u8>) -> Self {
todo!()
}

pub async fn init_chain<S>(self, abci_server: S) -> TestNode<S> {
TestNode {
abci_server,
_last_app_hash: vec![],
}
}
}

0 comments on commit c85e3ea

Please sign in to comment.