Skip to content

Commit

Permalink
feat: 📝 implement cometstub crate (work-in-progress)
Browse files Browse the repository at this point in the history
this commit introduces a new library, in `crates/test/`. this library
contains a mock implementation of cometbft, for use in cargo integration
tests.

* this does NOT add the `penumbra-cometstub` crate to the list of
  crates included in the rust documentation in
  `deployments/scripts/rust-docs`. the `penumbra-tct-property-test`
  crate was also not included in that list at the time of writing.

/!\ ------------------------------------------------------- /!\
/!\ NOTE: this is a rolling work-in-progress.               /!\
/!\ this branch will be force-pushed frequently until it is /!\
/!\ ready for review. proceed accordingly!                  /!\
/!\ ------------------------------------------------------- /!\

NOTE 24-01-02: i have brought over a test from
`core/app/src/tests/spend.rs`. currently, it panics because proving keys
could not be loaded. printlns and panics have been left to point out
how far we can get through that test before failing. track down the core
of this issue next week.

use this command to see it break:

```
cargo watch -Bc -x 'test -p penumbra-cometstub spend_happy_path'
```
  • Loading branch information
cratelyn committed Jan 19, 2024
1 parent d8df8c7 commit 737f4fb
Show file tree
Hide file tree
Showing 11 changed files with 479 additions and 0 deletions.
26 changes: 26 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ members = [
"crates/bin/pclientd",
"crates/bin/pcli",
"crates/wasm",
"crates/test/cometstub",
"crates/test/tct-property-test",
"crates/misc/measure",
"crates/misc/tct-visualize",
Expand Down
1 change: 1 addition & 0 deletions crates/crypto/proof-params/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ impl Deref for LazyProvingKey {

/// Proving key for the spend proof.
pub static SPEND_PROOF_PROVING_KEY: Lazy<LazyProvingKey> = Lazy::new(|| {
println!("XXX(kate) loading spend proving key");
let spend_proving_key = LazyProvingKey::new(spend::PROVING_KEY_ID);

#[cfg(feature = "bundled-proving-keys")]
Expand Down
46 changes: 46 additions & 0 deletions crates/test/cometstub/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[package]
name = "penumbra-cometstub"
version = "0.64.1"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = ["component", "std"]
component = [
# TODO(kate): are these feature flags needed? maybe.
# "cnidarium",
# "cnidarium-component",
# "penumbra-proto/cnidarium",
# "penumbra-chain/component",
]
std = ["ibc-types/std"]

[dependencies]
anyhow = "1"
# ed25519 = "2.2.3"
ed25519-consensus = "2.1.0"
ibc-proto = { version = "0.40.0", default-features = false }
ibc-types = { version = "0.11.0", default-features = false }
rand_core = "0.6.3"
sha2 = "0.10.8"
tendermint = "0.34.0"
tendermint-light-client-verifier = "0.34.0"

[dev-dependencies]
cnidarium = { path = "../../cnidarium" }
cnidarium-component = { path = "../../cnidarium-component" }
penumbra-app = { path = "../../core/app" }
penumbra-chain = { path = "../../core/component/chain", features = ["component"] }
penumbra-compact-block = { path = "../../core/component/compact-block" }
penumbra-keys = { path = "../../core/keys" }
penumbra-proof-params = { path = "../../crypto/proof-params", features = [
"bundled-proving-keys",
"download-proving-keys",
] }
penumbra-sct = { path = "../../core/component/sct" }
penumbra-shielded-pool = { path = "../../core/component/shielded-pool", features = ["component"] }
penumbra-txhash = { path = "../../core/txhash" }
rand_chacha = "0.3"
rand_core = "0.6"
tokio = { version = "1.21.1", features = ["full", "tracing"] }
45 changes: 45 additions & 0 deletions crates/test/cometstub/src/abci.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! ABCI- and ABCI++-related facilities.
//!
//! See the [ABCI++ specification][abci-spec] for more information. See ["Methods][abci-methods]
//! for more information on ABCI methods.
//!
//! [abci-spec]: https://github.com/cometbft/cometbft/blob/main/spec/abci/README.md
//! [abci-methods]: https://github.com/cometbft/cometbft/blob/main/spec/abci/abci++_methods.md
//
// TODO(kate): `tendermint::abci::request` types, stub these out as needed.
// - apply_snapshot_chunk::ApplySnapshotChunk,
// - begin_block::BeginBlock,
// - check_tx::{CheckTx, CheckTxKind},
// - deliver_tx::DeliverTx,
// - echo::Echo,
// - end_block::EndBlock,
// - extend_vote::ExtendVote,
// - finalize_block::FinalizeBlock,
// - info::Info,
// - init_chain::InitChain,
// - load_snapshot_chunk::LoadSnapshotChunk,
// - offer_snapshot::OfferSnapshot,
// - prepare_proposal::PrepareProposal,
// - process_proposal::ProcessProposal,
// - query::Query,
// - set_option::SetOption,
// - verify_vote_extension::VerifyVoteExtension,

use tendermint::{
abci::{request::BeginBlock, types},
block::Round,
Hash,
};

#[allow(dead_code)] // XXX(kate)
pub(crate) fn begin_block() -> BeginBlock {
BeginBlock {
hash: Hash::None,
header: crate::header::header(),
last_commit_info: types::CommitInfo {
round: Round::default(),
votes: vec![],
},
byzantine_validators: vec![],
}
}
36 changes: 36 additions & 0 deletions crates/test/cometstub/src/header.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! Facilities for generating tendermint [`Header`]s
use tendermint::{
account,
block::{self, Header},
chain,
validator::Set,
AppHash, Hash, Time,
};

pub(crate) fn header() -> Header {
let validators_hash = Set::new(vec![], None).hash();
Header {
version: block::header::Version { block: 0, app: 0 },
chain_id: chain::Id::try_from("test").unwrap(),
height: block::Height::default(),
time: Time::unix_epoch(),
last_block_id: None,
last_commit_hash: None,
data_hash: None,
validators_hash,
next_validators_hash: validators_hash,
consensus_hash: Hash::None,
app_hash: app_hash(),
last_results_hash: None,
evidence_hash: None,
proposer_address: account::Id::new([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]),
}
}

// TODO(kate): informalsystems/tendermint-rs#1243
fn app_hash() -> AppHash {
AppHash::try_from(vec![1, 2, 3]).expect("AppHash::try_from is infallible")
}
186 changes: 186 additions & 0 deletions crates/test/cometstub/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//! `penumbra-cometstub` is an in-memory consensus engine for integration tests.
//
// TODO(kate):
// - `tests/ibc_handshake.rs` contains a starter test case to build out.

mod abci;
mod header;
mod state;

use {
rand_core::OsRng,
tendermint::{block, chain, vote, AppHash, Hash, Time},
};

/// An in-memory consensus engine for integration tests.
///
/// See the crate-level documentation for more information.
pub struct Engine {
/// Inner consensus state.
#[allow(dead_code)] // XXX(kate)
state: State,
}

/// Consensus state used by the [`Engine`] to generate [`Block`s][tendermint::Block].
#[allow(dead_code)] // XXX(kate)
struct State {
/// The chain identifier.
chain_id: chain::Id,
/// The initial [`block::Height`].
initial_height: block::Height,
/// Metadata regarding the last block generated.
last_block: Option<LastBlock>,
/// The set of validators.
validators: Validators,
/// The latest app hash.
app_hash: AppHash,
/// The merkle root of the result of executing the previous block.
last_results_hash: Option<Hash>,
// TODO(kate): handle consensus parameters later.
// consensus_params: (),
// last_height_consensus_params_changed: block::Height,
}

/// Consensus [`State`] metadata regarding the last block generated.
#[allow(dead_code)] // XXX(kate)
struct LastBlock {
height: block::Height,
id: block::Id,
time: Time,
}

/// Consensus [`State`] fields regarding for the validator set.
#[allow(dead_code)] // XXX(kate)
pub struct Validators {
current: Vec<Validator>,
// TODO(kate): we may want these fields too.
// - next: Vec<Validator>,
// - last: Vec<Validator>,
// - last_height_validators_changed: block::Height,
}

/// A validator address with voting power.
///
/// This is a [`tendermint::abci::types::Validator`], but with signing keys held in-memory.
///
/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#validator)
#[allow(dead_code)] // XXX(kate)
pub struct Validator {
/// The validator's address (the first 20 bytes of `SHA256(public_key)`).
pub address: [u8; 20],
/// The voting power of the validator.
pub power: vote::Power,
/// The validator's (private) signing key.
signing_key: ed25519_consensus::SigningKey,
}

// === impl Engine ===

impl Engine {
/// Returns a new [`Engine`].
pub fn new() -> Self {
Self {
state: State::new(),
}
}
}

// === impl State ===

impl State {
/// Returns a new [`State`].
//
// TODO(kate): what do we need at the start? avoid recreating a whole genesis file.
// use a single validator key, no arbitrary genesis height, simple.
pub fn new() -> Self {
let app_hash: AppHash = b"placeholder-app-hash"
.to_vec()
.try_into()
.expect("infallible");

let validators = {
/// A hard-coded voting power assignment for our mock validator.
// TODO(kate): upstream a constant `Power` constructor, if possible.
const POWER: u32 = 100;
let v = Validator::new(POWER.into());
Validators { current: vec![v] }
};

State {
chain_id: chain::Id::try_from("penumbra-cometstub").unwrap(),
initial_height: 0_u32.into(),
last_block: None,
validators,
app_hash,
last_results_hash: None,
}
}
}

// === impl Validators ===

impl Validators {
/// Returns a validator containing a single [`Validator`].
pub fn single() -> Self {
let power = 100_u32.into();
Self {
current: vec![
Validator::new(power),
],
}
}

pub fn new(validators: Vec<Validator>) -> Self {
Self {
current: validators,
}
}
}

impl FromIterator<Validator> for Validators {
fn from_iter<T: IntoIterator<Item = Validator>>(iter: T) -> Self {
let current = iter.into_iter().collect();
Self { current }
}
}

// === impl Validator ===

impl Validator {
/// Returns a [`Validator`] with the designated voting power.
///
/// NB: This will generate a [`ed25519_consensus::SigningKey`] for this validator.
pub fn new(power: vote::Power) -> Self {
let signing_key = ed25519_consensus::SigningKey::new(OsRng);
let address = Self::address_from_public_key(&signing_key);
Validator {
address,
power,
signing_key,
}
}

// TODO(kate): tendermint-rs says addresses are the first 20 bytes of the sha256 hash of the
// public key. track down where this is described in the spec.
fn address_from_public_key(public_key: impl AsRef<[u8]>) -> [u8; 20] {
use sha2::{Digest as _, Sha256};
let mut hasher = Sha256::new();
hasher.update(public_key);
let hash = hasher.finalize();
hash[..20].try_into().expect("")
}
}

// TODO(kate): `Into`, implementation for converting this into a tendermint::..Validator, and
// for getting its key as a tendermint::..SigningKey. write those when needed.

// === unit tests ===

#[cfg(test)]
mod validator_tests {
use super::*;
#[test]
fn single_validator_can_be_created() {
let _ = Validators::single();
}
}
1 change: 1 addition & 0 deletions crates/test/cometstub/src/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading

0 comments on commit 737f4fb

Please sign in to comment.