Skip to content

Commit

Permalink
docs(mock-consensus): 🦅 document various testing interfaces (#4184)
Browse files Browse the repository at this point in the history
fixes #4181. see #3588.

this makes a pass through the `penumbra-mock-consensus` library and
further documents various interfaces. one other small tweak, logging a
warning if a caller may be inadvertently discarding transactions, is
made while we are here.

these docs may be rendered by running:

`cargo doc --package penumbra-mock-consensus --open`

---------

Co-authored-by: Conor Schaefer <[email protected]>
  • Loading branch information
cratelyn and conorsch authored Apr 9, 2024
1 parent 1593f12 commit 15e5e06
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 23 deletions.
33 changes: 24 additions & 9 deletions crates/test/mock-consensus/src/block.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
//! [`Builder`] facilities for constructing [`Block`]s.
//!
//! Builders are acquired by calling [`TestNode::block()`].

/// Interfaces for generating commit signatures.
mod signature;
//! Builders are acquired by calling [`TestNode::block()`], see [`TestNode`] for more information.

use {
crate::TestNode,
Expand All @@ -19,19 +16,24 @@ use {
tracing::{instrument, trace},
};

/// A builder, used to prepare and instantiate a new [`Block`].
/// Interfaces for generating commit signatures.
mod signature;

/// A block builder.
///
/// A block builder can be used to prepare and instantiate a new [`Block`]. A block builder is
/// acquired by calling [`TestNode::block()`]. This builder holds an exclusive reference to a
/// [`TestNode`], so only one block may be built at once.
///
/// These are acquired by calling [`TestNode::block()`].
/// This builder can be consumed, executing the block against the [`TestNode`]'s consensus service,
/// by calling [`Builder::execute()`].
pub struct Builder<'e, C> {
/// A unique reference to the test node.
test_node: &'e mut TestNode<C>,

/// Transaction data.
data: Vec<Vec<u8>>,

/// Evidence of malfeasance.
evidence: evidence::List,

/// The list of signatures.
signatures: Vec<block::CommitSig>,
}
Expand All @@ -40,6 +42,10 @@ pub struct Builder<'e, C> {

impl<C> TestNode<C> {
/// Returns a new [`Builder`].
///
/// By default, signatures for all of the validators currently within the keyring will be
/// included in the block. Use [`Builder::with_signatures()`] to set a different set of
/// validator signatures.
pub fn block<'e>(&'e mut self) -> Builder<'e, C> {
let signatures = self.generate_signatures().collect();
Builder {
Expand All @@ -56,6 +62,15 @@ impl<C> TestNode<C> {
impl<'e, C> Builder<'e, C> {
/// Sets the data for this block.
pub fn with_data(self, data: Vec<Vec<u8>>) -> Self {
let Self { data: prev, .. } = self;

if !prev.is_empty() {
tracing::warn!(
count = %prev.len(),
"block builder overwriting transaction data, this may be a bug!"
);
}

Self { data, ..self }
}

Expand Down
2 changes: 1 addition & 1 deletion crates/test/mock-consensus/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use {
bytes::Bytes,
};

/// A buider, used to prepare and instantiate a new [`TestNode`].
/// A builder, used to prepare and instantiate a new [`TestNode`].
#[derive(Default)]
pub struct Builder {
pub app_state: Option<Bytes>,
Expand Down
5 changes: 5 additions & 0 deletions crates/test/mock-consensus/src/builder/init_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ use {

impl Builder {
/// Consumes this builder, using the provided consensus service.
///
/// This function returns an error if the builder was not fully initialized, or if the
/// application could not successfully perform the chain initialization.
///
/// See [`TestNode`] for more information on the 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>
Expand Down
83 changes: 70 additions & 13 deletions crates/test/mock-consensus/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,33 @@
//! `penumbra-mock-consensus` is a library for testing consensus-driven applications.
//! `penumbra-mock-consensus` is a library for testing consensus-driven ABCI applications.
//!
//! See [`TestNode`] for more information.
//
// see penumbra-zone/penumbra#3588.
//! # Overview
//!
//! This library provides facilities that can act as a stand-in for consensus engines like
//! [CometBFT][cometbft] or [Tendermint][tendermint] in integration tests.
//!
//! Testing applications using a mock consensus engine has many benefits. For example, this allows
//! integration test cases to run as fast as possible, without needing wait real wall-clock time
//! for blocks to be generated, or for integration test cases to exercise slashing logic related to
//! byzantine misbehavior (_e.g., double-signing_).
//!
//! This library is agnostic with respect to the replicable state transition machine that it
//! is used to test. This means that, while it may be used to write integration tests for the
//! [Penumbra][penumbra] network, it can also be used to test other decentralized applications.
//!
//! See [`TestNode`] for more information about using `penumbra-mock-consensus`.
//!
//! # Alternatives
//!
//! Projects implemented in Go may wish to consider using [CometMock][cometmock].
//! `penumbra-mock-consensus` is primarily oriented towards projects implemented in Rust that wish
//! to use [`cargo test`][cargo-test] or [`cargo nextest`][cargo-nextest] as a test-runner.
//!
//! [cargo-nextest]: https://nexte.st/
//! [cargo-test]: https://doc.rust-lang.org/cargo/commands/cargo-test.html
//! [cometbft]: https://github.com/cometbft/cometbft
//! [cometmock]: https://github.com/informalsystems/CometMock
//! [penumbra]: https://github.com/penumbra-zone/penumbra
//! [tendermint]: https://github.com/tendermint/tendermint

use {
ed25519_consensus::{SigningKey, VerificationKey},
Expand All @@ -16,17 +41,46 @@ mod abci;

/// A test node.
///
/// Construct a new test node by calling [`TestNode::builder()`]. Use [`TestNode::block()`] to
/// build a new [`Block`].
/// A [`TestNode<C>`] represents a validator node containing an instance of the state transition
/// machine and its accompanying consensus engine.
///
/// # Initialization
///
/// Construct a new test node by calling [`TestNode::builder()`]. The [`builder::Builder`]
/// returned by that method can be used to set the initial application state, and configure
/// validators' consensus keys that should be present at genesis. Use
/// [`builder::Builder::init_chain()`] to consume the builder and initialize the application.
///
/// # Consensus Service
///
/// A test node is generic in terms of a consensus service `C`. This service should implement
/// [`tower::Service`], accepting [`ConsensusRequest`][consensus-request]s, and returning
/// [`ConsensusResponse`][consensus-response]s.
///
/// For [`tower-abci`][tower-abci] users, this should correspond with the `C` parameter of the
/// [`Server`][tower-abci-server] type.
///
/// # Blocks
///
/// Blocks can be executed by using [`TestNode::block()`]. This can be used to add transactions,
/// signatures, and evidence to a [`Block`][tendermint-rs-block], before invoking
/// [`block::Builder::execute()`] to execute the next block.
///
/// This contains a consensus service `C`, which should be a [`tower::Service`] implementor that
/// accepts [`ConsensusRequest`][0_37::abci::ConsensusRequest]s, and returns
/// [`ConsensusResponse`][0_37::abci::ConsensusResponse]s. For `tower-abci` users, this should
/// correspond with the `ConsensusService` parameter of the `Server` type.
/// [consensus-request]: tendermint::v0_37::abci::ConsensusRequest
/// [consensus-response]: tendermint::v0_37::abci::ConsensusResponse
/// [tendermint-rs-block]: tendermint::block::Block
/// [tower-abci-server]: https://docs.rs/tower-abci/latest/tower_abci/v037/struct.Server.html#
/// [tower-abci]: https://docs.rs/tower-abci/latest/tower_abci
pub struct TestNode<C> {
/// The inner consensus service being tested.
consensus: C,
/// The last `app_hash` value.
last_app_hash: Vec<u8>,
/// The current block [`Height`][tendermint::block::Height].
height: tendermint::block::Height,
/// Validators' consensus keys.
///
/// Entries in this keyring consist of a [`VerificationKey`] and a [`SigningKey`].
keyring: Keyring,
}

Expand All @@ -35,15 +89,17 @@ pub struct TestNode<C> {
/// Entries in this keyring consist of a [`VerificationKey`] and a [`SigningKey`].
type Keyring = BTreeMap<VerificationKey, SigningKey>;

/// Accessors.
impl<C> TestNode<C> {
/// A chain ID for use in tests.
pub const CHAIN_ID: &'static str = "penumbra-test-chain";

/// Returns the last app_hash value, as a slice of bytes.
/// Returns the last `app_hash` value, represented as a slice of bytes.
pub fn last_app_hash(&self) -> &[u8] {
&self.last_app_hash
}

/// Returns the last app_hash value, as a hexadecimal string.
/// Returns the last `app_hash` value, represented as a hexadecimal string.
pub fn last_app_hash_hex(&self) -> String {
// Use upper-case hexadecimal integers, include leading zeroes.
// - https://doc.rust-lang.org/std/fmt/#formatting-traits
Expand All @@ -61,6 +117,7 @@ impl<C> TestNode<C> {
}
}

/// Fast forward interfaces.
impl<C> TestNode<C>
where
C: tower::Service<
Expand All @@ -73,7 +130,7 @@ where
C::Future: Send + 'static,
C::Error: Sized,
{
/// Fast forwards a number of blocks.
/// Fast forwards the given number of blocks.
#[tracing::instrument(
skip(self),
fields(fast_forward.blocks = %blocks)
Expand Down
1 change: 1 addition & 0 deletions deployments/scripts/rust-docs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ cargo +nightly doc --no-deps \
-p penumbra-ibc \
-p penumbra-keys \
-p penumbra-measure \
-p penumbra-mock-consensus \
-p penumbra-num \
-p penumbra-proof-params \
-p penumbra-proof-setup \
Expand Down

0 comments on commit 15e5e06

Please sign in to comment.