penumbra_sdk_mock_consensus/
lib.rs

1//! `penumbra-mock-consensus` is a library for testing consensus-driven ABCI applications.
2//!
3//! # Overview
4//!
5//! This library provides facilities that can act as a stand-in for consensus engines like
6//! [CometBFT][cometbft] or [Tendermint][tendermint] in integration tests.
7//!
8//! Testing applications using a mock consensus engine has many benefits. For example, this allows
9//! integration test cases to run as fast as possible, without needing wait real wall-clock time
10//! for blocks to be generated, or for integration test cases to exercise slashing logic related to
11//! byzantine misbehavior (_e.g., double-signing_).
12//!
13//! This library is agnostic with respect to the replicable state transition machine that it
14//! is used to test. This means that, while it may be used to write integration tests for the
15//! [Penumbra][penumbra] network, it can also be used to test other decentralized applications.
16//!
17//! See [`TestNode`] for more information about using `penumbra-mock-consensus`.
18//!
19//! # Alternatives
20//!
21//! Projects implemented in Go may wish to consider using [CometMock][cometmock].
22//! `penumbra-mock-consensus` is primarily oriented towards projects implemented in Rust that wish
23//! to use [`cargo test`][cargo-test] or [`cargo nextest`][cargo-nextest] as a test-runner.
24//!
25//! [cargo-nextest]: https://nexte.st/
26//! [cargo-test]: https://doc.rust-lang.org/cargo/commands/cargo-test.html
27//! [cometbft]: https://github.com/cometbft/cometbft
28//! [cometmock]: https://github.com/informalsystems/CometMock
29//! [penumbra]: https://github.com/penumbra-zone/penumbra
30//! [tendermint]: https://github.com/tendermint/tendermint
31
32use {
33    ed25519_consensus::{SigningKey, VerificationKey},
34    std::collections::BTreeMap,
35    tendermint::{
36        block::{Commit, Height},
37        Time,
38    },
39};
40
41pub mod block;
42pub mod builder;
43
44mod abci;
45
46/// A test node.
47///
48/// A [`TestNode<C>`] represents a validator node containing an instance of the state transition
49/// machine and its accompanying consensus engine.
50///
51/// # Initialization
52///
53/// Construct a new test node by calling [`TestNode::builder()`]. The [`builder::Builder`]
54/// returned by that method can be used to set the initial application state, and configure
55/// validators' consensus keys that should be present at genesis. Use
56/// [`builder::Builder::init_chain()`] to consume the builder and initialize the application.
57///
58/// # Consensus Service
59///
60/// A test node is generic in terms of a consensus service `C`. This service should implement
61/// [`tower::Service`], accepting [`ConsensusRequest`][consensus-request]s, and returning
62/// [`ConsensusResponse`][consensus-response]s.
63///
64/// For [`tower-abci`][tower-abci] users, this should correspond with the `C` parameter of the
65/// [`Server`][tower-abci-server] type.
66///
67/// # Blocks
68///
69/// Blocks can be executed by using [`TestNode::block()`]. This can be used to add transactions,
70/// signatures, and evidence to a [`Block`][tendermint-rs-block], before invoking
71/// [`block::Builder::execute()`] to execute the next block.
72///
73/// [consensus-request]: tendermint::v0_37::abci::ConsensusRequest
74/// [consensus-response]: tendermint::v0_37::abci::ConsensusResponse
75/// [tendermint-rs-block]: tendermint::block::Block
76/// [tower-abci-server]: https://docs.rs/tower-abci/latest/tower_abci/v037/struct.Server.html#
77/// [tower-abci]: https://docs.rs/tower-abci/latest/tower_abci
78pub struct TestNode<C> {
79    /// The inner consensus service being tested.
80    consensus: C,
81    /// The last `app_hash` value.
82    last_app_hash: Vec<u8>,
83    /// The last validator set hash value.
84    last_validator_set_hash: Option<tendermint::Hash>,
85    /// The last tendermint block header commit value.
86    last_commit: Option<tendermint::block::Commit>,
87    /// The consensus params hash.
88    consensus_params_hash: Vec<u8>,
89    /// The current block [`Height`][tendermint::block::Height].
90    height: tendermint::block::Height,
91    /// Validators' consensus keys.
92    ///
93    /// Entries in this keyring consist of a [`VerificationKey`] and a [`SigningKey`].
94    keyring: Keyring,
95    /// A callback that will be invoked when a new block is constructed.
96    on_block: Option<OnBlockFn>,
97    /// A callback that will be invoked when a new block is committed, to produce the next timestamp.
98    ts_callback: TsCallbackFn,
99    /// The current timestamp of the node.
100    timestamp: Time,
101    /// The chain ID.
102    chain_id: tendermint::chain::Id,
103}
104
105/// A type alias for the `TestNode::on_block` callback.
106pub type OnBlockFn = Box<dyn FnMut(tendermint::Block) + Send + Sync + 'static>;
107
108/// A type alias for the `TestNode::ts_callback` callback.
109pub type TsCallbackFn = Box<dyn Fn(Time) -> Time + Send + Sync + 'static>;
110
111/// An ordered map of consensus keys.
112///
113/// Entries in this keyring consist of a [`VerificationKey`] and a [`SigningKey`].
114type Keyring = BTreeMap<VerificationKey, SigningKey>;
115
116/// Accessors.
117impl<C> TestNode<C> {
118    /// A chain ID for use in tests.
119    pub const CHAIN_ID: &'static str = "penumbra-test-chain";
120
121    /// Returns the last `app_hash` value, represented as a slice of bytes.
122    pub fn last_app_hash(&self) -> &[u8] {
123        &self.last_app_hash
124    }
125
126    /// Returns the last `commit` value.
127    pub fn last_commit(&self) -> Option<&Commit> {
128        self.last_commit.as_ref()
129    }
130
131    /// Returns the last `validator_set_hash` value.
132    pub fn last_validator_set_hash(&self) -> Option<&tendermint::Hash> {
133        self.last_validator_set_hash.as_ref()
134    }
135
136    /// Returns the most recent `timestamp` value.
137    pub fn timestamp(&self) -> &Time {
138        &self.timestamp
139    }
140
141    /// Returns the last `app_hash` value, represented as a hexadecimal string.
142    pub fn last_app_hash_hex(&self) -> String {
143        // Use upper-case hexadecimal integers, include leading zeroes.
144        // - https://doc.rust-lang.org/std/fmt/#formatting-traits
145        self.last_app_hash
146            .iter()
147            .map(|b| format!("{:02X}", b).to_string())
148            .collect::<Vec<String>>()
149            .join("")
150    }
151
152    /// Returns a reference to the test node's set of consensus keys.
153    pub fn keyring(&self) -> &Keyring {
154        &self.keyring
155    }
156
157    /// Returns a mutable reference to the test node's set of consensus keys.
158    pub fn keyring_mut(&mut self) -> &mut Keyring {
159        &mut self.keyring
160    }
161
162    pub fn height(&self) -> &Height {
163        &self.height
164    }
165}
166
167/// Fast forward interfaces.
168impl<C> TestNode<C>
169where
170    C: tower::Service<
171            tendermint::v0_37::abci::ConsensusRequest,
172            Response = tendermint::v0_37::abci::ConsensusResponse,
173            Error = tower::BoxError,
174        > + Send
175        + Clone
176        + 'static,
177    C::Future: Send + 'static,
178    C::Error: Sized,
179{
180    /// Fast forwards the given number of blocks.
181    #[tracing::instrument(
182        skip(self),
183        fields(fast_forward.blocks = %blocks)
184    )]
185    pub async fn fast_forward(&mut self, blocks: u64) -> anyhow::Result<()> {
186        use {
187            tap::Tap,
188            tracing::{info, trace, trace_span, Instrument},
189        };
190
191        for i in 1..=blocks {
192            self.block()
193                .execute()
194                .tap(|_| trace!(%i, "executing empty block"))
195                .instrument(trace_span!("executing empty block", %i))
196                .await
197                .tap(|_| trace!(%i, "finished executing empty block"))?;
198        }
199
200        Ok(()).tap(|_| info!("finished fast forward"))
201    }
202}
203
204/// Assert that a [`TestNode`] is both [`Send`] and [`Sync`].
205#[allow(dead_code)]
206mod assert_address_is_send_and_sync {
207    fn is_send<T: Send>() {}
208    fn is_sync<T: Sync>() {}
209    fn f() {
210        is_send::<super::TestNode<()>>();
211        is_sync::<super::TestNode<()>>();
212    }
213}