Skip to content

Add run-only option in mithril-end-to-end test #1052

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

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

16 changes: 12 additions & 4 deletions mithril-test-lab/mithril-devnet/devnet-mkfiles.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ NUM_POOL_NODES=$3
SLOT_LENGTH=$4
EPOCH_LENGTH=$5

if [[ "$SKIP_CARDANO_BIN_DOWNLOAD" = "true" ]]; then
SKIP_CARDANO_BIN_DOWNLOAD=true
else
SKIP_CARDANO_BIN_DOWNLOAD=false
fi

SUPPLY=100000000000
NETWORK_MAGIC=42
SECURITY_PARAM=2
Expand Down Expand Up @@ -105,10 +111,12 @@ if ! mkdir -p "${ROOT}"; then
exit
fi

# download cardano-cli & cardano-node
curl -sL ${CARDANO_BINARY_URL} --output cardano-bin.tar.gz
tar xzf cardano-bin.tar.gz ./cardano-cli ./cardano-node
rm -f cardano-bin.tar.gz
# download cardano-cli & cardano-node if enabled (default: yes)
if [ "$SKIP_CARDANO_BIN_DOWNLOAD" = false ]; then
curl -sL ${CARDANO_BINARY_URL} --output cardano-bin.tar.gz
tar xzf cardano-bin.tar.gz ./cardano-cli ./cardano-node
rm -f cardano-bin.tar.gz
fi

# and copy cardano-cli & cardano-node
cp cardano-cli ${ROOT}/cardano-cli
Expand Down
2 changes: 1 addition & 1 deletion mithril-test-lab/mithril-end-to-end/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-end-to-end"
version = "0.1.28"
version = "0.1.29"
authors = { workspace = true }
edition = { workspace = true }
documentation = { workspace = true }
Expand Down
232 changes: 232 additions & 0 deletions mithril-test-lab/mithril-end-to-end/src/assertions/check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
use crate::{
attempt, utils::AttemptResult, Client, ClientCommand, MithrilStakeDistributionCommand,
SnapshotCommand,
};
use mithril_common::{
entities::Epoch,
messages::{
CertificateMessage, MithrilStakeDistributionListMessage, MithrilStakeDistributionMessage,
SnapshotMessage,
},
};
use reqwest::StatusCode;
use slog_scope::info;
use std::{error::Error, time::Duration};

pub async fn assert_node_producing_mithril_stake_distribution(
aggregator_endpoint: &str,
) -> Result<String, String> {
let url = format!("{aggregator_endpoint}/artifact/mithril-stake-distributions");
info!("Waiting for the aggregator to produce a mithril stake distribution");

// todo: reduce the number of attempts if we can reduce the delay between two immutables
match attempt!(45, Duration::from_millis(2000), {
match reqwest::get(url.clone()).await {
Ok(response) => match response.status() {
StatusCode::OK => match response.json::<MithrilStakeDistributionListMessage>().await.as_deref() {
Ok([stake_distribution, ..]) => Ok(Some(stake_distribution.hash.clone())),
Ok(&[]) => Ok(None),
Err(err) => Err(format!("Invalid mithril stake distribution body : {err}",)),
},
s => Err(format!("Unexpected status code from Aggregator: {s}")),
},
Err(err) => Err(format!("Request to `{url}` failed: {err}")),
}
}) {
AttemptResult::Ok(hash) => {
info!("Aggregator produced a mithril stake distribution"; "hash" => &hash);
Ok(hash)
}
AttemptResult::Err(error) => Err(error),
AttemptResult::Timeout() => Err(format!(
"Timeout exhausted assert_node_producing_mithril_stake_distribution, no response from `{url}`"
)),
}
}

pub async fn assert_signer_is_signing_mithril_stake_distribution(
aggregator_endpoint: &str,
hash: &str,
expected_epoch_min: Epoch,
) -> Result<String, String> {
let url = format!("{aggregator_endpoint}/artifact/mithril-stake-distribution/{hash}");
info!(
"Asserting the aggregator is signing the mithril stake distribution message `{}` with an expected min epoch of `{}`",
hash,
expected_epoch_min
);

match attempt!(10, Duration::from_millis(1000), {
match reqwest::get(url.clone()).await {
Ok(response) => match response.status() {
StatusCode::OK => match response.json::<MithrilStakeDistributionMessage>().await {
Ok(stake_distribution) => match stake_distribution.epoch {
epoch if epoch >= expected_epoch_min => Ok(Some(stake_distribution)),
epoch => Err(format!(
"Minimum expected mithril stake distribution epoch not reached : {epoch} < {expected_epoch_min}"
)),
},
Err(err) => Err(format!("Invalid mithril stake distribution body : {err}",)),
},
StatusCode::NOT_FOUND => Ok(None),
s => Err(format!("Unexpected status code from Aggregator: {s}")),
},
Err(err) => Err(format!("Request to `{url}` failed: {err}")),
}
}) {
AttemptResult::Ok(stake_distribution) => {
// todo: assert that the mithril stake distribution is really signed
info!("Signer signed a mithril stake distribution"; "certificate_hash" => &stake_distribution.certificate_hash);
Ok(stake_distribution.certificate_hash)
}
AttemptResult::Err(error) => Err(error),
AttemptResult::Timeout() => Err(format!(
"Timeout exhausted assert_signer_is_signing_mithril_stake_distribution, no response from `{url}`"
)),
}
}

pub async fn assert_node_producing_snapshot(aggregator_endpoint: &str) -> Result<String, String> {
let url = format!("{aggregator_endpoint}/artifact/snapshots");
info!("Waiting for the aggregator to produce a snapshot");

// todo: reduce the number of attempts if we can reduce the delay between two immutables
match attempt!(45, Duration::from_millis(2000), {
match reqwest::get(url.clone()).await {
Ok(response) => match response.status() {
StatusCode::OK => match response.json::<Vec<SnapshotMessage>>().await.as_deref() {
Ok([snapshot, ..]) => Ok(Some(snapshot.digest.clone())),
Ok(&[]) => Ok(None),
Err(err) => Err(format!("Invalid snapshot body : {err}",)),
},
s => Err(format!("Unexpected status code from Aggregator: {s}")),
},
Err(err) => Err(format!("Request to `{url}` failed: {err}")),
}
}) {
AttemptResult::Ok(digest) => {
info!("Aggregator produced a snapshot"; "digest" => &digest);
Ok(digest)
}
AttemptResult::Err(error) => Err(error),
AttemptResult::Timeout() => Err(format!(
"Timeout exhausted assert_node_producing_snapshot, no response from `{url}`"
)),
}
}

pub async fn assert_signer_is_signing_snapshot(
aggregator_endpoint: &str,
digest: &str,
expected_epoch_min: Epoch,
) -> Result<String, String> {
let url = format!("{aggregator_endpoint}/artifact/snapshot/{digest}");
info!(
"Asserting the aggregator is signing the snapshot message `{}` with an expected min epoch of `{}`",
digest,
expected_epoch_min
);

match attempt!(10, Duration::from_millis(1000), {
match reqwest::get(url.clone()).await {
Ok(response) => match response.status() {
StatusCode::OK => match response.json::<SnapshotMessage>().await {
Ok(snapshot) => match snapshot.beacon.epoch {
epoch if epoch >= expected_epoch_min => Ok(Some(snapshot)),
epoch => Err(format!(
"Minimum expected snapshot epoch not reached : {epoch} < {expected_epoch_min}"
)),
},
Err(err) => Err(format!("Invalid snapshot body : {err}",)),
},
StatusCode::NOT_FOUND => Ok(None),
s => Err(format!("Unexpected status code from Aggregator: {s}")),
},
Err(err) => Err(format!("Request to `{url}` failed: {err}")),
}
}) {
AttemptResult::Ok(snapshot) => {
// todo: assert that the snapshot is really signed
info!("Signer signed a snapshot"; "certificate_hash" => &snapshot.certificate_hash);
Ok(snapshot.certificate_hash)
}
AttemptResult::Err(error) => Err(error),
AttemptResult::Timeout() => Err(format!(
"Timeout exhausted assert_signer_is_signing_snapshot, no response from `{url}`"
)),
}
}

pub async fn assert_is_creating_certificate_with_enough_signers(
aggregator_endpoint: &str,
certificate_hash: &str,
total_signers_expected: usize,
) -> Result<(), String> {
let url = format!("{aggregator_endpoint}/certificate/{certificate_hash}");

match attempt!(10, Duration::from_millis(1000), {
match reqwest::get(url.clone()).await {
Ok(response) => match response.status() {
StatusCode::OK => match response.json::<CertificateMessage>().await {
Ok(certificate) => Ok(Some(certificate)),
Err(err) => Err(format!("Invalid snapshot body : {err}",)),
},
StatusCode::NOT_FOUND => Ok(None),
s => Err(format!("Unexpected status code from Aggregator: {s}")),
},
Err(err) => Err(format!("Request to `{url}` failed: {err}")),
}
}) {
AttemptResult::Ok(certificate) => {
info!("Aggregator produced a certificate"; "certificate" => #?certificate);
if certificate.metadata.signers.len() == total_signers_expected {
info!(
"Certificate is signed by expected number of signers: {} >= {} ",
certificate.metadata.signers.len(),
total_signers_expected
);
Ok(())
} else {
Err(format!(
"Certificate is not signed by expected number of signers: {} < {} ",
certificate.metadata.signers.len(),
total_signers_expected
))
}
}
AttemptResult::Err(error) => Err(error),
AttemptResult::Timeout() => Err(format!(
"Timeout exhausted assert_is_creating_certificate, no response from `{url}`"
)),
}
}

pub async fn assert_client_can_verify_snapshot(
client: &mut Client,
digest: &str,
) -> Result<(), String> {
client
.run(ClientCommand::Snapshot(SnapshotCommand::Download {
digest: digest.to_string(),
}))
.await?;
info!("Client downloaded & restored the snapshot"; "digest" => &digest);

Ok(())
}

pub async fn assert_client_can_verify_mithril_stake_distribution(
client: &mut Client,
hash: &str,
) -> Result<(), Box<dyn Error>> {
client
.run(ClientCommand::MithrilStakeDistribution(
MithrilStakeDistributionCommand::Download {
hash: hash.to_owned(),
},
))
.await?;
info!("Client downloaded the Mithril stake distribution"; "hash" => &hash);

Ok(())
}
45 changes: 45 additions & 0 deletions mithril-test-lab/mithril-end-to-end/src/assertions/exec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use crate::{Aggregator, Devnet};
use mithril_common::entities::ProtocolParameters;
use slog_scope::info;

pub async fn bootstrap_genesis_certificate(aggregator: &mut Aggregator) -> Result<(), String> {
info!("Bootstrap genesis certificate");

info!("> stopping aggregator");
aggregator.stop().await?;
info!("> bootstrapping genesis using signers registered two epochs ago...");
aggregator.bootstrap_genesis().await?;
info!("> done, restarting aggregator");
aggregator.serve()?;

Ok(())
}

pub async fn delegate_stakes_to_pools(devnet: &Devnet) -> Result<(), String> {
info!("Delegate stakes to the cardano pools");

devnet.delegate_stakes().await?;

Ok(())
}

pub async fn update_protocol_parameters(aggregator: &mut Aggregator) -> Result<(), String> {
info!("Update protocol parameters");

info!("> stopping aggregator");
aggregator.stop().await?;
let protocol_parameters_new = ProtocolParameters {
k: 150,
m: 210,
phi_f: 0.80,
};
info!(
"> updating protocol parameters to {:?}...",
protocol_parameters_new
);
aggregator.set_protocol_parameters(&protocol_parameters_new);
info!("> done, restarting aggregator");
aggregator.serve()?;

Ok(())
}
7 changes: 7 additions & 0 deletions mithril-test-lab/mithril-end-to-end/src/assertions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod check;
mod exec;
mod wait;

pub use check::*;
pub use exec::*;
pub use wait::*;
Loading