-
Notifications
You must be signed in to change notification settings - Fork 305
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
tests(app): 🧪 test that uptime tracks signed blocks (#4070)
**NB:** this is based on #4099. this adds test coverage, complementary to the work in #4061, which asserts that we properly track the _affirmative_ case of validators signing blocks. fixes #4040. see #3995.
- Loading branch information
Showing
8 changed files
with
238 additions
and
17 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
crates/core/app/tests/app_tracks_uptime_for_genesis_validator_signing_blocks.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
mod common; | ||
|
||
use { | ||
self::common::BuilderExt, | ||
anyhow::Context, | ||
cnidarium::TempStorage, | ||
penumbra_app::server::consensus::Consensus, | ||
penumbra_genesis::AppState, | ||
penumbra_mock_consensus::TestNode, | ||
penumbra_stake::{ | ||
component::validator_handler::validator_store::ValidatorDataRead, validator::Validator, | ||
}, | ||
tap::Tap, | ||
tracing::{error_span, info, Instrument}, | ||
}; | ||
|
||
#[tokio::test] | ||
async fn app_tracks_uptime_for_genesis_validator_missing_blocks() -> anyhow::Result<()> { | ||
// Install a test logger, acquire some temporary storage, and start the test node. | ||
let guard = common::set_tracing_subscriber(); | ||
let storage = TempStorage::new().await?; | ||
|
||
// Start the test node. | ||
let mut node = { | ||
let app_state = AppState::default(); | ||
let consensus = Consensus::new(storage.as_ref().clone()); | ||
TestNode::builder() | ||
.single_validator() | ||
.with_penumbra_auto_app_state(app_state)? | ||
.init_chain(consensus) | ||
.await | ||
}?; | ||
|
||
// Retrieve the validator definition from the latest snapshot. | ||
let Validator { identity_key, .. } = match storage | ||
.latest_snapshot() | ||
.validator_definitions() | ||
.tap(|_| info!("getting validator definitions")) | ||
.await? | ||
.as_slice() | ||
{ | ||
[v] => v.clone(), | ||
unexpected => panic!("there should be one validator, got: {unexpected:?}"), | ||
}; | ||
let get_uptime = || async { | ||
storage | ||
.latest_snapshot() | ||
.get_validator_uptime(&identity_key) | ||
.await | ||
.expect("should be able to get a validator uptime") | ||
.expect("validator uptime should exist") | ||
}; | ||
|
||
// Jump ahead a few blocks. | ||
// TODO TODO TODO have the validator sign blocks here. | ||
let height = 4; | ||
node.fast_forward(height) | ||
.instrument(error_span!("fast forwarding test node {height} blocks")) | ||
.await | ||
.context("fast forwarding {height} blocks")?; | ||
|
||
// Check the validator's uptime once more. We should have uptime data up to the fourth block, | ||
// and the validator should have missed all of the blocks between genesis and now. | ||
{ | ||
let uptime = get_uptime().await; | ||
assert_eq!(uptime.as_of_height(), height); | ||
assert_eq!( | ||
uptime.num_missed_blocks(), | ||
0, | ||
"validator should have signed the last {height} blocks" | ||
); | ||
} | ||
|
||
Ok(()) | ||
.tap(|_| drop(node)) | ||
.tap(|_| drop(storage)) | ||
.tap(|_| drop(guard)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
use { | ||
super::Builder, | ||
crate::TestNode, | ||
sha2::{Digest, Sha256}, | ||
tendermint::{ | ||
abci::types::{BlockSignatureInfo, CommitInfo, VoteInfo}, | ||
account, | ||
block::{BlockIdFlag, Commit, CommitSig, Round}, | ||
vote::Power, | ||
}, | ||
}; | ||
|
||
/// Helper functions for generating [commit signatures]. | ||
mod sign { | ||
use tendermint::{account::Id, block::CommitSig, time::Time}; | ||
|
||
/// Returns a [commit signature] saying this validator voted for the block. | ||
/// | ||
/// [commit signature]: CommitSig | ||
pub(super) fn commit(validator_address: Id) -> CommitSig { | ||
CommitSig::BlockIdFlagCommit { | ||
validator_address, | ||
timestamp: timestamp(), | ||
signature: None, | ||
} | ||
} | ||
|
||
/// Returns a [commit signature] saying this validator voted nil. | ||
/// | ||
/// [commit signature]: CommitSig | ||
#[allow(dead_code)] | ||
pub(super) fn nil(validator_address: Id) -> CommitSig { | ||
CommitSig::BlockIdFlagNil { | ||
validator_address, | ||
timestamp: timestamp(), | ||
signature: None, | ||
} | ||
} | ||
|
||
/// Generates a new timestamp, marked at the current time. | ||
// | ||
// TODO(kate): see https://github.com/penumbra-zone/penumbra/issues/3759, re: timestamps. | ||
// eventually, we will add hooks so that we can control these timestamps. | ||
fn timestamp() -> Time { | ||
Time::now() | ||
} | ||
} | ||
|
||
// === impl TestNode === | ||
|
||
impl<C> TestNode<C> { | ||
// TODO(kate): other interfaces may be helpful to add in the future, and these may eventually | ||
// warrant being made `pub`. we defer doing so for now, only defining what is needed to provide | ||
// commit signatures from all of the validators. | ||
|
||
/// Returns an [`Iterator`] of signatures for validators in the keyring. | ||
pub(super) fn generate_signatures(&self) -> impl Iterator<Item = CommitSig> + '_ { | ||
self.keyring | ||
.iter() | ||
// Compute the address of this validator. | ||
.map(|(vk, _)| -> [u8; 20] { | ||
<Sha256 as Digest>::digest(vk).as_slice()[0..20] | ||
.try_into() | ||
.expect("") | ||
}) | ||
.map(account::Id::new) | ||
.map(self::sign::commit) | ||
} | ||
} | ||
|
||
// === impl Builder === | ||
|
||
impl<'e, C: 'e> Builder<'e, C> { | ||
/// Returns [`CommitInfo`] given a block's [`Commit`]. | ||
pub(super) fn last_commit_info(last_commit: Option<Commit>) -> CommitInfo { | ||
let Some(Commit { | ||
round, signatures, .. | ||
}) = last_commit | ||
else { | ||
// If there is no commit information about the last block, return an empty object. | ||
return CommitInfo { | ||
round: Round::default(), | ||
votes: Vec::default(), | ||
}; | ||
}; | ||
|
||
CommitInfo { | ||
round, | ||
votes: signatures | ||
.into_iter() | ||
.map(Self::vote) | ||
.filter_map(|v| v) | ||
.collect(), | ||
} | ||
} | ||
|
||
/// Returns a [`VoteInfo`] for this [`CommitSig`]. | ||
/// | ||
/// If no validator voted, returns [`None`]. | ||
fn vote(commit_sig: CommitSig) -> Option<VoteInfo> { | ||
use tendermint::abci::types::Validator; | ||
|
||
// TODO(kate): upstream this into the `tendermint` library. | ||
let sig_info = BlockSignatureInfo::Flag(match commit_sig { | ||
CommitSig::BlockIdFlagAbsent => BlockIdFlag::Absent, | ||
CommitSig::BlockIdFlagCommit { .. } => BlockIdFlag::Commit, | ||
CommitSig::BlockIdFlagNil { .. } => BlockIdFlag::Nil, | ||
}); | ||
|
||
let address: [u8; 20] = commit_sig | ||
.validator_address()? | ||
// TODO(kate): upstream an accessor to retrieve this as the [u8; 20] that it is. | ||
.as_bytes() | ||
.try_into() | ||
.expect("validator address should be 20 bytes"); | ||
let power = Power::from(1_u8); // TODO(kate): for now, hard-code voting power to 1. | ||
let validator = Validator { address, power }; | ||
|
||
Some(VoteInfo { | ||
validator, | ||
sig_info, | ||
}) | ||
} | ||
} |