From 792f3c1e78287e90a532a24a0ec272d4a13ba14c Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:43:52 +0200 Subject: [PATCH 01/19] refactor(e2): restructure `Cli` arguments into modular substructures * rename `Args` to `Cli` to avoid name colision with `clap::Args` * restructured args are flatten in to the main `Cli` struct, meaning that there's no changes to the cli interface --- .../mithril-end-to-end/src/main.rs | 236 ++++++++++-------- 1 file changed, 133 insertions(+), 103 deletions(-) diff --git a/mithril-test-lab/mithril-end-to-end/src/main.rs b/mithril-test-lab/mithril-end-to-end/src/main.rs index fcf7c8b8fec..397d3087f41 100644 --- a/mithril-test-lab/mithril-end-to-end/src/main.rs +++ b/mithril-test-lab/mithril-end-to-end/src/main.rs @@ -1,5 +1,5 @@ use anyhow::{Context, anyhow}; -use clap::{CommandFactory, Parser, Subcommand}; +use clap::{Args, CommandFactory, Parser, Subcommand}; use slog::{Drain, Level, Logger}; use slog_scope::{error, info}; use std::{ @@ -27,7 +27,7 @@ use mithril_end_to_end::{ /// Tests args #[derive(Parser, Debug, Clone)] -pub struct Args { +pub struct Cli { /// Available commands #[command(subcommand)] command: Option, @@ -41,10 +41,6 @@ pub struct Args { #[clap(long)] work_directory: Option, - /// Directory containing scripts to bootstrap a devnet - #[clap(long, default_value = "./devnet")] - devnet_scripts_directory: PathBuf, - /// Directory to the mithril binaries /// /// It must contains the binaries of the aggregator, signer and client. @@ -53,6 +49,41 @@ pub struct Args { #[clap(long, default_value = ".")] bin_directory: PathBuf, + #[command(flatten)] + cardano_devnet: CardanoDevnetArgs, + + #[command(flatten)] + mithril: MithrilArgs, + + #[command(flatten)] + network_topology: NetworkTopologyArgs, + + #[command(flatten)] + scenario: ScenarioArgs, + + /// Verbosity level + #[clap( + short, + long, + action = clap::ArgAction::Count, + help = "Verbosity level, add more v to increase" + )] + verbose: u8, +} + +#[derive(Args, Debug, Clone)] +struct ScenarioArgs { + /// Enable 'run-only' mode + #[clap(long)] + run_only: bool, + + /// Will check the ledger snapshot conversion step using utxo-hd snapshot-converter + #[clap(long)] + check_client_cli_snapshot_converter: bool, +} + +#[derive(Args, Debug, Clone)] +struct NetworkTopologyArgs { /// Number of aggregators #[clap(long, default_value_t = 1, value_parser = clap::value_parser!(u8).range(1..))] number_of_aggregators: u8, @@ -61,22 +92,40 @@ pub struct Args { #[clap(long, default_value_t = 2, value_parser = clap::value_parser!(u8).range(1..))] number_of_signers: u8, - /// Length of a Cardano slot in the devnet (in s) - #[clap(long, default_value_t = 0.10)] - cardano_slot_length: f64, + /// Use Mithril relays + #[clap(long)] + use_relays: bool, - /// Length of a Cardano epoch in the devnet (in s) - #[clap(long, default_value_t = 30.0)] - cardano_epoch_length: f64, + /// Signer registration relay mode (used only when 'use_relays' is set, can be 'passthrough' or 'p2p') + #[clap(long, default_value = "passthrough")] + relay_signer_registration_mode: String, - /// Cardano node version, must be a valid semver version - #[clap(long, default_value = "11.0.1")] - cardano_node_version: semver::Version, + /// Signature registration relay mode (used only when 'use_relays' is set, can be 'passthrough' or 'p2p') + #[clap(long, default_value = "p2p")] + relay_signature_registration_mode: String, - /// Epoch at which hard fork to the latest Cardano era will be made (starts with the latest era by default) - #[clap(long, default_value_t = 0)] - cardano_hard_fork_latest_era_at_epoch: u16, + /// Enable P2P passive relays in P2P mode (used only when 'use_relays' is set) + #[clap(long, default_value = "false")] + use_p2p_passive_relays: bool, + + /// Use DMQ protocol (used to broadcast signatures) + #[clap(long)] + use_dmq: bool, + + /// DMQ node flavor (used only when 'use_dmq' is set, can be 'haskell' or 'fake') + /// + /// 'haskell': will use the DMQ network created within the 'cardano-devnet' + /// 'fake': will use a fake DMQ network within created with the Mithril relay + #[arg(long, value_enum, default_value = "haskell")] + dmq_node_flavor: Option, + + /// Haskell DMQ node version + #[clap(long)] + dmq_node_version: Option, +} +#[derive(Args, Debug, Clone)] +struct MithrilArgs { /// Mithril run interval for nodes (in ms) #[clap(long, default_value_t = 125)] mithril_run_interval: u32, @@ -109,48 +158,32 @@ pub struct Args { #[clap(long, value_enum, default_value = "Concatenation")] aggregate_signature_type: AggregateSignatureType, - /// Enable run only mode - #[clap(long)] - run_only: bool, - - /// Will check the ledger snapshot conversion step using utxo-hd snapshot-converter - #[clap(long)] - check_client_cli_snapshot_converter: bool, - - /// Use Mithril relays + /// Skip the signature delayer in mithril-signer #[clap(long)] - use_relays: bool, - - /// Signer registration relay mode (used only when 'use_relays' is set, can be 'passthrough' or 'p2p') - #[clap(long, default_value = "passthrough")] - relay_signer_registration_mode: String, - - /// Signature registration relay mode (used only when 'use_relays' is set, can be 'passthrough' or 'p2p') - #[clap(long, default_value = "p2p")] - relay_signature_registration_mode: String, + skip_signature_delayer: bool, +} - /// Enable P2P passive relays in P2P mode (used only when 'use_relays' is set) - #[clap(long, default_value = "false")] - use_p2p_passive_relays: bool, +#[derive(Args, Debug, Clone)] +struct CardanoDevnetArgs { + /// Directory containing scripts to bootstrap a devnet + #[clap(long, default_value = "./devnet")] + devnet_scripts_directory: PathBuf, - /// Skip the signature delayer - #[clap(long)] - skip_signature_delayer: bool, + /// Length of a Cardano slot in the devnet (in s) + #[clap(long, default_value_t = 0.10)] + cardano_slot_length: f64, - /// Use DMQ protocol (used to broadcast signatures) - #[clap(long)] - use_dmq: bool, + /// Length of a Cardano epoch in the devnet (in s) + #[clap(long, default_value_t = 30.0)] + cardano_epoch_length: f64, - /// DMQ node flavor (used only when 'use_dmq' is set, can be 'haskell' or 'fake') - /// - /// 'haskell': will use the DMQ network created within the 'cardano-devnet' - /// 'fake': will use a fake DMQ network within created with the Mithril relay - #[arg(long, value_enum, default_value = "haskell")] - dmq_node_flavor: Option, + /// Cardano node version, must be a valid semver version + #[clap(long, default_value = "11.0.1")] + cardano_node_version: semver::Version, - /// Haskell DMQ node version - #[clap(long)] - dmq_node_version: Option, + /// Epoch at which hard fork to the latest Cardano era will be made (starts with the latest era by default) + #[clap(long, default_value_t = 0)] + cardano_hard_fork_latest_era_at_epoch: u16, /// Skip cardano binaries download /// (will use the ones in the `bin` folder of the `Args::devnet_scripts_directory`, which defaults to `./devnet/bin`) @@ -160,18 +193,9 @@ pub struct Args { /// URL to download cardano binaries from (if not set, it will default to the official cardano releases) #[clap(long)] cardano_binary_url: Option, - - /// Verbosity level - #[clap( - short, - long, - action = clap::ArgAction::Count, - help = "Verbosity level, add more v to increase" - )] - verbose: u8, } -impl Args { +impl Cli { fn log_level(&self) -> Level { match self.verbose { 0 => Level::Error, @@ -183,7 +207,7 @@ impl Args { } fn validate(&self) -> StdResult<()> { - if !self.use_relays && self.number_of_aggregators >= 2 { + if !self.network_topology.use_relays && self.network_topology.number_of_aggregators >= 2 { return Err(anyhow!( "The 'use_relays' parameter must be activated to run more than one aggregator" )); @@ -209,11 +233,11 @@ fn main() -> AppResult { } async fn main_exec() -> StdResult<()> { - let args = Args::parse(); + let args = Cli::parse(); let _guard = slog_scope::set_global_logger(build_logger(&args)); if let Some(EndToEndCommands::GenerateDoc(cmd)) = &args.command { - return cmd.execute(&mut Args::command()).map_err(|message| anyhow!(message)); + return cmd.execute(&mut Cli::command()).map_err(|message| anyhow!(message)); } let work_dir = { @@ -360,21 +384,22 @@ impl App { pub async fn run( &mut self, - args: Args, + args: Cli, work_dir: PathBuf, store_dir: PathBuf, artifacts_dir: PathBuf, ) -> StdResult<()> { let server_port = 8080; args.validate()?; - let run_only_mode = args.run_only; - let check_client_cli_snapshot_converter = args.check_client_cli_snapshot_converter; - let use_relays = args.use_relays; - let relay_signer_registration_mode = args.relay_signer_registration_mode; - let relay_signature_registration_mode = args.relay_signature_registration_mode; - let use_dmq = args.use_dmq; + let run_only_mode = args.scenario.run_only; + let check_client_cli_snapshot_converter = args.scenario.check_client_cli_snapshot_converter; + let use_relays = args.network_topology.use_relays; + let relay_signer_registration_mode = args.network_topology.relay_signer_registration_mode; + let relay_signature_registration_mode = + args.network_topology.relay_signature_registration_mode; + let use_dmq = args.network_topology.use_dmq; - let use_p2p_passive_relays = args.use_p2p_passive_relays; + let use_p2p_passive_relays = args.network_topology.use_p2p_passive_relays; CompatibilityChecker::default().check(BTreeMap::from([ ( @@ -393,51 +418,56 @@ impl App { RelaySigner::BIN_NAME, NodeVersion::fetch_semver(RelaySigner::BIN_NAME, &args.bin_directory)?, ), - ("cardano-node", args.cardano_node_version.to_owned()), + ( + "cardano-node", + args.cardano_devnet.cardano_node_version.to_owned(), + ), ]))?; let devnet = Devnet::bootstrap(&DevnetBootstrapArgs { - devnet_scripts_dir: args.devnet_scripts_directory, + devnet_scripts_dir: args.cardano_devnet.devnet_scripts_directory, artifacts_target_dir: work_dir.join("devnet"), - number_of_pool_nodes: args.number_of_signers, - number_of_full_nodes: args.number_of_aggregators, - cardano_slot_length: args.cardano_slot_length, - cardano_epoch_length: args.cardano_epoch_length, - cardano_node_version: args.cardano_node_version.to_owned(), - dmq_node_version: args.dmq_node_version.clone(), - cardano_hard_fork_latest_era_at_epoch: args.cardano_hard_fork_latest_era_at_epoch, - skip_cardano_bin_download: args.skip_cardano_bin_download, - cardano_binary_url: args.cardano_binary_url.clone(), + number_of_pool_nodes: args.network_topology.number_of_signers, + number_of_full_nodes: args.network_topology.number_of_aggregators, + cardano_slot_length: args.cardano_devnet.cardano_slot_length, + cardano_epoch_length: args.cardano_devnet.cardano_epoch_length, + cardano_node_version: args.cardano_devnet.cardano_node_version.to_owned(), + dmq_node_version: args.network_topology.dmq_node_version.clone(), + cardano_hard_fork_latest_era_at_epoch: args + .cardano_devnet + .cardano_hard_fork_latest_era_at_epoch, + skip_cardano_bin_download: args.cardano_devnet.skip_cardano_bin_download, + cardano_binary_url: args.cardano_devnet.cardano_binary_url.clone(), }) .await?; *self.devnet.lock().await = Some(devnet.clone()); let infrastructure = Arc::new( MithrilInfrastructure::start(&MithrilInfrastructureConfig { - number_of_aggregators: args.number_of_aggregators, - number_of_signers: args.number_of_signers, + number_of_aggregators: args.network_topology.number_of_aggregators, + number_of_signers: args.network_topology.number_of_signers, server_port, devnet: devnet.clone(), work_dir, store_dir, artifacts_dir, bin_dir: args.bin_directory, - cardano_node_version: args.cardano_node_version, - mithril_run_interval: args.mithril_run_interval, - mithril_era: args.mithril_era, - mithril_era_reader_adapter: args.mithril_era_reader_adapter, - signed_entity_types: args.signed_entity_types.clone(), - aggregate_signature_type: args.aggregate_signature_type, + cardano_node_version: args.cardano_devnet.cardano_node_version, + mithril_run_interval: args.mithril.mithril_run_interval, + mithril_era: args.mithril.mithril_era, + mithril_era_reader_adapter: args.mithril.mithril_era_reader_adapter, + signed_entity_types: args.mithril.signed_entity_types.clone(), + aggregate_signature_type: args.mithril.aggregate_signature_type, run_only_mode, check_client_cli_snapshot_converter, use_dmq, - dmq_node_flavor: args.dmq_node_flavor, + dmq_node_flavor: args.network_topology.dmq_node_flavor, use_relays, relay_signer_registration_mode, relay_signature_registration_mode, - skip_signature_delayer: args.skip_signature_delayer, + skip_signature_delayer: args.mithril.skip_signature_delayer, use_p2p_passive_relays, - use_era_specific_work_dir: args.mithril_next_era.is_some(), + use_era_specific_work_dir: args.mithril.mithril_next_era.is_some(), }) .await?, ); @@ -448,9 +478,9 @@ impl App { false => { Spec::new( infrastructure, - args.signed_entity_types, - args.mithril_next_era, - args.mithril_era_regenesis_on_switch, + args.mithril.signed_entity_types, + args.mithril.mithril_next_era, + args.mithril.mithril_era_regenesis_on_switch, ) .run() .await @@ -501,7 +531,7 @@ impl AppStopper { } } -fn build_logger(args: &Args) -> Logger { +fn build_logger(args: &Cli) -> Logger { let decorator = slog_term::TermDecorator::new().build(); let drain = slog_term::FullFormat::new(decorator).build().fuse(); let drain = slog::LevelFilter::new(drain, args.log_level()).fuse(); @@ -589,12 +619,12 @@ mod tests { #[test] fn args_fails_validation() { - let args = Args::parse_from(["", "--number-of-aggregators", "2"]); + let args = Cli::parse_from(["", "--number-of-aggregators", "2"]); args.validate().expect_err( "validate should fail with more than one aggregator if p2p network is not used", ); - let args = Args::parse_from(["", "--use-relays", "--number-of-aggregators", "2"]); + let args = Cli::parse_from(["", "--use-relays", "--number-of-aggregators", "2"]); args.validate() .expect("validate should succeed with more than one aggregator if p2p network is used"); } From 815ea861a9ddddfabd16c42c2a1f195844095af9 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 2 Jun 2026 17:30:40 +0200 Subject: [PATCH 02/19] refactor(e2e): restructure scenarios into `scenario` module and rename for clarity * introduced a new `scenario` module to group related logic * moved `Spec` to `FullScenario` and `RunOnly` to `RunOnlyScenario` --- mithril-test-lab/mithril-end-to-end/src/lib.rs | 5 +---- mithril-test-lab/mithril-end-to-end/src/main.rs | 7 ++++--- .../src/{end_to_end_spec.rs => scenario/full.rs} | 4 ++-- mithril-test-lab/mithril-end-to-end/src/scenario/mod.rs | 5 +++++ .../mithril-end-to-end/src/{ => scenario}/run_only.rs | 4 ++-- 5 files changed, 14 insertions(+), 11 deletions(-) rename mithril-test-lab/mithril-end-to-end/src/{end_to_end_spec.rs => scenario/full.rs} (99%) create mode 100644 mithril-test-lab/mithril-end-to-end/src/scenario/mod.rs rename mithril-test-lab/mithril-end-to-end/src/{ => scenario}/run_only.rs (97%) diff --git a/mithril-test-lab/mithril-end-to-end/src/lib.rs b/mithril-test-lab/mithril-end-to-end/src/lib.rs index 68f63c6ec68..983a5e858a9 100644 --- a/mithril-test-lab/mithril-end-to-end/src/lib.rs +++ b/mithril-test-lab/mithril-end-to-end/src/lib.rs @@ -1,15 +1,12 @@ pub mod assertions; mod devnet; -mod end_to_end_spec; mod mithril; -mod run_only; +pub mod scenario; pub mod stress_test; mod utils; pub use devnet::*; -pub use end_to_end_spec::Spec; pub use mithril::*; -pub use run_only::RunOnly; pub use utils::{CompatibilityChecker, CompatibilityCheckerError, NodeVersion}; use clap::ValueEnum; diff --git a/mithril-test-lab/mithril-end-to-end/src/main.rs b/mithril-test-lab/mithril-end-to-end/src/main.rs index 397d3087f41..6b68b7d6eb9 100644 --- a/mithril-test-lab/mithril-end-to-end/src/main.rs +++ b/mithril-test-lab/mithril-end-to-end/src/main.rs @@ -19,10 +19,11 @@ use tokio::{ use mithril_common::StdResult; use mithril_doc::GenerateDocCommands; +use mithril_end_to_end::scenario::{FullScenario, RunOnlyScenario}; use mithril_end_to_end::{ AggregateSignatureType, Aggregator, Client, CompatibilityChecker, CompatibilityCheckerError, Devnet, DevnetBootstrapArgs, DmqNodeFlavor, MithrilInfrastructure, MithrilInfrastructureConfig, - NodeVersion, RelaySigner, RetryableDevnetError, RunOnly, Signer, Spec, + NodeVersion, RelaySigner, RetryableDevnetError, Signer, }; /// Tests args @@ -474,9 +475,9 @@ impl App { *self.infrastructure.lock().await = Some(infrastructure.clone()); let runner: StdResult<()> = match run_only_mode { - true => RunOnly::new(infrastructure).run().await, + true => RunOnlyScenario::new(infrastructure).run().await, false => { - Spec::new( + FullScenario::new( infrastructure, args.mithril.signed_entity_types, args.mithril.mithril_next_era, diff --git a/mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs b/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs similarity index 99% rename from mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs rename to mithril-test-lab/mithril-end-to-end/src/scenario/full.rs index 5a9f948cbe2..4767e87ed98 100644 --- a/mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs +++ b/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs @@ -16,7 +16,7 @@ use crate::{ }, }; -pub struct Spec { +pub struct FullScenario { pub infrastructure: Arc, is_signing_cardano_transactions: bool, is_signing_cardano_blocks_transactions: bool, @@ -26,7 +26,7 @@ pub struct Spec { regenesis_on_era_switch: bool, } -impl Spec { +impl FullScenario { pub fn new( infrastructure: Arc, signed_entity_types: Vec, diff --git a/mithril-test-lab/mithril-end-to-end/src/scenario/mod.rs b/mithril-test-lab/mithril-end-to-end/src/scenario/mod.rs new file mode 100644 index 00000000000..139d5db22b0 --- /dev/null +++ b/mithril-test-lab/mithril-end-to-end/src/scenario/mod.rs @@ -0,0 +1,5 @@ +mod full; +mod run_only; + +pub use full::FullScenario; +pub use run_only::RunOnlyScenario; diff --git a/mithril-test-lab/mithril-end-to-end/src/run_only.rs b/mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs similarity index 97% rename from mithril-test-lab/mithril-end-to-end/src/run_only.rs rename to mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs index abcedfa59c2..3585c6d2b3c 100644 --- a/mithril-test-lab/mithril-end-to-end/src/run_only.rs +++ b/mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs @@ -6,11 +6,11 @@ use mithril_common::StdResult; use crate::{MithrilInfrastructure, assertions}; -pub struct RunOnly { +pub struct RunOnlyScenario { pub infrastructure: Arc, } -impl RunOnly { +impl RunOnlyScenario { pub fn new(infrastructure: Arc) -> Self { Self { infrastructure } } From e45e30a079c0c873c8a0467b936670a99586a4e1 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Thu, 4 Jun 2026 18:02:43 +0200 Subject: [PATCH 03/19] refactor(e2e): rename `assertions` module to `toolkit` First step of the refactor which will move those functions into a type which will hold the test context. --- .../mithril-end-to-end/src/lib.rs | 2 +- .../src/mithril/infrastructure.rs | 7 +- .../mithril-end-to-end/src/scenario/full.rs | 92 +++++++++---------- .../src/scenario/run_only.rs | 12 +-- .../src/{assertions => toolkit}/check.rs | 0 .../src/{assertions => toolkit}/exec.rs | 0 .../src/{assertions => toolkit}/mod.rs | 0 .../src/{assertions => toolkit}/wait.rs | 0 8 files changed, 53 insertions(+), 60 deletions(-) rename mithril-test-lab/mithril-end-to-end/src/{assertions => toolkit}/check.rs (100%) rename mithril-test-lab/mithril-end-to-end/src/{assertions => toolkit}/exec.rs (100%) rename mithril-test-lab/mithril-end-to-end/src/{assertions => toolkit}/mod.rs (100%) rename mithril-test-lab/mithril-end-to-end/src/{assertions => toolkit}/wait.rs (100%) diff --git a/mithril-test-lab/mithril-end-to-end/src/lib.rs b/mithril-test-lab/mithril-end-to-end/src/lib.rs index 983a5e858a9..8a74c334439 100644 --- a/mithril-test-lab/mithril-end-to-end/src/lib.rs +++ b/mithril-test-lab/mithril-end-to-end/src/lib.rs @@ -1,8 +1,8 @@ -pub mod assertions; mod devnet; mod mithril; pub mod scenario; pub mod stress_test; +pub mod toolkit; mod utils; pub use devnet::*; diff --git a/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs b/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs index 92261f7aaf5..28ab2e4c574 100644 --- a/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs +++ b/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs @@ -11,8 +11,7 @@ use mithril_common::{CardanoNetwork, StdResult}; use crate::mithril::relay_signer::RelaySignerConfiguration; use crate::{ AggregateSignatureType, Aggregator, AggregatorConfig, Client, DEVNET_MAGIC_ID, Devnet, - DmqNodeFlavor, FullNode, PoolNode, RelayAggregator, RelayPassive, RelaySigner, Signer, - assertions, + DmqNodeFlavor, FullNode, PoolNode, RelayAggregator, RelayPassive, RelaySigner, Signer, toolkit, }; use super::signer::SignerConfig; @@ -183,7 +182,7 @@ impl MithrilInfrastructure { ) -> StdResult<()> { let era_epoch = Epoch(0); if config.mithril_era_reader_adapter == "cardano-chain" { - assertions::register_era_marker( + toolkit::register_era_marker( aggregator, &config.devnet, &config.mithril_era, @@ -204,7 +203,7 @@ impl MithrilInfrastructure { + 1; if self.era_reader_adapter == "cardano-chain" { let devnet = self.devnet.clone(); - assertions::register_era_marker( + toolkit::register_era_marker( self.leader_aggregator(), &devnet, next_era, diff --git a/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs b/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs index 4767e87ed98..7fe080a2cb0 100644 --- a/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs +++ b/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs @@ -9,7 +9,7 @@ use mithril_common::{ }; use crate::{ - Aggregator, MithrilInfrastructure, NodeVersion, assertions, + Aggregator, MithrilInfrastructure, NodeVersion, toolkit, utils::{ randomly_take_blocks_hashes, randomly_take_transactions_hashes, retrieve_blocks_transactions_from_immutable_files, @@ -71,7 +71,7 @@ impl FullScenario { // This step needs to be executed early in the process so that the transactions are available // for signing in the penultimate immutable chunk before the end of the test. // As we get closer to the tip of the chain when signing, we'll be able to relax this constraint. - assertions::transfer_funds(spec.infrastructure.devnet()).await?; + toolkit::transfer_funds(spec.infrastructure.devnet()).await?; info!("Bootstrapping leader aggregator"); spec.bootstrap_leader_aggregator(&spec.infrastructure).await?; @@ -106,25 +106,25 @@ impl FullScenario { ) -> StdResult<()> { let leader_aggregator = infrastructure.leader_aggregator(); - assertions::wait_for_enough_immutable(leader_aggregator).await?; + toolkit::wait_for_enough_immutable(leader_aggregator).await?; let chain_observer = leader_aggregator.chain_observer(); let start_epoch = chain_observer.get_current_epoch().await?.unwrap_or_default(); // Wait 4 epochs after start epoch for the aggregator to be able to bootstrap a genesis certificate let mut target_epoch = start_epoch + 4; - assertions::wait_for_aggregator_at_target_epoch( + toolkit::wait_for_aggregator_at_target_epoch( leader_aggregator, target_epoch, "minimal epoch for the aggregator to be able to bootstrap genesis certificate" .to_string(), ) .await?; - assertions::bootstrap_genesis_certificate(leader_aggregator).await?; - assertions::wait_for_epoch_settings(leader_aggregator).await?; + toolkit::bootstrap_genesis_certificate(leader_aggregator).await?; + toolkit::wait_for_epoch_settings(leader_aggregator).await?; // Wait 2 epochs before changing stake distribution, so that we use at least one original stake distribution target_epoch += 2; - assertions::wait_for_aggregator_at_target_epoch( + toolkit::wait_for_aggregator_at_target_epoch( leader_aggregator, target_epoch, "epoch after which the stake distribution will change".to_string(), @@ -133,7 +133,7 @@ impl FullScenario { // Delegate some stakes to pools let delegation_round = 1; - assertions::delegate_stakes_to_pools(infrastructure.devnet(), delegation_round).await?; + toolkit::delegate_stakes_to_pools(infrastructure.devnet(), delegation_round).await?; Ok(()) } @@ -148,7 +148,7 @@ impl FullScenario { // Wait 2 epochs before changing protocol parameters let mut target_epoch = start_epoch + 2; - assertions::wait_for_aggregator_at_target_epoch( + toolkit::wait_for_aggregator_at_target_epoch( aggregator, target_epoch, "epoch after which the protocol parameters will change".to_string(), @@ -156,7 +156,7 @@ impl FullScenario { .await?; if aggregator.is_first() || aggregator.version().is_below("0.7.94") { - assertions::update_protocol_parameters( + toolkit::update_protocol_parameters( aggregator, infrastructure.aggregate_signature_type(), ) @@ -165,7 +165,7 @@ impl FullScenario { // Wait 6 epochs after protocol parameters update, so that we make sure that we use new protocol parameters as well as new stake distribution a few times target_epoch += 6; - assertions::wait_for_aggregator_at_target_epoch( + toolkit::wait_for_aggregator_at_target_epoch( aggregator, target_epoch, "epoch after which the certificate chain will be long enough to catch most common troubles with stake distribution and protocol parameters".to_string(), @@ -184,7 +184,7 @@ impl FullScenario { infrastructure.register_switch_to_next_era(next_era).await?; } target_epoch += 5; - assertions::wait_for_aggregator_at_target_epoch( + toolkit::wait_for_aggregator_at_target_epoch( aggregator, target_epoch, "epoch after which the era switch will have triggered".to_string(), @@ -193,9 +193,9 @@ impl FullScenario { // Proceed to a re-genesis of the certificate chain if self.regenesis_on_era_switch { - assertions::bootstrap_genesis_certificate(aggregator).await?; + toolkit::bootstrap_genesis_certificate(aggregator).await?; target_epoch += 5; - assertions::wait_for_aggregator_at_target_epoch( + toolkit::wait_for_aggregator_at_target_epoch( aggregator, target_epoch, "epoch after which the re-genesis on era switch will be completed".to_string(), @@ -211,7 +211,7 @@ impl FullScenario { // Check the ledger snapshot conversion step using utxo-hd snapshot-converter if infrastructure.check_client_cli_snapshot_converter() { let mut client = infrastructure.build_client(aggregator).await?; - assertions::assert_client_can_convert_the_ledger_snapshot( + toolkit::assert_client_can_convert_the_ledger_snapshot( &mut client, aggregator.full_node(), infrastructure.devnet().artifacts_dir(), @@ -240,59 +240,58 @@ impl FullScenario { // Verify that mithril stake distribution artifacts are produced and signed correctly { let hash = - assertions::assert_node_producing_mithril_stake_distribution(aggregator).await?; - let certificate_hash = assertions::assert_signer_is_signing_mithril_stake_distribution( + toolkit::assert_node_producing_mithril_stake_distribution(aggregator).await?; + let certificate_hash = toolkit::assert_signer_is_signing_mithril_stake_distribution( aggregator, &hash, expected_epoch_min, ) .await?; - assertions::assert_is_creating_certificate_with_enough_signers( + toolkit::assert_is_creating_certificate_with_enough_signers( aggregator, &certificate_hash, infrastructure.signers().len(), ) .await?; let mut client = infrastructure.build_client(aggregator).await?; - assertions::assert_client_can_verify_mithril_stake_distribution(&mut client, &hash) + toolkit::assert_client_can_verify_mithril_stake_distribution(&mut client, &hash) .await?; } // Verify that Cardano database snapshot artifacts are produced and signed correctly if self.is_signing_cardano_database { - let hash = - assertions::assert_node_producing_cardano_database_snapshot(aggregator).await?; - let certificate_hash = assertions::assert_signer_is_signing_cardano_database_snapshot( + let hash = toolkit::assert_node_producing_cardano_database_snapshot(aggregator).await?; + let certificate_hash = toolkit::assert_signer_is_signing_cardano_database_snapshot( aggregator, &hash, expected_epoch_min, ) .await?; - assertions::assert_is_creating_certificate_with_enough_signers( + toolkit::assert_is_creating_certificate_with_enough_signers( aggregator, &certificate_hash, infrastructure.signers().len(), ) .await?; - assertions::assert_node_producing_cardano_database_digests_map(aggregator).await?; + toolkit::assert_node_producing_cardano_database_digests_map(aggregator).await?; let mut client = infrastructure.build_client(aggregator).await?; - assertions::assert_client_can_verify_cardano_database(&mut client, &hash).await?; + toolkit::assert_client_can_verify_cardano_database(&mut client, &hash).await?; } // Verify that Cardano transactions artifacts are produced and signed correctly if self.is_signing_cardano_transactions { - let hash = assertions::assert_node_producing_cardano_transactions(aggregator).await?; - let certificate_hash = assertions::assert_signer_is_signing_cardano_transactions( + let hash = toolkit::assert_node_producing_cardano_transactions(aggregator).await?; + let certificate_hash = toolkit::assert_signer_is_signing_cardano_transactions( aggregator, &hash, expected_epoch_min, ) .await?; - assertions::assert_is_creating_certificate_with_enough_signers( + toolkit::assert_is_creating_certificate_with_enough_signers( aggregator, &certificate_hash, infrastructure.signers().len(), @@ -300,26 +299,22 @@ impl FullScenario { .await?; let mut client = infrastructure.build_client(aggregator).await?; - assertions::assert_client_can_verify_transactions( - &mut client, - transaction_hashes.clone(), - ) - .await?; + toolkit::assert_client_can_verify_transactions(&mut client, transaction_hashes.clone()) + .await?; } // Verify that Cardano blocks transactions artifacts are produced and signed correctly if self.is_signing_cardano_blocks_transactions { let hash = - assertions::assert_node_producing_cardano_blocks_transactions(aggregator).await?; - let certificate_hash = - assertions::assert_signer_is_signing_cardano_blocks_transactions( - aggregator, - &hash, - expected_epoch_min, - ) - .await?; + toolkit::assert_node_producing_cardano_blocks_transactions(aggregator).await?; + let certificate_hash = toolkit::assert_signer_is_signing_cardano_blocks_transactions( + aggregator, + &hash, + expected_epoch_min, + ) + .await?; - assertions::assert_is_creating_certificate_with_enough_signers( + toolkit::assert_is_creating_certificate_with_enough_signers( aggregator, &certificate_hash, infrastructure.signers().len(), @@ -327,27 +322,26 @@ impl FullScenario { .await?; let mut client = infrastructure.build_client(aggregator).await?; - assertions::assert_client_can_verify_transactions_v2(&mut client, transaction_hashes) + toolkit::assert_client_can_verify_transactions_v2(&mut client, transaction_hashes) .await?; let mut client = infrastructure.build_client(aggregator).await?; - assertions::assert_client_can_verify_blocks(&mut client, block_hashes).await?; + toolkit::assert_client_can_verify_blocks(&mut client, block_hashes).await?; } // Verify that Cardano stake distribution artifacts are produced and signed correctly if self.is_signing_cardano_stake_distribution { { let (hash, epoch) = - assertions::assert_node_producing_cardano_stake_distribution(aggregator) - .await?; + toolkit::assert_node_producing_cardano_stake_distribution(aggregator).await?; let certificate_hash = - assertions::assert_signer_is_signing_cardano_stake_distribution( + toolkit::assert_signer_is_signing_cardano_stake_distribution( aggregator, &hash, expected_epoch_min, ) .await?; - assertions::assert_is_creating_certificate_with_enough_signers( + toolkit::assert_is_creating_certificate_with_enough_signers( aggregator, &certificate_hash, infrastructure.signers().len(), @@ -355,7 +349,7 @@ impl FullScenario { .await?; let mut client = infrastructure.build_client(aggregator).await?; - assertions::assert_client_can_verify_cardano_stake_distribution( + toolkit::assert_client_can_verify_cardano_stake_distribution( &mut client, &hash, epoch, diff --git a/mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs b/mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs index 3585c6d2b3c..e8d36a8cffe 100644 --- a/mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs +++ b/mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs @@ -4,7 +4,7 @@ use slog_scope::info; use mithril_common::StdResult; -use crate::{MithrilInfrastructure, assertions}; +use crate::{MithrilInfrastructure, toolkit}; pub struct RunOnlyScenario { pub infrastructure: Arc, @@ -34,24 +34,24 @@ impl RunOnlyScenario { ) -> StdResult<()> { let leader_aggregator = infrastructure.leader_aggregator(); - assertions::wait_for_enough_immutable(leader_aggregator).await?; + toolkit::wait_for_enough_immutable(leader_aggregator).await?; let chain_observer = leader_aggregator.chain_observer(); let start_epoch = chain_observer.get_current_epoch().await?.unwrap_or_default(); // Wait 3 epochs after start epoch for the aggregator to be able to bootstrap a genesis certificate let target_epoch = start_epoch + 3; - assertions::wait_for_aggregator_at_target_epoch( + toolkit::wait_for_aggregator_at_target_epoch( leader_aggregator, target_epoch, "minimal epoch for the aggregator to be able to bootstrap genesis certificate" .to_string(), ) .await?; - assertions::bootstrap_genesis_certificate(leader_aggregator).await?; - assertions::wait_for_epoch_settings(leader_aggregator).await?; + toolkit::bootstrap_genesis_certificate(leader_aggregator).await?; + toolkit::wait_for_epoch_settings(leader_aggregator).await?; // Transfer some funds on the devnet to have some Cardano transactions to sign - assertions::transfer_funds(infrastructure.devnet()).await?; + toolkit::transfer_funds(infrastructure.devnet()).await?; Ok(()) } diff --git a/mithril-test-lab/mithril-end-to-end/src/assertions/check.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check.rs similarity index 100% rename from mithril-test-lab/mithril-end-to-end/src/assertions/check.rs rename to mithril-test-lab/mithril-end-to-end/src/toolkit/check.rs diff --git a/mithril-test-lab/mithril-end-to-end/src/assertions/exec.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs similarity index 100% rename from mithril-test-lab/mithril-end-to-end/src/assertions/exec.rs rename to mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs diff --git a/mithril-test-lab/mithril-end-to-end/src/assertions/mod.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/mod.rs similarity index 100% rename from mithril-test-lab/mithril-end-to-end/src/assertions/mod.rs rename to mithril-test-lab/mithril-end-to-end/src/toolkit/mod.rs diff --git a/mithril-test-lab/mithril-end-to-end/src/assertions/wait.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs similarity index 100% rename from mithril-test-lab/mithril-end-to-end/src/assertions/wait.rs rename to mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs From 246ecb7d13b4559adc334c19d9359c559352547b Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Thu, 4 Jun 2026 18:23:13 +0200 Subject: [PATCH 04/19] feat(e2e): introduce `ScenarioToolkit` with modular subcomponents (`CheckToolkit`, `ExecToolkit`, `WaitToolkit`) * Added `ScenarioToolkit` for centralized test context handling. * Implemented `ScenarioToolkitContext` and `AttemptPolicy` to control retry policies and delays. --- .../mithril-end-to-end/src/toolkit/check.rs | 12 +++++ .../mithril-end-to-end/src/toolkit/context.rs | 53 +++++++++++++++++++ .../mithril-end-to-end/src/toolkit/exec.rs | 18 ++++++- .../mithril-end-to-end/src/toolkit/mod.rs | 25 +++++++++ .../mithril-end-to-end/src/toolkit/wait.rs | 13 ++++- 5 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 mithril-test-lab/mithril-end-to-end/src/toolkit/context.rs diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check.rs index 22f2a57d63a..234aadd1000 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check.rs @@ -22,9 +22,21 @@ use crate::{ Aggregator, CardanoBlockCommand, CardanoDbV2Command, CardanoStakeDistributionCommand, CardanoTransactionCommand, CardanoTransactionV2Command, Client, ClientCommand, FullNode, MithrilStakeDistributionCommand, NodeVersion, ToolsCommand, UtxoHdCommand, attempt, + toolkit::ScenarioToolkitContext, utils::{AttemptResult, file_utils::copy_dir_all}, }; +#[derive(Debug, Clone, Default)] +pub struct CheckToolkit { + context: ScenarioToolkitContext, +} + +impl CheckToolkit { + pub fn new(context: ScenarioToolkitContext) -> Self { + Self { context } + } +} + async fn get_json_response(url: String) -> StdResult> { match reqwest::get(url.clone()).await { Ok(response) => { diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/context.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/context.rs new file mode 100644 index 00000000000..487e95ce40e --- /dev/null +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/context.rs @@ -0,0 +1,53 @@ +use std::time::Duration; + +#[derive(Debug, Clone, Default)] +pub struct ScenarioToolkitContext { + attempt_policy: AttemptPolicy, +} + +impl ScenarioToolkitContext { + pub fn new(attempt_policy: AttemptPolicy) -> Self { + Self { attempt_policy } + } + + pub fn with_base_duration(base_duration: Duration) -> Self { + Self::new(AttemptPolicy::new(base_duration)) + } + + pub fn attempt_policy(&self) -> AttemptPolicy { + self.attempt_policy + } + + pub fn short_delay(self) -> Duration { + self.attempt_policy.delay(1) + } + + pub fn artifact_delay(self) -> Duration { + self.attempt_policy.delay(2) + } + + pub fn long_delay(self) -> Duration { + self.attempt_policy.delay(5) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct AttemptPolicy { + base_duration: Duration, +} + +impl AttemptPolicy { + pub const fn new(base_duration: Duration) -> Self { + Self { base_duration } + } + + pub fn delay(self, multiplier: u32) -> Duration { + self.base_duration * multiplier + } +} + +impl Default for AttemptPolicy { + fn default() -> Self { + Self::new(Duration::from_secs(1)) + } +} diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs index d30ea487099..3a25519b808 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs @@ -1,11 +1,25 @@ use std::path::PathBuf; -use crate::{AggregateSignatureType, Aggregator, Devnet}; use anyhow::Context; +use slog_scope::info; + use mithril_common::StdResult; use mithril_common::entities::{Epoch, ProtocolParameters}; use mithril_common::messages::AggregatorStatusMessage; -use slog_scope::info; + +use crate::toolkit::ScenarioToolkitContext; +use crate::{AggregateSignatureType, Aggregator, Devnet}; + +#[derive(Debug, Clone, Default)] +pub struct ExecToolkit { + context: ScenarioToolkitContext, +} + +impl ExecToolkit { + pub fn new(context: ScenarioToolkitContext) -> Self { + Self { context } + } +} /// Retrieve the current Mithril era from a running aggregator by querying its `/status` route. pub async fn retrieve_current_era(aggregator: &Aggregator) -> StdResult { diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/mod.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/mod.rs index aed51aabe55..02e79f0b167 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/mod.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/mod.rs @@ -1,7 +1,32 @@ mod check; +mod context; mod exec; mod wait; pub use check::*; +pub use context::*; pub use exec::*; pub use wait::*; + +use std::time::Duration; + +#[derive(Debug, Clone, Default)] +pub struct ScenarioToolkit { + pub check: CheckToolkit, + pub exec: ExecToolkit, + pub wait: WaitToolkit, +} + +impl ScenarioToolkit { + pub fn new(context: ScenarioToolkitContext) -> Self { + Self { + check: CheckToolkit::new(context.clone()), + exec: ExecToolkit::new(context.clone()), + wait: WaitToolkit::new(context), + } + } + + pub fn with_base_duration(base_duration: Duration) -> Self { + Self::new(ScenarioToolkitContext::with_base_duration(base_duration)) + } +} diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs index e45b896e57f..43560be71b8 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs @@ -6,7 +6,18 @@ use std::time::Duration; use mithril_cardano_node_internal_database::entities::ImmutableFile; use mithril_common::{StdResult, entities::Epoch, messages::EpochSettingsMessage}; -use crate::{Aggregator, attempt, utils::AttemptResult}; +use crate::{Aggregator, attempt, toolkit::ScenarioToolkitContext, utils::AttemptResult}; + +#[derive(Debug, Clone, Default)] +pub struct WaitToolkit { + context: ScenarioToolkitContext, +} + +impl WaitToolkit { + pub fn new(context: ScenarioToolkitContext) -> Self { + Self { context } + } +} pub async fn wait_for_enough_immutable(aggregator: &Aggregator) -> StdResult<()> { info!("Waiting that enough immutable have been written in the devnet"; "aggregator" => aggregator.name()); From 28926476a0bc200bb7de3b8a81a3613af2c85476 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Fri, 5 Jun 2026 15:30:40 +0200 Subject: [PATCH 05/19] refactor(e2e): consolidate assertions into `****Toolkit` structs * Moved toolkit functions into their dedicated struct. * Improved reusability and modularity by integrating context handling. * Temporary keep old methods, with an indirection, to keep compatibity and avoid a bigbang --- .../mithril-end-to-end/src/toolkit/check.rs | 1565 ++++++++++------- .../mithril-end-to-end/src/toolkit/exec.rs | 189 +- .../mithril-end-to-end/src/toolkit/wait.rs | 194 +- 3 files changed, 1113 insertions(+), 835 deletions(-) diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check.rs index 234aadd1000..9de2e5d17e8 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check.rs @@ -26,17 +26,6 @@ use crate::{ utils::{AttemptResult, file_utils::copy_dir_all}, }; -#[derive(Debug, Clone, Default)] -pub struct CheckToolkit { - context: ScenarioToolkitContext, -} - -impl CheckToolkit { - pub fn new(context: ScenarioToolkitContext) -> Self { - Self { context } - } -} - async fn get_json_response(url: String) -> StdResult> { match reqwest::get(url.clone()).await { Ok(response) => { @@ -50,774 +39,1026 @@ async fn get_json_response(url: String) -> StdResult StdResult { - let url = format!( - "{}/artifact/mithril-stake-distributions", - aggregator.endpoint() - ); - info!("Waiting for the aggregator to produce a mithril stake distribution"; "aggregator" => &aggregator.name()); - - async fn fetch_last_mithril_stake_distribution_hash(url: String) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([stake_distribution, ..]) => Ok(Some(stake_distribution.hash.clone())), - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!("Invalid mithril stake distribution body: {err}",)), - } +#[derive(Debug, Clone, Default)] +pub struct CheckToolkit { + context: ScenarioToolkitContext, +} + +impl CheckToolkit { + pub fn new(context: ScenarioToolkitContext) -> Self { + Self { context } } - match attempt!(30, Duration::from_secs(3), { + pub async fn node_producing_mithril_stake_distribution( + &self, + aggregator: &Aggregator, + ) -> StdResult { + let url = format!( + "{}/artifact/mithril-stake-distributions", + aggregator.endpoint() + ); + info!("Waiting for the aggregator to produce a mithril stake distribution"; "aggregator" => &aggregator.name()); + + async fn fetch_last_mithril_stake_distribution_hash( + url: String, + ) -> StdResult> { + match get_json_response::(url) + .await? + .as_deref() + { + Ok([stake_distribution, ..]) => Ok(Some(stake_distribution.hash.clone())), + Ok(&[]) => Ok(None), + Err(err) => Err(anyhow!("Invalid mithril stake distribution body: {err}",)), + } + } + + match attempt!(30, Duration::from_secs(3), { fetch_last_mithril_stake_distribution_hash(url.clone()).await }) { - AttemptResult::Ok(hash) => { - info!("Aggregator produced a mithril stake distribution"; "hash" => &hash, "aggregator" => &aggregator.name()); - Ok(hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( + AttemptResult::Ok(hash) => { + info!("Aggregator produced a mithril stake distribution"; "hash" => &hash, "aggregator" => &aggregator.name()); + Ok(hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( "Timeout exhausted assert_node_producing_mithril_stake_distribution, no response from `{url}`" )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) -} + }.with_context(|| { + format!( + "Requesting aggregator `{}`", + aggregator.name() + ) + }) + } -pub async fn assert_signer_is_signing_mithril_stake_distribution( - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, -) -> StdResult { - let url = format!( - "{}/artifact/mithril-stake-distribution/{hash}", - aggregator.endpoint() - ); - info!( - "Asserting the aggregator is signing the mithril stake distribution message `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_mithril_stake_distribution_message( - url: String, + pub async fn signer_is_signing_mithril_stake_distribution( + &self, + aggregator: &Aggregator, + hash: &str, expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url.clone()).await? { - Ok(stake_distribution) => match stake_distribution.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(stake_distribution)), - epoch => Err(anyhow!( - "Minimum expected mithril stake distribution epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => Err(anyhow!("Invalid mithril stake distribution body: {err}",)), + ) -> StdResult { + let url = format!( + "{}/artifact/mithril-stake-distribution/{hash}", + aggregator.endpoint() + ); + info!( + "Asserting the aggregator is signing the mithril stake distribution message `{}` with an expected min epoch of `{}`", + hash, + expected_epoch_min; + "aggregator" => &aggregator.name() + ); + + async fn fetch_mithril_stake_distribution_message( + url: String, + expected_epoch_min: Epoch, + ) -> StdResult> { + match get_json_response::(url.clone()).await? { + Ok(stake_distribution) => match stake_distribution.epoch { + epoch if epoch >= expected_epoch_min => Ok(Some(stake_distribution)), + epoch => Err(anyhow!( + "Minimum expected mithril stake distribution epoch not reached: {epoch} < {expected_epoch_min}" + )), + }, + Err(err) => Err(anyhow!("Invalid mithril stake distribution body: {err}",)), + } } - } - match attempt!(10, Duration::from_millis(1000), { + match attempt!(10, Duration::from_millis(1000), { fetch_mithril_stake_distribution_message(url.clone(), expected_epoch_min).await }) { - AttemptResult::Ok(stake_distribution) => { - info!("Signer signed a mithril stake distribution"; "certificate_hash" => &stake_distribution.certificate_hash, "aggregator" => &aggregator.name()); - Ok(stake_distribution.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( + AttemptResult::Ok(stake_distribution) => { + info!("Signer signed a mithril stake distribution"; "certificate_hash" => &stake_distribution.certificate_hash, "aggregator" => &aggregator.name()); + Ok(stake_distribution.certificate_hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( "Timeout exhausted assert_signer_is_signing_mithril_stake_distribution, no response from `{url}`" )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) -} - -pub async fn assert_node_producing_cardano_database_snapshot( - aggregator: &Aggregator, -) -> StdResult { - let url = format!("{}/artifact/cardano-database", aggregator.endpoint()); - info!("Waiting for the aggregator to produce a Cardano database snapshot"; "aggregator" => &aggregator.name()); + }.with_context(|| { + format!( + "Requesting aggregator `{}`", + aggregator.name() + ) + }) + } - async fn fetch_last_cardano_database_snapshot_hash(url: String) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([cardano_database_snapshot, ..]) => Ok(Some(cardano_database_snapshot.hash.clone())), - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!("Invalid Cardano database snapshot body: {err}",)), + pub async fn node_producing_cardano_database_snapshot( + &self, + aggregator: &Aggregator, + ) -> StdResult { + let url = format!("{}/artifact/cardano-database", aggregator.endpoint()); + info!("Waiting for the aggregator to produce a Cardano database snapshot"; "aggregator" => &aggregator.name()); + + async fn fetch_last_cardano_database_snapshot_hash( + url: String, + ) -> StdResult> { + match get_json_response::(url) + .await? + .as_deref() + { + Ok([cardano_database_snapshot, ..]) => { + Ok(Some(cardano_database_snapshot.hash.clone())) + } + Ok(&[]) => Ok(None), + Err(err) => Err(anyhow!("Invalid Cardano database snapshot body: {err}",)), + } } - } - match attempt!(30, Duration::from_millis(2000), { + match attempt!(30, Duration::from_millis(2000), { fetch_last_cardano_database_snapshot_hash(url.clone()).await }) { - AttemptResult::Ok(hash) => { - info!("Aggregator produced a Cardano database snapshot"; "hash" => &hash, "aggregator" => &aggregator.name()); - Ok(hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( + AttemptResult::Ok(hash) => { + info!("Aggregator produced a Cardano database snapshot"; "hash" => &hash, "aggregator" => &aggregator.name()); + Ok(hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( "Timeout exhausted assert_node_producing_snapshot, no response from `{url}`" )), + } + .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) -} -pub async fn assert_signer_is_signing_cardano_database_snapshot( - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, -) -> StdResult { - let url = format!("{}/artifact/cardano-database/{hash}", aggregator.endpoint()); - info!( - "Asserting the aggregator is signing the Cardano database snapshot message `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_cardano_database_snapshot_message( - url: String, + pub async fn signer_is_signing_cardano_database_snapshot( + &self, + aggregator: &Aggregator, + hash: &str, expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url).await? { - Ok(cardano_database_snapshot) => match cardano_database_snapshot.beacon.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(cardano_database_snapshot)), - epoch => Err(anyhow!( - "Minimum expected Cardano database snapshot epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => Err(anyhow!(err).context("Invalid Cardano database snapshot body")), + ) -> StdResult { + let url = format!("{}/artifact/cardano-database/{hash}", aggregator.endpoint()); + info!( + "Asserting the aggregator is signing the Cardano database snapshot message `{}` with an expected min epoch of `{}`", + hash, + expected_epoch_min; + "aggregator" => &aggregator.name() + ); + + async fn fetch_cardano_database_snapshot_message( + url: String, + expected_epoch_min: Epoch, + ) -> StdResult> { + match get_json_response::(url).await? { + Ok(cardano_database_snapshot) => match cardano_database_snapshot.beacon.epoch { + epoch if epoch >= expected_epoch_min => Ok(Some(cardano_database_snapshot)), + epoch => Err(anyhow!( + "Minimum expected Cardano database snapshot epoch not reached: {epoch} < {expected_epoch_min}" + )), + }, + Err(err) => Err(anyhow!(err).context("Invalid Cardano database snapshot body")), + } } - } - match attempt!(10, Duration::from_millis(1000), { + match attempt!(10, Duration::from_millis(1000), { fetch_cardano_database_snapshot_message(url.clone(), expected_epoch_min).await }) { - AttemptResult::Ok(snapshot) => { - info!("Signer signed a snapshot"; "certificate_hash" => &snapshot.certificate_hash, "aggregator" => &aggregator.name()); - Ok(snapshot.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( + AttemptResult::Ok(snapshot) => { + info!("Signer signed a snapshot"; "certificate_hash" => &snapshot.certificate_hash, "aggregator" => &aggregator.name()); + Ok(snapshot.certificate_hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( "Timeout exhausted assert_signer_is_signing_snapshot, no response from `{url}`" )), + } + .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) -} -pub async fn assert_node_producing_cardano_database_digests_map( - aggregator: &Aggregator, -) -> StdResult> { - let url = format!( - "{}/artifact/cardano-database/digests", - aggregator.endpoint() - ); - info!("Waiting for the aggregator to produce a Cardano database digests map"; "aggregator" => &aggregator.name()); - - async fn fetch_cardano_database_digests_map( - url: String, - ) -> StdResult>> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok(&[]) => Ok(None), - Ok(cardano_database_digests_map) => Ok(Some( - cardano_database_digests_map - .iter() - .map(|item| (item.immutable_file_name.clone(), item.digest.clone())) - .collect(), - )), - Err(err) => Err(anyhow!("Invalid Cardano database digests map body: {err}",)), + pub async fn node_producing_cardano_database_digests_map( + &self, + aggregator: &Aggregator, + ) -> StdResult> { + let url = format!( + "{}/artifact/cardano-database/digests", + aggregator.endpoint() + ); + info!("Waiting for the aggregator to produce a Cardano database digests map"; "aggregator" => &aggregator.name()); + + async fn fetch_cardano_database_digests_map( + url: String, + ) -> StdResult>> { + match get_json_response::(url) + .await? + .as_deref() + { + Ok(&[]) => Ok(None), + Ok(cardano_database_digests_map) => Ok(Some( + cardano_database_digests_map + .iter() + .map(|item| (item.immutable_file_name.clone(), item.digest.clone())) + .collect(), + )), + Err(err) => Err(anyhow!("Invalid Cardano database digests map body: {err}",)), + } } - } - match attempt!(30, Duration::from_millis(2000), { + match attempt!(30, Duration::from_millis(2000), { fetch_cardano_database_digests_map(url.clone()).await }) { - AttemptResult::Ok(cardano_database_digests_map) => { - info!("Aggregator produced a Cardano database digests map"; "total_digests" => &cardano_database_digests_map.len(), "aggregator" => &aggregator.name()); - Ok(cardano_database_digests_map) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( + AttemptResult::Ok(cardano_database_digests_map) => { + info!("Aggregator produced a Cardano database digests map"; "total_digests" => &cardano_database_digests_map.len(), "aggregator" => &aggregator.name()); + Ok(cardano_database_digests_map) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( "Timeout exhausted assert_node_producing_cardano_database_digests_map, no response from `{url}`" )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) -} + }.with_context(|| { + format!( + "Requesting aggregator `{}`", + aggregator.name() + ) + }) + } -pub async fn assert_node_producing_cardano_transactions( - aggregator: &Aggregator, -) -> StdResult { - let url = format!("{}/artifact/cardano-transactions", aggregator.endpoint()); - info!("Waiting for the aggregator to produce a Cardano transactions artifact"; "aggregator" => &aggregator.name(), "aggregator" => &aggregator.name()); - - async fn fetch_last_cardano_transaction_snapshot_hash( - url: String, - ) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([artifact, ..]) => Ok(Some(artifact.hash.clone())), - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!("Invalid Cardano transactions artifact body: {err}",)), + pub async fn node_producing_cardano_transactions( + &self, + aggregator: &Aggregator, + ) -> StdResult { + let url = format!("{}/artifact/cardano-transactions", aggregator.endpoint()); + info!("Waiting for the aggregator to produce a Cardano transactions artifact"; "aggregator" => &aggregator.name(), "aggregator" => &aggregator.name()); + + async fn fetch_last_cardano_transaction_snapshot_hash( + url: String, + ) -> StdResult> { + match get_json_response::(url) + .await? + .as_deref() + { + Ok([artifact, ..]) => Ok(Some(artifact.hash.clone())), + Ok(&[]) => Ok(None), + Err(err) => Err(anyhow!("Invalid Cardano transactions artifact body: {err}",)), + } } - } - match attempt!(30, Duration::from_millis(2000), { + match attempt!(30, Duration::from_millis(2000), { fetch_last_cardano_transaction_snapshot_hash(url.clone()).await }) { - AttemptResult::Ok(hash) => { - info!("Aggregator produced a Cardano transactions artifact"; "hash" => &hash, "aggregator" => &aggregator.name()); - Ok(hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( + AttemptResult::Ok(hash) => { + info!("Aggregator produced a Cardano transactions artifact"; "hash" => &hash, "aggregator" => &aggregator.name()); + Ok(hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( "Timeout exhausted assert_node_producing_cardano_transactions, no response from `{url}`" )), + } + .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) -} -pub async fn assert_signer_is_signing_cardano_transactions( - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, -) -> StdResult { - let url = format!( - "{}/artifact/cardano-transaction/{hash}", - aggregator.endpoint() - ); - info!( - "Asserting the aggregator is signing the Cardano transactions artifact `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_cardano_transaction_snapshot_message( - url: String, + pub async fn signer_is_signing_cardano_transactions( + &self, + aggregator: &Aggregator, + hash: &str, expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url).await? { - Ok(artifact) => match artifact.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(artifact)), - epoch => Err(anyhow!( - "Minimum expected artifact epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => Err(anyhow!(err).context("Invalid Cardano transactions artifact body")), + ) -> StdResult { + let url = format!( + "{}/artifact/cardano-transaction/{hash}", + aggregator.endpoint() + ); + info!( + "Asserting the aggregator is signing the Cardano transactions artifact `{}` with an expected min epoch of `{}`", + hash, + expected_epoch_min; + "aggregator" => &aggregator.name() + ); + + async fn fetch_cardano_transaction_snapshot_message( + url: String, + expected_epoch_min: Epoch, + ) -> StdResult> { + match get_json_response::(url).await? { + Ok(artifact) => match artifact.epoch { + epoch if epoch >= expected_epoch_min => Ok(Some(artifact)), + epoch => Err(anyhow!( + "Minimum expected artifact epoch not reached: {epoch} < {expected_epoch_min}" + )), + }, + Err(err) => Err(anyhow!(err).context("Invalid Cardano transactions artifact body")), + } } - } - match attempt!(10, Duration::from_millis(1000), { + match attempt!(10, Duration::from_millis(1000), { fetch_cardano_transaction_snapshot_message(url.clone(), expected_epoch_min).await }) { - AttemptResult::Ok(artifact) => { - info!("Signer signed a Cardano transactions artifact"; "certificate_hash" => &artifact.certificate_hash, "aggregator" => &aggregator.name()); - Ok(artifact.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( + AttemptResult::Ok(artifact) => { + info!("Signer signed a Cardano transactions artifact"; "certificate_hash" => &artifact.certificate_hash, "aggregator" => &aggregator.name()); + Ok(artifact.certificate_hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( "Timeout exhausted assert_signer_is_signing_cardano_transactions, no response from `{url}`" )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) -} + }.with_context(|| { + format!( + "Requesting aggregator `{}`", + aggregator.name() + ) + }) + } -pub async fn assert_node_producing_cardano_blocks_transactions( - aggregator: &Aggregator, -) -> StdResult { - let url = format!( - "{}/artifact/cardano-blocks-transactions", - aggregator.endpoint() - ); - info!("Waiting for the aggregator to produce a Cardano blocks transactions artifact"; "aggregator" => &aggregator.name()); - - async fn fetch_last_cardano_blocks_transactions_snapshot_hash( - url: String, - ) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([artifact, ..]) => Ok(Some(artifact.hash.clone())), - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!( - "Invalid Cardano blocks transactions artifact body: {err}", - )), + pub async fn node_producing_cardano_blocks_transactions( + &self, + aggregator: &Aggregator, + ) -> StdResult { + let url = format!( + "{}/artifact/cardano-blocks-transactions", + aggregator.endpoint() + ); + info!("Waiting for the aggregator to produce a Cardano blocks transactions artifact"; "aggregator" => &aggregator.name()); + + async fn fetch_last_cardano_blocks_transactions_snapshot_hash( + url: String, + ) -> StdResult> { + match get_json_response::(url) + .await? + .as_deref() + { + Ok([artifact, ..]) => Ok(Some(artifact.hash.clone())), + Ok(&[]) => Ok(None), + Err(err) => Err(anyhow!( + "Invalid Cardano blocks transactions artifact body: {err}", + )), + } } - } - match attempt!(30, Duration::from_millis(2000), { + match attempt!(30, Duration::from_millis(2000), { fetch_last_cardano_blocks_transactions_snapshot_hash(url.clone()).await }) { - AttemptResult::Ok(hash) => { - info!("Aggregator produced a Cardano blocks transactions artifact"; "hash" => &hash, "aggregator" => &aggregator.name()); - Ok(hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( + AttemptResult::Ok(hash) => { + info!("Aggregator produced a Cardano blocks transactions artifact"; "hash" => &hash, "aggregator" => &aggregator.name()); + Ok(hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( "Timeout exhausted assert_node_producing_cardano_blocks_transactions, no response from `{url}`" )), + } + .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) -} -pub async fn assert_signer_is_signing_cardano_blocks_transactions( - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, -) -> StdResult { - let url = format!( - "{}/artifact/cardano-blocks-transactions/{hash}", - aggregator.endpoint() - ); - info!( - "Asserting the aggregator is signing the Cardano blocks transactions artifact `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_cardano_blocks_transactions_snapshot_message( - url: String, + pub async fn signer_is_signing_cardano_blocks_transactions( + &self, + aggregator: &Aggregator, + hash: &str, expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url).await? { - Ok(artifact) => match artifact.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(artifact)), - epoch => Err(anyhow!( - "Minimum expected artifact epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => { - Err(anyhow!(err).context("Invalid Cardano blocks transactions artifact body")) + ) -> StdResult { + let url = format!( + "{}/artifact/cardano-blocks-transactions/{hash}", + aggregator.endpoint() + ); + info!( + "Asserting the aggregator is signing the Cardano blocks transactions artifact `{}` with an expected min epoch of `{}`", + hash, + expected_epoch_min; + "aggregator" => &aggregator.name() + ); + + async fn fetch_cardano_blocks_transactions_snapshot_message( + url: String, + expected_epoch_min: Epoch, + ) -> StdResult> { + match get_json_response::(url).await? { + Ok(artifact) => match artifact.epoch { + epoch if epoch >= expected_epoch_min => Ok(Some(artifact)), + epoch => Err(anyhow!( + "Minimum expected artifact epoch not reached: {epoch} < {expected_epoch_min}" + )), + }, + Err(err) => { + Err(anyhow!(err).context("Invalid Cardano blocks transactions artifact body")) + } } } - } - match attempt!(10, Duration::from_millis(1000), { + match attempt!(10, Duration::from_millis(1000), { fetch_cardano_blocks_transactions_snapshot_message(url.clone(), expected_epoch_min).await }) { - AttemptResult::Ok(artifact) => { - info!("Signer signed a Cardano blocks transactions artifact"; "certificate_hash" => &artifact.certificate_hash, "aggregator" => &aggregator.name()); - Ok(artifact.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( + AttemptResult::Ok(artifact) => { + info!("Signer signed a Cardano blocks transactions artifact"; "certificate_hash" => &artifact.certificate_hash, "aggregator" => &aggregator.name()); + Ok(artifact.certificate_hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( "Timeout exhausted assert_signer_is_signing_cardano_blocks_transactions, no response from `{url}`" )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) -} + }.with_context(|| { + format!( + "Requesting aggregator `{}`", + aggregator.name() + ) + }) + } -pub async fn assert_node_producing_cardano_stake_distribution( - aggregator: &Aggregator, -) -> StdResult<(String, Epoch)> { - let url = format!( - "{}/artifact/cardano-stake-distributions", - aggregator.endpoint() - ); - info!("Waiting for the aggregator to produce a Cardano stake distribution"; "aggregator" => &aggregator.name()); - - async fn fetch_last_cardano_stake_distribution_message( - url: String, - ) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([stake_distribution, ..]) => Ok(Some(( - stake_distribution.hash.clone(), - stake_distribution.epoch, - ))), - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!("Invalid Cardano stake distribution body: {err}",)), + pub async fn node_producing_cardano_stake_distribution( + &self, + aggregator: &Aggregator, + ) -> StdResult<(String, Epoch)> { + let url = format!( + "{}/artifact/cardano-stake-distributions", + aggregator.endpoint() + ); + info!("Waiting for the aggregator to produce a Cardano stake distribution"; "aggregator" => &aggregator.name()); + + async fn fetch_last_cardano_stake_distribution_message( + url: String, + ) -> StdResult> { + match get_json_response::(url) + .await? + .as_deref() + { + Ok([stake_distribution, ..]) => Ok(Some(( + stake_distribution.hash.clone(), + stake_distribution.epoch, + ))), + Ok(&[]) => Ok(None), + Err(err) => Err(anyhow!("Invalid Cardano stake distribution body: {err}",)), + } } - } - match attempt!(30, Duration::from_millis(2000), { + match attempt!(30, Duration::from_millis(2000), { fetch_last_cardano_stake_distribution_message(url.clone()).await }) { - AttemptResult::Ok((hash, epoch)) => { - info!("Aggregator produced a Cardano stake distribution"; "hash" => &hash, "epoch" => #?epoch, "aggregator" => &aggregator.name()); - Ok((hash, epoch)) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( + AttemptResult::Ok((hash, epoch)) => { + info!("Aggregator produced a Cardano stake distribution"; "hash" => &hash, "epoch" => #?epoch, "aggregator" => &aggregator.name()); + Ok((hash, epoch)) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( "Timeout exhausted assert_node_producing_cardano_stake_distribution, no response from `{url}`" )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) -} + }.with_context(|| { + format!( + "Requesting aggregator `{}`", + aggregator.name() + ) + }) + } -pub async fn assert_signer_is_signing_cardano_stake_distribution( - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, -) -> StdResult { - let url = format!( - "{}/artifact/cardano-stake-distribution/{hash}", - aggregator.endpoint() - ); - info!( - "Asserting the aggregator is signing the Cardano stake distribution message `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_cardano_stake_distribution_message( - url: String, + pub async fn signer_is_signing_cardano_stake_distribution( + &self, + aggregator: &Aggregator, + hash: &str, expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url).await? { - Ok(stake_distribution) => match stake_distribution.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(stake_distribution)), - epoch => Err(anyhow!( - "Minimum expected Cardano stake distribution epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => Err(anyhow!(err).context("Invalid Cardano stake distribution body")), + ) -> StdResult { + let url = format!( + "{}/artifact/cardano-stake-distribution/{hash}", + aggregator.endpoint() + ); + info!( + "Asserting the aggregator is signing the Cardano stake distribution message `{}` with an expected min epoch of `{}`", + hash, + expected_epoch_min; + "aggregator" => &aggregator.name() + ); + + async fn fetch_cardano_stake_distribution_message( + url: String, + expected_epoch_min: Epoch, + ) -> StdResult> { + match get_json_response::(url).await? { + Ok(stake_distribution) => match stake_distribution.epoch { + epoch if epoch >= expected_epoch_min => Ok(Some(stake_distribution)), + epoch => Err(anyhow!( + "Minimum expected Cardano stake distribution epoch not reached: {epoch} < {expected_epoch_min}" + )), + }, + Err(err) => Err(anyhow!(err).context("Invalid Cardano stake distribution body")), + } } - } - match attempt!(10, Duration::from_millis(1000), { + match attempt!(10, Duration::from_millis(1000), { fetch_cardano_stake_distribution_message(url.clone(), expected_epoch_min).await }) { - AttemptResult::Ok(cardano_stake_distribution) => { - info!("Signer signed a Cardano stake distribution"; "certificate_hash" => &cardano_stake_distribution.certificate_hash, "aggregator" => &aggregator.name()); - Ok(cardano_stake_distribution.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( + AttemptResult::Ok(cardano_stake_distribution) => { + info!("Signer signed a Cardano stake distribution"; "certificate_hash" => &cardano_stake_distribution.certificate_hash, "aggregator" => &aggregator.name()); + Ok(cardano_stake_distribution.certificate_hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( "Timeout exhausted assert_signer_is_signing_cardano_stake_distribution, no response from `{url}`" )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) -} + }.with_context(|| { + format!( + "Requesting aggregator `{}`", + aggregator.name() + ) + }) + } -pub async fn assert_is_creating_certificate_with_enough_signers( - aggregator: &Aggregator, - certificate_hash: &str, - total_signers_expected: usize, -) -> StdResult<()> { - let url = format!("{}/certificate/{certificate_hash}", aggregator.endpoint()); - info!("Waiting for the aggregator to create a certificate with enough signers"; "aggregator" => &aggregator.name()); + pub async fn is_creating_certificate_with_enough_signers( + &self, + aggregator: &Aggregator, + certificate_hash: &str, + total_signers_expected: usize, + ) -> StdResult<()> { + let url = format!("{}/certificate/{certificate_hash}", aggregator.endpoint()); + info!("Waiting for the aggregator to create a certificate with enough signers"; "aggregator" => &aggregator.name()); + + async fn fetch_certificate_message(url: String) -> StdResult> { + match get_json_response::(url).await? { + Ok(certificate) => Ok(Some(certificate)), + Err(err) => Err(anyhow!(err).context("Invalid snapshot body")), + } + } - async fn fetch_certificate_message(url: String) -> StdResult> { - match get_json_response::(url).await? { - Ok(certificate) => Ok(Some(certificate)), - Err(err) => Err(anyhow!(err).context("Invalid snapshot body")), + match attempt!(10, Duration::from_millis(1000), { + fetch_certificate_message(url.clone()).await + }) { + 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 ; + "aggregator" => &aggregator.name() + ); + Ok(()) + } else { + Err(anyhow!( + "Certificate is not signed by expected number of signers: {} < {} ", + certificate.metadata.signers.len(), + total_signers_expected + )) + } + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted assert_is_creating_certificate, no response from `{url}`" + )), } + .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) } - match attempt!(10, Duration::from_millis(1000), { - fetch_certificate_message(url.clone()).await - }) { - 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 ; - "aggregator" => &aggregator.name() - ); - Ok(()) - } else { - Err(anyhow!( - "Certificate is not signed by expected number of signers: {} < {} ", - certificate.metadata.signers.len(), - total_signers_expected + pub async fn client_can_verify_cardano_database( + &self, + client: &mut Client, + hash: &str, + ) -> StdResult<()> { + client + .run(ClientCommand::CardanoDbV2(CardanoDbV2Command::List)) + .await?; + + if client.version().is_above_or_equal("0.12.34") { + client + .run(ClientCommand::CardanoDbV2( + CardanoDbV2Command::ListPerEpoch { + epoch_specifier: EpochSpecifier::LatestMinusOffset(5), + }, )) - } + .await?; + } else { + warn!( + "Client version is below 0.12.34, skipping `cardano-db snapshot list --epoch latest-5` check" + ); } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_is_creating_certificate, no response from `{url}`" - )), - } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) -} -pub async fn assert_client_can_verify_cardano_database( - client: &mut Client, - hash: &str, -) -> StdResult<()> { - client - .run(ClientCommand::CardanoDbV2(CardanoDbV2Command::List)) - .await?; + client + .run(ClientCommand::CardanoDbV2(CardanoDbV2Command::Show { + hash: hash.to_string(), + })) + .await?; + info!("Client list & show the cardano database snapshot"; "hash" => &hash); + + client + .run(ClientCommand::CardanoDbV2(CardanoDbV2Command::Download { + hash: hash.to_string(), + })) + .await?; + info!("Client downloaded & restored the cardano database snapshot"; "hash" => &hash); + + Ok(()) + } - if client.version().is_above_or_equal("0.12.34") { + pub async fn client_can_verify_mithril_stake_distribution( + &self, + client: &mut Client, + hash: &str, + ) -> StdResult<()> { client - .run(ClientCommand::CardanoDbV2( - CardanoDbV2Command::ListPerEpoch { - epoch_specifier: EpochSpecifier::LatestMinusOffset(5), + .run(ClientCommand::MithrilStakeDistribution( + MithrilStakeDistributionCommand::Download { + hash: hash.to_owned(), }, )) .await?; - } else { - warn!( - "Client version is below 0.12.34, skipping `cardano-db snapshot list --epoch latest-5` check" - ); + info!("Client downloaded the Mithril stake distribution"; "hash" => &hash); + + Ok(()) } - client - .run(ClientCommand::CardanoDbV2(CardanoDbV2Command::Show { - hash: hash.to_string(), - })) - .await?; - info!("Client list & show the cardano database snapshot"; "hash" => &hash); - - client - .run(ClientCommand::CardanoDbV2(CardanoDbV2Command::Download { - hash: hash.to_string(), - })) - .await?; - info!("Client downloaded & restored the cardano database snapshot"; "hash" => &hash); - - Ok(()) -} + pub async fn client_can_verify_transactions( + &self, + client: &mut Client, + tx_hashes: Vec, + ) -> StdResult<()> { + #[allow(dead_code)] + #[derive(Debug, serde::Deserialize)] + struct ClientCardanoTransactionCertifyResult { + certified_transactions: Vec, + non_certified_transactions: Vec, + } -pub async fn assert_client_can_verify_mithril_stake_distribution( - client: &mut Client, - hash: &str, -) -> StdResult<()> { - client - .run(ClientCommand::MithrilStakeDistribution( - MithrilStakeDistributionCommand::Download { - hash: hash.to_owned(), - }, - )) - .await?; - info!("Client downloaded the Mithril stake distribution"; "hash" => &hash); - - Ok(()) -} + let result_file = client + .run(ClientCommand::CardanoTransaction( + CardanoTransactionCommand::Certify { + tx_hashes: tx_hashes.clone(), + }, + )) + .await?; + info!("Client verified the Cardano transactions"; "tx_hashes" => ?tx_hashes); -pub async fn assert_client_can_verify_transactions( - client: &mut Client, - tx_hashes: Vec, -) -> StdResult<()> { - #[allow(dead_code)] - #[derive(Debug, serde::Deserialize)] - struct ClientCardanoTransactionCertifyResult { - certified_transactions: Vec, - non_certified_transactions: Vec, + let file = std::fs::read_to_string(&result_file).with_context(|| { + format!( + "Failed to read client output from file `{}`", + result_file.display() + ) + })?; + let result: ClientCardanoTransactionCertifyResult = serde_json::from_str(&file) + .with_context(|| { + format!( + "Failed to parse client output as json from file `{}`", + result_file.display() + ) + })?; + + info!("Asserting that all Cardano transactions were verified by the Client..."); + if tx_hashes.iter().all(|tx| result.certified_transactions.contains(tx)) { + Ok(()) + } else { + Err(anyhow!( + "Not all transactions were certified:\n'{:#?}'", + result, + )) + } } - let result_file = client - .run(ClientCommand::CardanoTransaction( - CardanoTransactionCommand::Certify { - tx_hashes: tx_hashes.clone(), - }, - )) - .await?; - info!("Client verified the Cardano transactions"; "tx_hashes" => ?tx_hashes); - - let file = std::fs::read_to_string(&result_file).with_context(|| { - format!( - "Failed to read client output from file `{}`", - result_file.display() - ) - })?; - let result: ClientCardanoTransactionCertifyResult = - serde_json::from_str(&file).with_context(|| { + pub async fn client_can_verify_transactions_v2( + &self, + client: &mut Client, + tx_hashes: Vec, + ) -> StdResult<()> { + #[allow(dead_code)] + #[derive(Debug, serde::Deserialize)] + struct ClientCardanoTransactionCertifyResult { + certified_transactions: Vec, + non_certified_transactions: Vec, + } + + #[derive(Debug, serde::Deserialize)] + struct CertifiedTransactionV2 { + transaction_hash: String, + } + + if !client.version().is_above_or_equal("0.13.1") { + warn!( + "Client version is below 0.13.1, skipping `cardano-transaction certify --backend v2` check" + ); + return Ok(()); + } + + let result_file = client + .run(ClientCommand::CardanoTransactionV2( + CardanoTransactionV2Command::Certify { + tx_hashes: tx_hashes.clone(), + }, + )) + .await?; + info!("Client verified the Cardano transactions V2"; "tx_hashes" => ?tx_hashes); + + let file = std::fs::read_to_string(&result_file).with_context(|| { format!( - "Failed to parse client output as json from file `{}`", + "Failed to read client output from file `{}`", result_file.display() ) })?; - - info!("Asserting that all Cardano transactions were verified by the Client..."); - if tx_hashes.iter().all(|tx| result.certified_transactions.contains(tx)) { - Ok(()) - } else { - Err(anyhow!( - "Not all transactions were certified:\n'{:#?}'", - result, - )) + let result: ClientCardanoTransactionCertifyResult = serde_json::from_str(&file) + .with_context(|| { + format!( + "Failed to parse client output as json from file `{}`", + result_file.display() + ) + })?; + + info!("Asserting that all Cardano transactions V2 were verified by the Client..."); + let certified_tx_hashes_result: Vec = result + .certified_transactions + .iter() + .map(|tx| tx.transaction_hash.clone()) + .collect(); + + if tx_hashes.iter().all(|tx| certified_tx_hashes_result.contains(tx)) { + Ok(()) + } else { + Err(anyhow!( + "Not all transactions V2 were certified:\n'{:#?}'", + result, + )) + } } -} -pub async fn assert_client_can_verify_transactions_v2( - client: &mut Client, - tx_hashes: Vec, -) -> StdResult<()> { - #[allow(dead_code)] - #[derive(Debug, serde::Deserialize)] - struct ClientCardanoTransactionCertifyResult { - certified_transactions: Vec, - non_certified_transactions: Vec, - } + pub async fn client_can_verify_blocks( + &self, + client: &mut Client, + block_hashes: Vec, + ) -> StdResult<()> { + #[allow(dead_code)] + #[derive(Debug, serde::Deserialize)] + struct ClientCardanoBlockCertifyResult { + certified_blocks: Vec, + non_certified_blocks: Vec, + } - #[derive(Debug, serde::Deserialize)] - struct CertifiedTransactionV2 { - transaction_hash: String, - } + #[derive(Debug, serde::Deserialize)] + struct CertifiedBlock { + block_hash: String, + } - if !client.version().is_above_or_equal("0.13.1") { - warn!( - "Client version is below 0.13.1, skipping `cardano-transaction certify --backend v2` check" - ); - return Ok(()); - } + if !client.version().is_above_or_equal("0.13.1") { + warn!("Client version is below 0.13.1, skipping `cardano-block certify` check"); + return Ok(()); + } - let result_file = client - .run(ClientCommand::CardanoTransactionV2( - CardanoTransactionV2Command::Certify { - tx_hashes: tx_hashes.clone(), - }, - )) - .await?; - info!("Client verified the Cardano transactions V2"; "tx_hashes" => ?tx_hashes); - - let file = std::fs::read_to_string(&result_file).with_context(|| { - format!( - "Failed to read client output from file `{}`", - result_file.display() - ) - })?; - let result: ClientCardanoTransactionCertifyResult = - serde_json::from_str(&file).with_context(|| { + let result_file = client + .run(ClientCommand::CardanoBlock(CardanoBlockCommand::Certify { + block_hashes: block_hashes.clone(), + })) + .await?; + info!("Client verified the Cardano blocks"; "block_hashes" => ?block_hashes); + let file = std::fs::read_to_string(&result_file).with_context(|| { format!( - "Failed to parse client output as json from file `{}`", + "Failed to read client output from file `{}`", result_file.display() ) })?; + let result: ClientCardanoBlockCertifyResult = + serde_json::from_str(&file).with_context(|| { + format!( + "Failed to parse client output as json from file `{}`", + result_file.display() + ) + })?; + + info!("Asserting that all Cardano blocks were verified by the Client..."); + let certified_blocks_hashes_result: Vec = result + .certified_blocks + .iter() + .map(|block| block.block_hash.clone()) + .collect(); + + if block_hashes + .iter() + .all(|block| certified_blocks_hashes_result.contains(block)) + { + Ok(()) + } else { + Err(anyhow!("Not all blocks were certified:\n'{:#?}'", result,)) + } + } + + pub async fn client_can_verify_cardano_stake_distribution( + &self, + client: &mut Client, + hash: &str, + epoch: Epoch, + ) -> StdResult<()> { + client + .run(ClientCommand::CardanoStakeDistribution( + CardanoStakeDistributionCommand::Download { + unique_identifier: epoch.to_string(), + }, + )) + .await?; + info!("Client downloaded the Cardano stake distribution by epoch"; "epoch" => epoch.to_string()); - info!("Asserting that all Cardano transactions V2 were verified by the Client..."); - let certified_tx_hashes_result: Vec = result - .certified_transactions - .iter() - .map(|tx| tx.transaction_hash.clone()) - .collect(); + client + .run(ClientCommand::CardanoStakeDistribution( + CardanoStakeDistributionCommand::Download { + unique_identifier: hash.to_string(), + }, + )) + .await?; + info!("Client downloaded the Cardano stake distribution by hash"; "hash" => hash.to_string()); - if tx_hashes.iter().all(|tx| certified_tx_hashes_result.contains(tx)) { Ok(()) - } else { - Err(anyhow!( - "Not all transactions V2 were certified:\n'{:#?}'", - result, - )) } -} -pub async fn assert_client_can_verify_blocks( - client: &mut Client, - block_hashes: Vec, -) -> StdResult<()> { - #[allow(dead_code)] - #[derive(Debug, serde::Deserialize)] - struct ClientCardanoBlockCertifyResult { - certified_blocks: Vec, - non_certified_blocks: Vec, - } + pub async fn client_can_convert_the_ledger_snapshot( + &self, + client: &mut Client, + full_node: &FullNode, + artifacts_dir: PathBuf, + cardano_node_version: NodeVersion, + ) -> StdResult<()> { + if client.version().is_below("0.13.10") { + warn!("Client version is below 0.13.10, skipping snapshot conversion check"); + return Ok(()); + } - #[derive(Debug, serde::Deserialize)] - struct CertifiedBlock { - block_hash: String, - } + let utxo_hd_flavor = if cardano_node_version.is_below("10.7.0") { + "LMDB" + } else { + "LSM" + }; - if !client.version().is_above_or_equal("0.13.1") { - warn!("Client version is below 0.13.1, skipping `cardano-block certify` check"); - return Ok(()); - } + let binary_path = artifacts_dir.join("bin").join("snapshot-converter"); - let result_file = client - .run(ClientCommand::CardanoBlock(CardanoBlockCommand::Certify { - block_hashes: block_hashes.clone(), - })) - .await?; - info!("Client verified the Cardano blocks"; "block_hashes" => ?block_hashes); - let file = std::fs::read_to_string(&result_file).with_context(|| { - format!( - "Failed to read client output from file `{}`", - result_file.display() - ) - })?; - let result: ClientCardanoBlockCertifyResult = - serde_json::from_str(&file).with_context(|| { + //copy the db to another temporary location to avoid any risk of modifying the original one during the conversion process + let db_to_convert = artifacts_dir.join("db_to_convert"); + copy_dir_all(&full_node.db_path, &db_to_convert).with_context(|| { format!( - "Failed to parse client output as json from file `{}`", - result_file.display() + "Failed to copy the ledger state database from `{}` to `{}` for the snapshot conversion process", + full_node.db_path.display(), + db_to_convert.display() ) })?; - info!("Asserting that all Cardano blocks were verified by the Client..."); - let certified_blocks_hashes_result: Vec = result - .certified_blocks - .iter() - .map(|block| block.block_hash.clone()) - .collect(); - - if block_hashes - .iter() - .all(|block| certified_blocks_hashes_result.contains(block)) - { + client + .run(ClientCommand::Tools(ToolsCommand::UtxoHd( + UtxoHdCommand::SnapshotConverter { + db_directory: db_to_convert.to_string_lossy().to_string(), + cardano_node_version: cardano_node_version.to_string(), + binary_path: binary_path.to_string_lossy().to_string(), + config_path: full_node + .snapshot_converter_config_path + .to_string_lossy() + .to_string(), + utxo_hd_flavor: utxo_hd_flavor.to_string(), + commit: true, + }, + ))) + .await?; + info!("Client converted the ledger state into {utxo_hd_flavor} format"); + Ok(()) - } else { - Err(anyhow!("Not all blocks were certified:\n'{:#?}'", result,)) } } +pub async fn assert_node_producing_mithril_stake_distribution( + aggregator: &Aggregator, +) -> StdResult { + CheckToolkit::default() + .node_producing_mithril_stake_distribution(aggregator) + .await +} + +pub async fn assert_signer_is_signing_mithril_stake_distribution( + aggregator: &Aggregator, + hash: &str, + expected_epoch_min: Epoch, +) -> StdResult { + CheckToolkit::default() + .signer_is_signing_mithril_stake_distribution(aggregator, hash, expected_epoch_min) + .await +} + +pub async fn assert_node_producing_cardano_database_snapshot( + aggregator: &Aggregator, +) -> StdResult { + CheckToolkit::default() + .node_producing_cardano_database_snapshot(aggregator) + .await +} + +pub async fn assert_signer_is_signing_cardano_database_snapshot( + aggregator: &Aggregator, + hash: &str, + expected_epoch_min: Epoch, +) -> StdResult { + CheckToolkit::default() + .signer_is_signing_cardano_database_snapshot(aggregator, hash, expected_epoch_min) + .await +} + +pub async fn assert_node_producing_cardano_database_digests_map( + aggregator: &Aggregator, +) -> StdResult> { + CheckToolkit::default() + .node_producing_cardano_database_digests_map(aggregator) + .await +} + +pub async fn assert_node_producing_cardano_transactions( + aggregator: &Aggregator, +) -> StdResult { + CheckToolkit::default() + .node_producing_cardano_transactions(aggregator) + .await +} + +pub async fn assert_signer_is_signing_cardano_transactions( + aggregator: &Aggregator, + hash: &str, + expected_epoch_min: Epoch, +) -> StdResult { + CheckToolkit::default() + .signer_is_signing_cardano_transactions(aggregator, hash, expected_epoch_min) + .await +} + +pub async fn assert_node_producing_cardano_blocks_transactions( + aggregator: &Aggregator, +) -> StdResult { + CheckToolkit::default() + .node_producing_cardano_blocks_transactions(aggregator) + .await +} + +pub async fn assert_signer_is_signing_cardano_blocks_transactions( + aggregator: &Aggregator, + hash: &str, + expected_epoch_min: Epoch, +) -> StdResult { + CheckToolkit::default() + .signer_is_signing_cardano_blocks_transactions(aggregator, hash, expected_epoch_min) + .await +} + +pub async fn assert_node_producing_cardano_stake_distribution( + aggregator: &Aggregator, +) -> StdResult<(String, Epoch)> { + CheckToolkit::default() + .node_producing_cardano_stake_distribution(aggregator) + .await +} + +pub async fn assert_signer_is_signing_cardano_stake_distribution( + aggregator: &Aggregator, + hash: &str, + expected_epoch_min: Epoch, +) -> StdResult { + CheckToolkit::default() + .signer_is_signing_cardano_stake_distribution(aggregator, hash, expected_epoch_min) + .await +} + +pub async fn assert_is_creating_certificate_with_enough_signers( + aggregator: &Aggregator, + certificate_hash: &str, + total_signers_expected: usize, +) -> StdResult<()> { + CheckToolkit::default() + .is_creating_certificate_with_enough_signers( + aggregator, + certificate_hash, + total_signers_expected, + ) + .await +} + +pub async fn assert_client_can_verify_cardano_database( + client: &mut Client, + hash: &str, +) -> StdResult<()> { + CheckToolkit::default() + .client_can_verify_cardano_database(client, hash) + .await +} + +pub async fn assert_client_can_verify_mithril_stake_distribution( + client: &mut Client, + hash: &str, +) -> StdResult<()> { + CheckToolkit::default() + .client_can_verify_mithril_stake_distribution(client, hash) + .await +} + +pub async fn assert_client_can_verify_transactions( + client: &mut Client, + tx_hashes: Vec, +) -> StdResult<()> { + CheckToolkit::default() + .client_can_verify_transactions(client, tx_hashes) + .await +} + +pub async fn assert_client_can_verify_transactions_v2( + client: &mut Client, + tx_hashes: Vec, +) -> StdResult<()> { + CheckToolkit::default() + .client_can_verify_transactions_v2(client, tx_hashes) + .await +} + +pub async fn assert_client_can_verify_blocks( + client: &mut Client, + block_hashes: Vec, +) -> StdResult<()> { + CheckToolkit::default() + .client_can_verify_blocks(client, block_hashes) + .await +} + pub async fn assert_client_can_verify_cardano_stake_distribution( client: &mut Client, hash: &str, epoch: Epoch, ) -> StdResult<()> { - client - .run(ClientCommand::CardanoStakeDistribution( - CardanoStakeDistributionCommand::Download { - unique_identifier: epoch.to_string(), - }, - )) - .await?; - info!("Client downloaded the Cardano stake distribution by epoch"; "epoch" => epoch.to_string()); - - client - .run(ClientCommand::CardanoStakeDistribution( - CardanoStakeDistributionCommand::Download { - unique_identifier: hash.to_string(), - }, - )) - .await?; - info!("Client downloaded the Cardano stake distribution by hash"; "hash" => hash.to_string()); - - Ok(()) + CheckToolkit::default() + .client_can_verify_cardano_stake_distribution(client, hash, epoch) + .await } pub async fn assert_client_can_convert_the_ledger_snapshot( @@ -826,42 +1067,12 @@ pub async fn assert_client_can_convert_the_ledger_snapshot( artifacts_dir: PathBuf, cardano_node_version: NodeVersion, ) -> StdResult<()> { - if client.version().is_below("0.13.10") { - warn!("Client version is below 0.13.10, skipping snapshot conversion check"); - return Ok(()); - } - - let utxo_hd_flavor = if cardano_node_version.is_below("10.7.0") { - "LMDB" - } else { - "LSM" - }; - - let binary_path = artifacts_dir.join("bin").join("snapshot-converter"); - - //copy the db to another temporary location to avoid any risk of modifying the original one during the conversion process - let db_to_convert = artifacts_dir.join("db_to_convert"); - copy_dir_all(&full_node.db_path, &db_to_convert).with_context(|| { - format!( - "Failed to copy the ledger state database from `{}` to `{}` for the snapshot conversion process", - full_node.db_path.display(), - db_to_convert.display() + CheckToolkit::default() + .client_can_convert_the_ledger_snapshot( + client, + full_node, + artifacts_dir, + cardano_node_version, ) - })?; - - client - .run(ClientCommand::Tools(ToolsCommand::UtxoHd( - UtxoHdCommand::SnapshotConverter { - db_directory: db_to_convert.to_string_lossy().to_string(), - cardano_node_version: cardano_node_version.to_string(), - binary_path: binary_path.to_string_lossy().to_string(), - config_path: full_node.snapshot_converter_config_path.to_string_lossy().to_string(), - utxo_hd_flavor: utxo_hd_flavor.to_string(), - commit: true, - }, - ))) - .await?; - info!("Client converted the ledger state into {utxo_hd_flavor} format"); - - Ok(()) + .await } diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs index 3a25519b808..a9b9298cc74 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs @@ -12,41 +12,125 @@ use crate::{AggregateSignatureType, Aggregator, Devnet}; #[derive(Debug, Clone, Default)] pub struct ExecToolkit { - context: ScenarioToolkitContext, + _context: ScenarioToolkitContext, } impl ExecToolkit { pub fn new(context: ScenarioToolkitContext) -> Self { - Self { context } + Self { _context: context } + } + + /// Retrieve the current Mithril era from a running aggregator by querying its `/status` route. + pub async fn retrieve_current_era(&self, aggregator: &Aggregator) -> StdResult { + let url = format!("{}/status", aggregator.endpoint()); + let response = reqwest::get(&url) + .await + .with_context(|| format!("Failed to query aggregator status at `{url}`"))?; + let status_message: AggregatorStatusMessage = response + .json() + .await + .with_context(|| "Failed to parse aggregator status response")?; + + Ok(status_message.mithril_era.to_string()) + } + + pub async fn bootstrap_genesis_certificate(&self, aggregator: &Aggregator) -> StdResult<()> { + info!("Bootstrap genesis certificate"; "aggregator" => &aggregator.name()); + info!("> retrieving current era from aggregator"; "aggregator" => &aggregator.name()); + let mithril_era = retrieve_current_era(aggregator).await?; + info!("> stopping aggregator"; "aggregator" => &aggregator.name()); + aggregator.stop().await?; + info!("> bootstrapping genesis using signers registered two epochs ago..."; "aggregator" => &aggregator.name()); + aggregator.bootstrap_genesis(&mithril_era).await?; + info!("> done, restarting aggregator"; "aggregator" => &aggregator.name()); + aggregator.serve().await?; + + Ok(()) + } + + pub async fn register_era_marker( + &self, + aggregator: &Aggregator, + devnet: &Devnet, + mithril_era: &str, + era_epoch: Epoch, + ) -> StdResult<()> { + info!("Register '{mithril_era}' era marker"; "aggregator" => &aggregator.name()); + + info!("> generating era marker tx datum..."; "aggregator" => &aggregator.name()); + let tx_datum_file_path = devnet + .artifacts_dir() + .join(PathBuf::from("era-tx-datum.txt".to_string())); + aggregator + .era_generate_tx_datum(&tx_datum_file_path, mithril_era, era_epoch) + .await?; + + info!("> writing '{mithril_era}' era marker on the Cardano chain..."; "aggregator" => &aggregator.name()); + devnet.write_era_marker(&tx_datum_file_path).await?; + + Ok(()) + } + + pub async fn delegate_stakes_to_pools( + &self, + devnet: &Devnet, + delegation_round: u16, + ) -> StdResult<()> { + info!("Delegate stakes to the cardano pools"); + + devnet.delegate_stakes(delegation_round).await?; + + Ok(()) + } + + pub async fn transfer_funds(&self, devnet: &Devnet) -> StdResult<()> { + info!("Transfer funds on the devnet"); + + devnet.transfer_funds().await?; + + Ok(()) + } + + pub async fn update_protocol_parameters( + &self, + aggregator: &Aggregator, + aggregate_signature_type: AggregateSignatureType, + ) -> StdResult<()> { + info!("Update protocol parameters"; "aggregator" => &aggregator.name()); + + info!("> stopping aggregator"); + aggregator.stop().await?; + let protocol_parameters_new = match aggregate_signature_type { + AggregateSignatureType::Concatenation => ProtocolParameters { + k: 145, + m: 210, + phi_f: 0.80, + }, + AggregateSignatureType::Snark => ProtocolParameters { + k: 7, + m: 10, + phi_f: 0.95, + }, + }; + + info!( + "> updating protocol parameters to {protocol_parameters_new:?}..."; "aggregator" => &aggregator.name() + ); + aggregator.set_protocol_parameters(&protocol_parameters_new).await; + info!("> done, restarting aggregator"; "aggregator" => &aggregator.name()); + aggregator.serve().await?; + + Ok(()) } } /// Retrieve the current Mithril era from a running aggregator by querying its `/status` route. pub async fn retrieve_current_era(aggregator: &Aggregator) -> StdResult { - let url = format!("{}/status", aggregator.endpoint()); - let response = reqwest::get(&url) - .await - .with_context(|| format!("Failed to query aggregator status at `{url}`"))?; - let status_message: AggregatorStatusMessage = response - .json() - .await - .with_context(|| "Failed to parse aggregator status response")?; - - Ok(status_message.mithril_era.to_string()) + ExecToolkit::default().retrieve_current_era(aggregator).await } pub async fn bootstrap_genesis_certificate(aggregator: &Aggregator) -> StdResult<()> { - info!("Bootstrap genesis certificate"; "aggregator" => &aggregator.name()); - info!("> retrieving current era from aggregator"; "aggregator" => &aggregator.name()); - let mithril_era = retrieve_current_era(aggregator).await?; - info!("> stopping aggregator"; "aggregator" => &aggregator.name()); - aggregator.stop().await?; - info!("> bootstrapping genesis using signers registered two epochs ago..."; "aggregator" => &aggregator.name()); - aggregator.bootstrap_genesis(&mithril_era).await?; - info!("> done, restarting aggregator"; "aggregator" => &aggregator.name()); - aggregator.serve().await?; - - Ok(()) + ExecToolkit::default().bootstrap_genesis_certificate(aggregator).await } pub async fn register_era_marker( @@ -55,65 +139,26 @@ pub async fn register_era_marker( mithril_era: &str, era_epoch: Epoch, ) -> StdResult<()> { - info!("Register '{mithril_era}' era marker"; "aggregator" => &aggregator.name()); - - info!("> generating era marker tx datum..."; "aggregator" => &aggregator.name()); - let tx_datum_file_path = devnet - .artifacts_dir() - .join(PathBuf::from("era-tx-datum.txt".to_string())); - aggregator - .era_generate_tx_datum(&tx_datum_file_path, mithril_era, era_epoch) - .await?; - - info!("> writing '{mithril_era}' era marker on the Cardano chain..."; "aggregator" => &aggregator.name()); - devnet.write_era_marker(&tx_datum_file_path).await?; - - Ok(()) + ExecToolkit::default() + .register_era_marker(aggregator, devnet, mithril_era, era_epoch) + .await } pub async fn delegate_stakes_to_pools(devnet: &Devnet, delegation_round: u16) -> StdResult<()> { - info!("Delegate stakes to the cardano pools"); - - devnet.delegate_stakes(delegation_round).await?; - - Ok(()) + ExecToolkit::default() + .delegate_stakes_to_pools(devnet, delegation_round) + .await } pub async fn transfer_funds(devnet: &Devnet) -> StdResult<()> { - info!("Transfer funds on the devnet"); - - devnet.transfer_funds().await?; - - Ok(()) + ExecToolkit::default().transfer_funds(devnet).await } pub async fn update_protocol_parameters( aggregator: &Aggregator, aggregate_signature_type: AggregateSignatureType, ) -> StdResult<()> { - info!("Update protocol parameters"; "aggregator" => &aggregator.name()); - - info!("> stopping aggregator"); - aggregator.stop().await?; - let protocol_parameters_new = match aggregate_signature_type { - AggregateSignatureType::Concatenation => ProtocolParameters { - k: 145, - m: 210, - phi_f: 0.80, - }, - AggregateSignatureType::Snark => ProtocolParameters { - k: 7, - m: 10, - phi_f: 0.95, - }, - }; - - info!( - "> updating protocol parameters to {protocol_parameters_new:?}..."; "aggregator" => &aggregator.name() - ); - aggregator.set_protocol_parameters(&protocol_parameters_new).await; - info!("> done, restarting aggregator"; "aggregator" => &aggregator.name()); - aggregator.serve().await?; - - Ok(()) + ExecToolkit::default() + .update_protocol_parameters(aggregator, aggregate_signature_type) + .await } diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs index 43560be71b8..a1495c07d3c 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs @@ -17,66 +17,119 @@ impl WaitToolkit { pub fn new(context: ScenarioToolkitContext) -> Self { Self { context } } -} -pub async fn wait_for_enough_immutable(aggregator: &Aggregator) -> StdResult<()> { - info!("Waiting that enough immutable have been written in the devnet"; "aggregator" => aggregator.name()); + pub async fn wait_for_enough_immutable(&self, aggregator: &Aggregator) -> StdResult<()> { + info!("Waiting that enough immutable have been written in the devnet"; "aggregator" => aggregator.name()); - let db_directory = aggregator.db_directory(); - match attempt!(24, Duration::from_secs(5), { - match ImmutableFile::list_completed_in_dir(db_directory) - .with_context(|| { - format!( - "Immutable file listing failed in dir `{}`", - db_directory.display(), - ) - })? - .last() - { - Some(_) => Ok(Some(())), - None => Ok(None), + let db_directory = aggregator.db_directory(); + match attempt!(24, Duration::from_secs(5), { + match ImmutableFile::list_completed_in_dir(db_directory) + .with_context(|| { + format!( + "Immutable file listing failed in dir `{}`", + db_directory.display(), + ) + })? + .last() + { + Some(_) => Ok(Some(())), + None => Ok(None), + } + }) { + AttemptResult::Ok(_) => Ok(()), + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted for enough immutable to be written in `{}`", + db_directory.display() + )), } - }) { - AttemptResult::Ok(_) => Ok(()), - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted for enough immutable to be written in `{}`", - db_directory.display() - )), } -} -pub async fn wait_for_epoch_settings(aggregator: &Aggregator) -> StdResult { - let aggregator_endpoint = aggregator.endpoint(); - let url = format!("{aggregator_endpoint}/epoch-settings"); - info!("Waiting for the aggregator to expose epoch settings"; "aggregator" => aggregator.name()); + pub async fn wait_for_epoch_settings( + &self, + aggregator: &Aggregator, + ) -> StdResult { + let aggregator_endpoint = aggregator.endpoint(); + let url = format!("{aggregator_endpoint}/epoch-settings"); + info!("Waiting for the aggregator to expose epoch settings"; "aggregator" => aggregator.name()); - match attempt!(20, Duration::from_millis(1000), { - match reqwest::get(url.clone()).await { - Ok(response) => match response.status() { - StatusCode::OK => { - let epoch_settings = response - .json::() - .await - .with_context(|| "Invalid EpochSettings body")?; - info!("Aggregator ready"; "epoch_settings" => ?epoch_settings); - Ok(Some(epoch_settings)) - } - s if s.is_server_error() => { - warn!( "Server error while waiting for the Aggregator, http code: {s}"; "aggregator" => aggregator.name()); - Ok(None) - } - _ => Ok(None), - }, - Err(_) => Ok(None), + match attempt!(20, Duration::from_millis(1000), { + match reqwest::get(url.clone()).await { + Ok(response) => match response.status() { + StatusCode::OK => { + let epoch_settings = response + .json::() + .await + .with_context(|| "Invalid EpochSettings body")?; + info!("Aggregator ready"; "epoch_settings" => ?epoch_settings); + Ok(Some(epoch_settings)) + } + s if s.is_server_error() => { + warn!( "Server error while waiting for the Aggregator, http code: {s}"; "aggregator" => aggregator.name()); + Ok(None) + } + _ => Ok(None), + }, + Err(_) => Ok(None), + } + }) { + AttemptResult::Ok(epoch_settings) => Ok(epoch_settings), + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted for aggregator to be up, no response from `{url}`" + )), } - }) { - AttemptResult::Ok(epoch_settings) => Ok(epoch_settings), - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted for aggregator to be up, no response from `{url}`" - )), } + + pub async fn wait_for_aggregator_at_target_epoch( + &self, + aggregator: &Aggregator, + target_epoch: Epoch, + wait_reason: String, + ) -> StdResult<()> { + info!( + "Waiting for the cardano network to be at the target epoch: {}", wait_reason; + "aggregator" => aggregator.name(), + "target_epoch" => ?target_epoch + ); + + match attempt!(90, Duration::from_millis(1000), { + match aggregator + .chain_observer() + .get_current_epoch() + .await + .with_context(|| "Could not query current epoch")? + { + Some(epoch) => { + if epoch >= target_epoch { + Ok(Some(())) + } else { + Ok(None) + } + } + None => Ok(None), + } + }) { + AttemptResult::Ok(_) => { + info!("Target epoch reached!"; "aggregator" => aggregator.name(), "target_epoch" => ?target_epoch); + Ok(()) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => { + Err(anyhow!("Timeout exhausted for target epoch to be reached")) + } + }?; + + Ok(()) + } +} + +pub async fn wait_for_enough_immutable(aggregator: &Aggregator) -> StdResult<()> { + WaitToolkit::default().wait_for_enough_immutable(aggregator).await +} + +pub async fn wait_for_epoch_settings(aggregator: &Aggregator) -> StdResult { + WaitToolkit::default().wait_for_epoch_settings(aggregator).await } pub async fn wait_for_aggregator_at_target_epoch( @@ -84,38 +137,7 @@ pub async fn wait_for_aggregator_at_target_epoch( target_epoch: Epoch, wait_reason: String, ) -> StdResult<()> { - info!( - "Waiting for the cardano network to be at the target epoch: {}", wait_reason; - "aggregator" => aggregator.name(), - "target_epoch" => ?target_epoch - ); - - match attempt!(90, Duration::from_millis(1000), { - match aggregator - .chain_observer() - .get_current_epoch() - .await - .with_context(|| "Could not query current epoch")? - { - Some(epoch) => { - if epoch >= target_epoch { - Ok(Some(())) - } else { - Ok(None) - } - } - None => Ok(None), - } - }) { - AttemptResult::Ok(_) => { - info!("Target epoch reached!"; "aggregator" => aggregator.name(), "target_epoch" => ?target_epoch); - Ok(()) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => { - Err(anyhow!("Timeout exhausted for target epoch to be reached")) - } - }?; - - Ok(()) + WaitToolkit::default() + .wait_for_aggregator_at_target_epoch(aggregator, target_epoch, wait_reason) + .await } From f8f023562804e5163a919ffc92b56102b4f8ffa2 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Fri, 5 Jun 2026 15:42:14 +0200 Subject: [PATCH 06/19] refactor(e2e): promote `check.rs` to module directory for future refactor --- .../mithril-end-to-end/src/toolkit/{check.rs => check/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename mithril-test-lab/mithril-end-to-end/src/toolkit/{check.rs => check/mod.rs} (100%) diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs similarity index 100% rename from mithril-test-lab/mithril-end-to-end/src/toolkit/check.rs rename to mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs From 586ae4504441047a368e5872005afcaa9667c228 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Fri, 5 Jun 2026 16:53:08 +0200 Subject: [PATCH 07/19] refactor(e2e): split check toolkits into subpart, one for each artificat types + certificate Integrated new specialized toolkits (`CheckCardanoBlocksTransactionsToolkit`, `CheckCardanoDatabaseToolkit`, `CheckCardanoStakeDistributionToolkit`, `CheckCardanoTransactionsToolkit`, `CheckCertificateToolkit`, `CheckMithrilStakeDistributionToolkit`) to enhance modularity and reusability in end-to-end testing scenarios. --- .../check/cardano_blocks_transactions.rs | 244 +++++ .../src/toolkit/check/cardano_database.rs | 197 ++++ .../check/cardano_stake_distribution.rs | 149 +++ .../src/toolkit/check/cardano_transactions.rs | 158 ++++ .../src/toolkit/check/certificate.rs | 67 ++ .../check/mithril_stake_distribution.rs | 136 +++ .../src/toolkit/check/mod.rs | 848 ++---------------- 7 files changed, 1002 insertions(+), 797 deletions(-) create mode 100644 mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs create mode 100644 mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs create mode 100644 mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs create mode 100644 mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs create mode 100644 mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs create mode 100644 mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs new file mode 100644 index 00000000000..bbae02abc2d --- /dev/null +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs @@ -0,0 +1,244 @@ +use anyhow::{Context, anyhow}; +use slog_scope::{info, warn}; +use std::time::Duration; + +use mithril_common::{ + StdResult, + entities::{BlockHash, Epoch, TransactionHash}, + messages::{ + CardanoBlocksTransactionsSnapshotListMessage, CardanoBlocksTransactionsSnapshotMessage, + }, +}; + +use crate::{ + Aggregator, CardanoBlockCommand, CardanoTransactionV2Command, Client, ClientCommand, attempt, + toolkit::{ScenarioToolkitContext, check::get_json_response}, + utils::AttemptResult, +}; + +#[derive(Debug, Clone, Default)] +pub struct CheckCardanoBlocksTransactionsToolkit { + context: ScenarioToolkitContext, +} + +impl CheckCardanoBlocksTransactionsToolkit { + pub fn new(context: ScenarioToolkitContext) -> Self { + Self { context } + } + + pub async fn node_producing_cardano_blocks_transactions( + &self, + aggregator: &Aggregator, + ) -> StdResult { + let url = format!( + "{}/artifact/cardano-blocks-transactions", + aggregator.endpoint() + ); + info!("Waiting for the aggregator to produce a Cardano blocks transactions artifact"; "aggregator" => &aggregator.name()); + + async fn fetch_last_cardano_blocks_transactions_snapshot_hash( + url: String, + ) -> StdResult> { + match get_json_response::(url) + .await? + .as_deref() + { + Ok([artifact, ..]) => Ok(Some(artifact.hash.clone())), + Ok(&[]) => Ok(None), + Err(err) => Err(anyhow!( + "Invalid Cardano blocks transactions artifact body: {err}", + )), + } + } + + match attempt!(30, Duration::from_millis(2000), { + fetch_last_cardano_blocks_transactions_snapshot_hash(url.clone()).await + }) { + AttemptResult::Ok(hash) => { + info!("Aggregator produced a Cardano blocks transactions artifact"; "hash" => &hash, "aggregator" => &aggregator.name()); + Ok(hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted assert_node_producing_cardano_blocks_transactions, no response from `{url}`" + )), + } + .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) + } + + pub async fn signer_is_signing_cardano_blocks_transactions( + &self, + aggregator: &Aggregator, + hash: &str, + expected_epoch_min: Epoch, + ) -> StdResult { + let url = format!( + "{}/artifact/cardano-blocks-transactions/{hash}", + aggregator.endpoint() + ); + info!( + "Asserting the aggregator is signing the Cardano blocks transactions artifact `{}` with an expected min epoch of `{}`", + hash, + expected_epoch_min; + "aggregator" => &aggregator.name() + ); + + async fn fetch_cardano_blocks_transactions_snapshot_message( + url: String, + expected_epoch_min: Epoch, + ) -> StdResult> { + match get_json_response::(url).await? { + Ok(artifact) => match artifact.epoch { + epoch if epoch >= expected_epoch_min => Ok(Some(artifact)), + epoch => Err(anyhow!( + "Minimum expected artifact epoch not reached: {epoch} < {expected_epoch_min}" + )), + }, + Err(err) => { + Err(anyhow!(err).context("Invalid Cardano blocks transactions artifact body")) + } + } + } + + match attempt!(10, Duration::from_millis(1000), { + fetch_cardano_blocks_transactions_snapshot_message(url.clone(), expected_epoch_min).await + }) { + AttemptResult::Ok(artifact) => { + info!("Signer signed a Cardano blocks transactions artifact"; "certificate_hash" => &artifact.certificate_hash, "aggregator" => &aggregator.name()); + Ok(artifact.certificate_hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted assert_signer_is_signing_cardano_blocks_transactions, no response from `{url}`" + )), + }.with_context(|| { + format!( + "Requesting aggregator `{}`", + aggregator.name() + ) + }) + } + + pub async fn client_verify_transactions( + &self, + client: &mut Client, + tx_hashes: Vec, + ) -> StdResult<()> { + #[allow(dead_code)] + #[derive(Debug, serde::Deserialize)] + struct ClientCardanoTransactionCertifyResult { + certified_transactions: Vec, + non_certified_transactions: Vec, + } + + #[derive(Debug, serde::Deserialize)] + struct CertifiedTransactionV2 { + transaction_hash: String, + } + + if !client.version().is_above_or_equal("0.13.1") { + warn!( + "Client version is below 0.13.1, skipping `cardano-transaction certify --backend v2` check" + ); + return Ok(()); + } + + let result_file = client + .run(ClientCommand::CardanoTransactionV2( + CardanoTransactionV2Command::Certify { + tx_hashes: tx_hashes.clone(), + }, + )) + .await?; + info!("Client verified the Cardano transactions V2"; "tx_hashes" => ?tx_hashes); + + let file = std::fs::read_to_string(&result_file).with_context(|| { + format!( + "Failed to read client output from file `{}`", + result_file.display() + ) + })?; + let result: ClientCardanoTransactionCertifyResult = serde_json::from_str(&file) + .with_context(|| { + format!( + "Failed to parse client output as json from file `{}`", + result_file.display() + ) + })?; + + info!("Asserting that all Cardano transactions V2 were verified by the Client..."); + let certified_tx_hashes_result: Vec = result + .certified_transactions + .iter() + .map(|tx| tx.transaction_hash.clone()) + .collect(); + + if tx_hashes.iter().all(|tx| certified_tx_hashes_result.contains(tx)) { + Ok(()) + } else { + Err(anyhow!( + "Not all transactions V2 were certified:\n'{:#?}'", + result, + )) + } + } + + pub async fn client_verify_blocks( + &self, + client: &mut Client, + block_hashes: Vec, + ) -> StdResult<()> { + #[allow(dead_code)] + #[derive(Debug, serde::Deserialize)] + struct ClientCardanoBlockCertifyResult { + certified_blocks: Vec, + non_certified_blocks: Vec, + } + + #[derive(Debug, serde::Deserialize)] + struct CertifiedBlock { + block_hash: String, + } + + if !client.version().is_above_or_equal("0.13.1") { + warn!("Client version is below 0.13.1, skipping `cardano-block certify` check"); + return Ok(()); + } + + let result_file = client + .run(ClientCommand::CardanoBlock(CardanoBlockCommand::Certify { + block_hashes: block_hashes.clone(), + })) + .await?; + info!("Client verified the Cardano blocks"; "block_hashes" => ?block_hashes); + let file = std::fs::read_to_string(&result_file).with_context(|| { + format!( + "Failed to read client output from file `{}`", + result_file.display() + ) + })?; + let result: ClientCardanoBlockCertifyResult = + serde_json::from_str(&file).with_context(|| { + format!( + "Failed to parse client output as json from file `{}`", + result_file.display() + ) + })?; + + info!("Asserting that all Cardano blocks were verified by the Client..."); + let certified_blocks_hashes_result: Vec = result + .certified_blocks + .iter() + .map(|block| block.block_hash.clone()) + .collect(); + + if block_hashes + .iter() + .all(|block| certified_blocks_hashes_result.contains(block)) + { + Ok(()) + } else { + Err(anyhow!("Not all blocks were certified:\n'{:#?}'", result,)) + } + } +} diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs new file mode 100644 index 00000000000..88ddaa4c29f --- /dev/null +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs @@ -0,0 +1,197 @@ +use anyhow::{Context, anyhow}; +use slog_scope::{info, warn}; +use std::time::Duration; + +use mithril_common::{ + StdResult, + entities::{Epoch, EpochSpecifier}, + messages::{ + CardanoDatabaseDigestListMessage, CardanoDatabaseSnapshotListMessage, + CardanoDatabaseSnapshotMessage, + }, +}; + +use crate::{ + Aggregator, CardanoDbV2Command, Client, ClientCommand, attempt, + toolkit::{ScenarioToolkitContext, check::get_json_response}, + utils::AttemptResult, +}; + +#[derive(Debug, Clone, Default)] +pub struct CheckCardanoDatabaseToolkit { + context: ScenarioToolkitContext, +} + +impl CheckCardanoDatabaseToolkit { + pub fn new(context: ScenarioToolkitContext) -> Self { + Self { context } + } + + pub async fn node_producing_cardano_database_snapshot( + &self, + aggregator: &Aggregator, + ) -> StdResult { + let url = format!("{}/artifact/cardano-database", aggregator.endpoint()); + info!("Waiting for the aggregator to produce a Cardano database snapshot"; "aggregator" => &aggregator.name()); + + async fn fetch_last_cardano_database_snapshot_hash( + url: String, + ) -> StdResult> { + match get_json_response::(url) + .await? + .as_deref() + { + Ok([cardano_database_snapshot, ..]) => { + Ok(Some(cardano_database_snapshot.hash.clone())) + } + Ok(&[]) => Ok(None), + Err(err) => Err(anyhow!("Invalid Cardano database snapshot body: {err}",)), + } + } + + match attempt!(30, Duration::from_millis(2000), { + fetch_last_cardano_database_snapshot_hash(url.clone()).await + }) { + AttemptResult::Ok(hash) => { + info!("Aggregator produced a Cardano database snapshot"; "hash" => &hash, "aggregator" => &aggregator.name()); + Ok(hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted assert_node_producing_snapshot, no response from `{url}`" + )), + } + .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) + } + + pub async fn signer_is_signing_cardano_database_snapshot( + &self, + aggregator: &Aggregator, + hash: &str, + expected_epoch_min: Epoch, + ) -> StdResult { + let url = format!("{}/artifact/cardano-database/{hash}", aggregator.endpoint()); + info!( + "Asserting the aggregator is signing the Cardano database snapshot message `{}` with an expected min epoch of `{}`", + hash, + expected_epoch_min; + "aggregator" => &aggregator.name() + ); + + async fn fetch_cardano_database_snapshot_message( + url: String, + expected_epoch_min: Epoch, + ) -> StdResult> { + match get_json_response::(url).await? { + Ok(cardano_database_snapshot) => match cardano_database_snapshot.beacon.epoch { + epoch if epoch >= expected_epoch_min => Ok(Some(cardano_database_snapshot)), + epoch => Err(anyhow!( + "Minimum expected Cardano database snapshot epoch not reached: {epoch} < {expected_epoch_min}" + )), + }, + Err(err) => Err(anyhow!(err).context("Invalid Cardano database snapshot body")), + } + } + + match attempt!(10, Duration::from_millis(1000), { + fetch_cardano_database_snapshot_message(url.clone(), expected_epoch_min).await + }) { + AttemptResult::Ok(snapshot) => { + info!("Signer signed a snapshot"; "certificate_hash" => &snapshot.certificate_hash, "aggregator" => &aggregator.name()); + Ok(snapshot.certificate_hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted assert_signer_is_signing_snapshot, no response from `{url}`" + )), + } + .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) + } + + pub async fn node_producing_cardano_database_digests_map( + &self, + aggregator: &Aggregator, + ) -> StdResult> { + let url = format!( + "{}/artifact/cardano-database/digests", + aggregator.endpoint() + ); + info!("Waiting for the aggregator to produce a Cardano database digests map"; "aggregator" => &aggregator.name()); + + async fn fetch_cardano_database_digests_map( + url: String, + ) -> StdResult>> { + match get_json_response::(url) + .await? + .as_deref() + { + Ok(&[]) => Ok(None), + Ok(cardano_database_digests_map) => Ok(Some( + cardano_database_digests_map + .iter() + .map(|item| (item.immutable_file_name.clone(), item.digest.clone())) + .collect(), + )), + Err(err) => Err(anyhow!("Invalid Cardano database digests map body: {err}",)), + } + } + + match attempt!(30, Duration::from_millis(2000), { + fetch_cardano_database_digests_map(url.clone()).await + }) { + AttemptResult::Ok(cardano_database_digests_map) => { + info!("Aggregator produced a Cardano database digests map"; "total_digests" => &cardano_database_digests_map.len(), "aggregator" => &aggregator.name()); + Ok(cardano_database_digests_map) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted assert_node_producing_cardano_database_digests_map, no response from `{url}`" + )), + }.with_context(|| { + format!( + "Requesting aggregator `{}`", + aggregator.name() + ) + }) + } + + pub async fn client_can_verify_cardano_database( + &self, + client: &mut Client, + hash: &str, + ) -> StdResult<()> { + client + .run(ClientCommand::CardanoDbV2(CardanoDbV2Command::List)) + .await?; + + if client.version().is_above_or_equal("0.12.34") { + client + .run(ClientCommand::CardanoDbV2( + CardanoDbV2Command::ListPerEpoch { + epoch_specifier: EpochSpecifier::LatestMinusOffset(5), + }, + )) + .await?; + } else { + warn!( + "Client version is below 0.12.34, skipping `cardano-db snapshot list --epoch latest-5` check" + ); + } + + client + .run(ClientCommand::CardanoDbV2(CardanoDbV2Command::Show { + hash: hash.to_string(), + })) + .await?; + info!("Client list & show the cardano database snapshot"; "hash" => &hash); + + client + .run(ClientCommand::CardanoDbV2(CardanoDbV2Command::Download { + hash: hash.to_string(), + })) + .await?; + info!("Client downloaded & restored the cardano database snapshot"; "hash" => &hash); + + Ok(()) + } +} diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs new file mode 100644 index 00000000000..3272c99e76a --- /dev/null +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs @@ -0,0 +1,149 @@ +use anyhow::{Context, anyhow}; +use slog_scope::info; +use std::time::Duration; + +use mithril_common::{ + StdResult, + entities::Epoch, + messages::{CardanoStakeDistributionListMessage, CardanoStakeDistributionMessage}, +}; + +use crate::{ + Aggregator, CardanoStakeDistributionCommand, Client, ClientCommand, attempt, + toolkit::{ScenarioToolkitContext, check::get_json_response}, + utils::AttemptResult, +}; + +#[derive(Debug, Clone, Default)] +pub struct CheckCardanoStakeDistributionToolkit { + context: ScenarioToolkitContext, +} + +impl CheckCardanoStakeDistributionToolkit { + pub fn new(context: ScenarioToolkitContext) -> Self { + Self { context } + } + + pub async fn node_producing_cardano_stake_distribution( + &self, + aggregator: &Aggregator, + ) -> StdResult<(String, Epoch)> { + let url = format!( + "{}/artifact/cardano-stake-distributions", + aggregator.endpoint() + ); + info!("Waiting for the aggregator to produce a Cardano stake distribution"; "aggregator" => &aggregator.name()); + + async fn fetch_last_cardano_stake_distribution_message( + url: String, + ) -> StdResult> { + match get_json_response::(url) + .await? + .as_deref() + { + Ok([stake_distribution, ..]) => Ok(Some(( + stake_distribution.hash.clone(), + stake_distribution.epoch, + ))), + Ok(&[]) => Ok(None), + Err(err) => Err(anyhow!("Invalid Cardano stake distribution body: {err}",)), + } + } + + match attempt!(30, Duration::from_millis(2000), { + fetch_last_cardano_stake_distribution_message(url.clone()).await + }) { + AttemptResult::Ok((hash, epoch)) => { + info!("Aggregator produced a Cardano stake distribution"; "hash" => &hash, "epoch" => #?epoch, "aggregator" => &aggregator.name()); + Ok((hash, epoch)) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted assert_node_producing_cardano_stake_distribution, no response from `{url}`" + )), + }.with_context(|| { + format!( + "Requesting aggregator `{}`", + aggregator.name() + ) + }) + } + + pub async fn signer_is_signing_cardano_stake_distribution( + &self, + aggregator: &Aggregator, + hash: &str, + expected_epoch_min: Epoch, + ) -> StdResult { + let url = format!( + "{}/artifact/cardano-stake-distribution/{hash}", + aggregator.endpoint() + ); + info!( + "Asserting the aggregator is signing the Cardano stake distribution message `{}` with an expected min epoch of `{}`", + hash, + expected_epoch_min; + "aggregator" => &aggregator.name() + ); + + async fn fetch_cardano_stake_distribution_message( + url: String, + expected_epoch_min: Epoch, + ) -> StdResult> { + match get_json_response::(url).await? { + Ok(stake_distribution) => match stake_distribution.epoch { + epoch if epoch >= expected_epoch_min => Ok(Some(stake_distribution)), + epoch => Err(anyhow!( + "Minimum expected Cardano stake distribution epoch not reached: {epoch} < {expected_epoch_min}" + )), + }, + Err(err) => Err(anyhow!(err).context("Invalid Cardano stake distribution body")), + } + } + + match attempt!(10, Duration::from_millis(1000), { + fetch_cardano_stake_distribution_message(url.clone(), expected_epoch_min).await + }) { + AttemptResult::Ok(cardano_stake_distribution) => { + info!("Signer signed a Cardano stake distribution"; "certificate_hash" => &cardano_stake_distribution.certificate_hash, "aggregator" => &aggregator.name()); + Ok(cardano_stake_distribution.certificate_hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted assert_signer_is_signing_cardano_stake_distribution, no response from `{url}`" + )), + }.with_context(|| { + format!( + "Requesting aggregator `{}`", + aggregator.name() + ) + }) + } + + pub async fn client_can_verify_cardano_stake_distribution( + &self, + client: &mut Client, + hash: &str, + epoch: Epoch, + ) -> StdResult<()> { + client + .run(ClientCommand::CardanoStakeDistribution( + CardanoStakeDistributionCommand::Download { + unique_identifier: epoch.to_string(), + }, + )) + .await?; + info!("Client downloaded the Cardano stake distribution by epoch"; "epoch" => epoch.to_string()); + + client + .run(ClientCommand::CardanoStakeDistribution( + CardanoStakeDistributionCommand::Download { + unique_identifier: hash.to_string(), + }, + )) + .await?; + info!("Client downloaded the Cardano stake distribution by hash"; "hash" => hash.to_string()); + + Ok(()) + } +} diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs new file mode 100644 index 00000000000..230a8a1a3a3 --- /dev/null +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs @@ -0,0 +1,158 @@ +use anyhow::{Context, anyhow}; +use slog_scope::info; +use std::time::Duration; + +use mithril_common::{ + StdResult, + entities::{Epoch, TransactionHash}, + messages::{CardanoTransactionSnapshotListMessage, CardanoTransactionSnapshotMessage}, +}; + +use crate::{ + Aggregator, CardanoTransactionCommand, Client, ClientCommand, attempt, + toolkit::{ScenarioToolkitContext, check::get_json_response}, + utils::AttemptResult, +}; + +#[derive(Debug, Clone, Default)] +pub struct CheckCardanoTransactionsToolkit { + context: ScenarioToolkitContext, +} + +impl CheckCardanoTransactionsToolkit { + pub fn new(context: ScenarioToolkitContext) -> Self { + Self { context } + } + + pub async fn node_producing_cardano_transactions( + &self, + aggregator: &Aggregator, + ) -> StdResult { + let url = format!("{}/artifact/cardano-transactions", aggregator.endpoint()); + info!("Waiting for the aggregator to produce a Cardano transactions artifact"; "aggregator" => &aggregator.name(), "aggregator" => &aggregator.name()); + + async fn fetch_last_cardano_transaction_snapshot_hash( + url: String, + ) -> StdResult> { + match get_json_response::(url) + .await? + .as_deref() + { + Ok([artifact, ..]) => Ok(Some(artifact.hash.clone())), + Ok(&[]) => Ok(None), + Err(err) => Err(anyhow!("Invalid Cardano transactions artifact body: {err}",)), + } + } + + match attempt!(30, Duration::from_millis(2000), { + fetch_last_cardano_transaction_snapshot_hash(url.clone()).await + }) { + AttemptResult::Ok(hash) => { + info!("Aggregator produced a Cardano transactions artifact"; "hash" => &hash, "aggregator" => &aggregator.name()); + Ok(hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted assert_node_producing_cardano_transactions, no response from `{url}`" + )), + } + .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) + } + + pub async fn signer_is_signing_cardano_transactions( + &self, + aggregator: &Aggregator, + hash: &str, + expected_epoch_min: Epoch, + ) -> StdResult { + let url = format!( + "{}/artifact/cardano-transaction/{hash}", + aggregator.endpoint() + ); + info!( + "Asserting the aggregator is signing the Cardano transactions artifact `{}` with an expected min epoch of `{}`", + hash, + expected_epoch_min; + "aggregator" => &aggregator.name() + ); + + async fn fetch_cardano_transaction_snapshot_message( + url: String, + expected_epoch_min: Epoch, + ) -> StdResult> { + match get_json_response::(url).await? { + Ok(artifact) => match artifact.epoch { + epoch if epoch >= expected_epoch_min => Ok(Some(artifact)), + epoch => Err(anyhow!( + "Minimum expected artifact epoch not reached: {epoch} < {expected_epoch_min}" + )), + }, + Err(err) => Err(anyhow!(err).context("Invalid Cardano transactions artifact body")), + } + } + + match attempt!(10, Duration::from_millis(1000), { + fetch_cardano_transaction_snapshot_message(url.clone(), expected_epoch_min).await + }) { + AttemptResult::Ok(artifact) => { + info!("Signer signed a Cardano transactions artifact"; "certificate_hash" => &artifact.certificate_hash, "aggregator" => &aggregator.name()); + Ok(artifact.certificate_hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted assert_signer_is_signing_cardano_transactions, no response from `{url}`" + )), + }.with_context(|| { + format!( + "Requesting aggregator `{}`", + aggregator.name() + ) + }) + } + + pub async fn client_can_verify_transactions( + &self, + client: &mut Client, + tx_hashes: Vec, + ) -> StdResult<()> { + #[allow(dead_code)] + #[derive(Debug, serde::Deserialize)] + struct ClientCardanoTransactionCertifyResult { + certified_transactions: Vec, + non_certified_transactions: Vec, + } + + let result_file = client + .run(ClientCommand::CardanoTransaction( + CardanoTransactionCommand::Certify { + tx_hashes: tx_hashes.clone(), + }, + )) + .await?; + info!("Client verified the Cardano transactions"; "tx_hashes" => ?tx_hashes); + + let file = std::fs::read_to_string(&result_file).with_context(|| { + format!( + "Failed to read client output from file `{}`", + result_file.display() + ) + })?; + let result: ClientCardanoTransactionCertifyResult = serde_json::from_str(&file) + .with_context(|| { + format!( + "Failed to parse client output as json from file `{}`", + result_file.display() + ) + })?; + + info!("Asserting that all Cardano transactions were verified by the Client..."); + if tx_hashes.iter().all(|tx| result.certified_transactions.contains(tx)) { + Ok(()) + } else { + Err(anyhow!( + "Not all transactions were certified:\n'{:#?}'", + result, + )) + } + } +} diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs new file mode 100644 index 00000000000..20ab637bdac --- /dev/null +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs @@ -0,0 +1,67 @@ +use anyhow::{Context, anyhow}; +use slog_scope::info; +use std::time::Duration; + +use mithril_common::{StdResult, messages::CertificateMessage}; + +use crate::{ + Aggregator, attempt, + toolkit::{ScenarioToolkitContext, check::get_json_response}, + utils::AttemptResult, +}; + +#[derive(Debug, Clone, Default)] +pub struct CheckCertificateToolkit { + context: ScenarioToolkitContext, +} + +impl CheckCertificateToolkit { + pub fn new(context: ScenarioToolkitContext) -> Self { + Self { context } + } + + pub async fn is_creating_certificate_with_enough_signers( + &self, + aggregator: &Aggregator, + certificate_hash: &str, + total_signers_expected: usize, + ) -> StdResult<()> { + let url = format!("{}/certificate/{certificate_hash}", aggregator.endpoint()); + info!("Waiting for the aggregator to create a certificate with enough signers"; "aggregator" => &aggregator.name()); + + async fn fetch_certificate_message(url: String) -> StdResult> { + match get_json_response::(url).await? { + Ok(certificate) => Ok(Some(certificate)), + Err(err) => Err(anyhow!(err).context("Invalid snapshot body")), + } + } + + match attempt!(10, Duration::from_millis(1000), { + fetch_certificate_message(url.clone()).await + }) { + 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 ; + "aggregator" => &aggregator.name() + ); + Ok(()) + } else { + Err(anyhow!( + "Certificate is not signed by expected number of signers: {} < {} ", + certificate.metadata.signers.len(), + total_signers_expected + )) + } + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted assert_is_creating_certificate, no response from `{url}`" + )), + } + .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) + } +} diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs new file mode 100644 index 00000000000..d38a47b9d8f --- /dev/null +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs @@ -0,0 +1,136 @@ +use anyhow::{Context, anyhow}; +use slog_scope::info; +use std::time::Duration; + +use mithril_common::{ + StdResult, + entities::Epoch, + messages::{MithrilStakeDistributionListMessage, MithrilStakeDistributionMessage}, +}; + +use crate::{ + Aggregator, Client, ClientCommand, MithrilStakeDistributionCommand, attempt, + toolkit::{ScenarioToolkitContext, check::get_json_response}, + utils::AttemptResult, +}; + +#[derive(Debug, Clone, Default)] +pub struct CheckMithrilStakeDistributionToolkit { + context: ScenarioToolkitContext, +} + +impl CheckMithrilStakeDistributionToolkit { + pub fn new(context: ScenarioToolkitContext) -> Self { + Self { context } + } + + pub async fn node_producing_mithril_stake_distribution( + &self, + aggregator: &Aggregator, + ) -> StdResult { + let url = format!( + "{}/artifact/mithril-stake-distributions", + aggregator.endpoint() + ); + info!("Waiting for the aggregator to produce a mithril stake distribution"; "aggregator" => &aggregator.name()); + + async fn fetch_last_mithril_stake_distribution_hash( + url: String, + ) -> StdResult> { + match get_json_response::(url) + .await? + .as_deref() + { + Ok([stake_distribution, ..]) => Ok(Some(stake_distribution.hash.clone())), + Ok(&[]) => Ok(None), + Err(err) => Err(anyhow!("Invalid mithril stake distribution body: {err}",)), + } + } + + match attempt!(30, Duration::from_secs(3), { + fetch_last_mithril_stake_distribution_hash(url.clone()).await + }) { + AttemptResult::Ok(hash) => { + info!("Aggregator produced a mithril stake distribution"; "hash" => &hash, "aggregator" => &aggregator.name()); + Ok(hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted assert_node_producing_mithril_stake_distribution, no response from `{url}`" + )), + }.with_context(|| { + format!( + "Requesting aggregator `{}`", + aggregator.name() + ) + }) + } + + pub async fn signer_is_signing_mithril_stake_distribution( + &self, + aggregator: &Aggregator, + hash: &str, + expected_epoch_min: Epoch, + ) -> StdResult { + let url = format!( + "{}/artifact/mithril-stake-distribution/{hash}", + aggregator.endpoint() + ); + info!( + "Asserting the aggregator is signing the mithril stake distribution message `{}` with an expected min epoch of `{}`", + hash, + expected_epoch_min; + "aggregator" => &aggregator.name() + ); + + async fn fetch_mithril_stake_distribution_message( + url: String, + expected_epoch_min: Epoch, + ) -> StdResult> { + match get_json_response::(url.clone()).await? { + Ok(stake_distribution) => match stake_distribution.epoch { + epoch if epoch >= expected_epoch_min => Ok(Some(stake_distribution)), + epoch => Err(anyhow!( + "Minimum expected mithril stake distribution epoch not reached: {epoch} < {expected_epoch_min}" + )), + }, + Err(err) => Err(anyhow!("Invalid mithril stake distribution body: {err}",)), + } + } + + match attempt!(10, Duration::from_millis(1000), { + fetch_mithril_stake_distribution_message(url.clone(), expected_epoch_min).await + }) { + AttemptResult::Ok(stake_distribution) => { + info!("Signer signed a mithril stake distribution"; "certificate_hash" => &stake_distribution.certificate_hash, "aggregator" => &aggregator.name()); + Ok(stake_distribution.certificate_hash) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted assert_signer_is_signing_mithril_stake_distribution, no response from `{url}`" + )), + }.with_context(|| { + format!( + "Requesting aggregator `{}`", + aggregator.name() + ) + }) + } + + pub async fn client_can_verify_mithril_stake_distribution( + &self, + client: &mut Client, + hash: &str, + ) -> StdResult<()> { + client + .run(ClientCommand::MithrilStakeDistribution( + MithrilStakeDistributionCommand::Download { + hash: hash.to_owned(), + }, + )) + .await?; + info!("Client downloaded the Mithril stake distribution"; "hash" => &hash); + + Ok(()) + } +} diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs index 9de2e5d17e8..356f079eff2 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs @@ -1,3 +1,19 @@ +#![allow(unused_imports)] + +mod cardano_blocks_transactions; +mod cardano_database; +mod cardano_stake_distribution; +mod cardano_transactions; +mod certificate; +mod mithril_stake_distribution; + +pub use cardano_blocks_transactions::*; +pub use cardano_database::*; +pub use cardano_stake_distribution::*; +pub use cardano_transactions::*; +pub use certificate::*; +pub use mithril_stake_distribution::*; + use std::{path::PathBuf, time::Duration}; use anyhow::{Context, anyhow}; @@ -41,806 +57,26 @@ async fn get_json_response(url: String) -> StdResult Self { - Self { context } - } - - pub async fn node_producing_mithril_stake_distribution( - &self, - aggregator: &Aggregator, - ) -> StdResult { - let url = format!( - "{}/artifact/mithril-stake-distributions", - aggregator.endpoint() - ); - info!("Waiting for the aggregator to produce a mithril stake distribution"; "aggregator" => &aggregator.name()); - - async fn fetch_last_mithril_stake_distribution_hash( - url: String, - ) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([stake_distribution, ..]) => Ok(Some(stake_distribution.hash.clone())), - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!("Invalid mithril stake distribution body: {err}",)), - } - } - - match attempt!(30, Duration::from_secs(3), { - fetch_last_mithril_stake_distribution_hash(url.clone()).await - }) { - AttemptResult::Ok(hash) => { - info!("Aggregator produced a mithril stake distribution"; "hash" => &hash, "aggregator" => &aggregator.name()); - Ok(hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_node_producing_mithril_stake_distribution, no response from `{url}`" - )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) - } - - pub async fn signer_is_signing_mithril_stake_distribution( - &self, - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, - ) -> StdResult { - let url = format!( - "{}/artifact/mithril-stake-distribution/{hash}", - aggregator.endpoint() - ); - info!( - "Asserting the aggregator is signing the mithril stake distribution message `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_mithril_stake_distribution_message( - url: String, - expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url.clone()).await? { - Ok(stake_distribution) => match stake_distribution.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(stake_distribution)), - epoch => Err(anyhow!( - "Minimum expected mithril stake distribution epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => Err(anyhow!("Invalid mithril stake distribution body: {err}",)), - } - } - - match attempt!(10, Duration::from_millis(1000), { - fetch_mithril_stake_distribution_message(url.clone(), expected_epoch_min).await - }) { - AttemptResult::Ok(stake_distribution) => { - info!("Signer signed a mithril stake distribution"; "certificate_hash" => &stake_distribution.certificate_hash, "aggregator" => &aggregator.name()); - Ok(stake_distribution.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_signer_is_signing_mithril_stake_distribution, no response from `{url}`" - )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) - } - - pub async fn node_producing_cardano_database_snapshot( - &self, - aggregator: &Aggregator, - ) -> StdResult { - let url = format!("{}/artifact/cardano-database", aggregator.endpoint()); - info!("Waiting for the aggregator to produce a Cardano database snapshot"; "aggregator" => &aggregator.name()); - - async fn fetch_last_cardano_database_snapshot_hash( - url: String, - ) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([cardano_database_snapshot, ..]) => { - Ok(Some(cardano_database_snapshot.hash.clone())) - } - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!("Invalid Cardano database snapshot body: {err}",)), - } - } - - match attempt!(30, Duration::from_millis(2000), { - fetch_last_cardano_database_snapshot_hash(url.clone()).await - }) { - AttemptResult::Ok(hash) => { - info!("Aggregator produced a Cardano database snapshot"; "hash" => &hash, "aggregator" => &aggregator.name()); - Ok(hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_node_producing_snapshot, no response from `{url}`" - )), - } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) - } - - pub async fn signer_is_signing_cardano_database_snapshot( - &self, - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, - ) -> StdResult { - let url = format!("{}/artifact/cardano-database/{hash}", aggregator.endpoint()); - info!( - "Asserting the aggregator is signing the Cardano database snapshot message `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_cardano_database_snapshot_message( - url: String, - expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url).await? { - Ok(cardano_database_snapshot) => match cardano_database_snapshot.beacon.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(cardano_database_snapshot)), - epoch => Err(anyhow!( - "Minimum expected Cardano database snapshot epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => Err(anyhow!(err).context("Invalid Cardano database snapshot body")), - } - } - - match attempt!(10, Duration::from_millis(1000), { - fetch_cardano_database_snapshot_message(url.clone(), expected_epoch_min).await - }) { - AttemptResult::Ok(snapshot) => { - info!("Signer signed a snapshot"; "certificate_hash" => &snapshot.certificate_hash, "aggregator" => &aggregator.name()); - Ok(snapshot.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_signer_is_signing_snapshot, no response from `{url}`" - )), - } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) - } - - pub async fn node_producing_cardano_database_digests_map( - &self, - aggregator: &Aggregator, - ) -> StdResult> { - let url = format!( - "{}/artifact/cardano-database/digests", - aggregator.endpoint() - ); - info!("Waiting for the aggregator to produce a Cardano database digests map"; "aggregator" => &aggregator.name()); - - async fn fetch_cardano_database_digests_map( - url: String, - ) -> StdResult>> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok(&[]) => Ok(None), - Ok(cardano_database_digests_map) => Ok(Some( - cardano_database_digests_map - .iter() - .map(|item| (item.immutable_file_name.clone(), item.digest.clone())) - .collect(), - )), - Err(err) => Err(anyhow!("Invalid Cardano database digests map body: {err}",)), - } - } - - match attempt!(30, Duration::from_millis(2000), { - fetch_cardano_database_digests_map(url.clone()).await - }) { - AttemptResult::Ok(cardano_database_digests_map) => { - info!("Aggregator produced a Cardano database digests map"; "total_digests" => &cardano_database_digests_map.len(), "aggregator" => &aggregator.name()); - Ok(cardano_database_digests_map) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_node_producing_cardano_database_digests_map, no response from `{url}`" - )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) - } - - pub async fn node_producing_cardano_transactions( - &self, - aggregator: &Aggregator, - ) -> StdResult { - let url = format!("{}/artifact/cardano-transactions", aggregator.endpoint()); - info!("Waiting for the aggregator to produce a Cardano transactions artifact"; "aggregator" => &aggregator.name(), "aggregator" => &aggregator.name()); - - async fn fetch_last_cardano_transaction_snapshot_hash( - url: String, - ) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([artifact, ..]) => Ok(Some(artifact.hash.clone())), - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!("Invalid Cardano transactions artifact body: {err}",)), - } + Self { + cardano_blocks_transactions: CheckCardanoBlocksTransactionsToolkit::new( + context.clone(), + ), + cardano_database: CheckCardanoDatabaseToolkit::new(context.clone()), + cardano_stake_distribution: CheckCardanoStakeDistributionToolkit::new(context.clone()), + cardano_transactions: CheckCardanoTransactionsToolkit::new(context.clone()), + certificate: CheckCertificateToolkit::new(context.clone()), + mithril_stake_distribution: CheckMithrilStakeDistributionToolkit::new(context.clone()), } - - match attempt!(30, Duration::from_millis(2000), { - fetch_last_cardano_transaction_snapshot_hash(url.clone()).await - }) { - AttemptResult::Ok(hash) => { - info!("Aggregator produced a Cardano transactions artifact"; "hash" => &hash, "aggregator" => &aggregator.name()); - Ok(hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_node_producing_cardano_transactions, no response from `{url}`" - )), - } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) - } - - pub async fn signer_is_signing_cardano_transactions( - &self, - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, - ) -> StdResult { - let url = format!( - "{}/artifact/cardano-transaction/{hash}", - aggregator.endpoint() - ); - info!( - "Asserting the aggregator is signing the Cardano transactions artifact `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_cardano_transaction_snapshot_message( - url: String, - expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url).await? { - Ok(artifact) => match artifact.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(artifact)), - epoch => Err(anyhow!( - "Minimum expected artifact epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => Err(anyhow!(err).context("Invalid Cardano transactions artifact body")), - } - } - - match attempt!(10, Duration::from_millis(1000), { - fetch_cardano_transaction_snapshot_message(url.clone(), expected_epoch_min).await - }) { - AttemptResult::Ok(artifact) => { - info!("Signer signed a Cardano transactions artifact"; "certificate_hash" => &artifact.certificate_hash, "aggregator" => &aggregator.name()); - Ok(artifact.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_signer_is_signing_cardano_transactions, no response from `{url}`" - )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) - } - - pub async fn node_producing_cardano_blocks_transactions( - &self, - aggregator: &Aggregator, - ) -> StdResult { - let url = format!( - "{}/artifact/cardano-blocks-transactions", - aggregator.endpoint() - ); - info!("Waiting for the aggregator to produce a Cardano blocks transactions artifact"; "aggregator" => &aggregator.name()); - - async fn fetch_last_cardano_blocks_transactions_snapshot_hash( - url: String, - ) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([artifact, ..]) => Ok(Some(artifact.hash.clone())), - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!( - "Invalid Cardano blocks transactions artifact body: {err}", - )), - } - } - - match attempt!(30, Duration::from_millis(2000), { - fetch_last_cardano_blocks_transactions_snapshot_hash(url.clone()).await - }) { - AttemptResult::Ok(hash) => { - info!("Aggregator produced a Cardano blocks transactions artifact"; "hash" => &hash, "aggregator" => &aggregator.name()); - Ok(hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_node_producing_cardano_blocks_transactions, no response from `{url}`" - )), - } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) - } - - pub async fn signer_is_signing_cardano_blocks_transactions( - &self, - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, - ) -> StdResult { - let url = format!( - "{}/artifact/cardano-blocks-transactions/{hash}", - aggregator.endpoint() - ); - info!( - "Asserting the aggregator is signing the Cardano blocks transactions artifact `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_cardano_blocks_transactions_snapshot_message( - url: String, - expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url).await? { - Ok(artifact) => match artifact.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(artifact)), - epoch => Err(anyhow!( - "Minimum expected artifact epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => { - Err(anyhow!(err).context("Invalid Cardano blocks transactions artifact body")) - } - } - } - - match attempt!(10, Duration::from_millis(1000), { - fetch_cardano_blocks_transactions_snapshot_message(url.clone(), expected_epoch_min).await - }) { - AttemptResult::Ok(artifact) => { - info!("Signer signed a Cardano blocks transactions artifact"; "certificate_hash" => &artifact.certificate_hash, "aggregator" => &aggregator.name()); - Ok(artifact.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_signer_is_signing_cardano_blocks_transactions, no response from `{url}`" - )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) - } - - pub async fn node_producing_cardano_stake_distribution( - &self, - aggregator: &Aggregator, - ) -> StdResult<(String, Epoch)> { - let url = format!( - "{}/artifact/cardano-stake-distributions", - aggregator.endpoint() - ); - info!("Waiting for the aggregator to produce a Cardano stake distribution"; "aggregator" => &aggregator.name()); - - async fn fetch_last_cardano_stake_distribution_message( - url: String, - ) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([stake_distribution, ..]) => Ok(Some(( - stake_distribution.hash.clone(), - stake_distribution.epoch, - ))), - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!("Invalid Cardano stake distribution body: {err}",)), - } - } - - match attempt!(30, Duration::from_millis(2000), { - fetch_last_cardano_stake_distribution_message(url.clone()).await - }) { - AttemptResult::Ok((hash, epoch)) => { - info!("Aggregator produced a Cardano stake distribution"; "hash" => &hash, "epoch" => #?epoch, "aggregator" => &aggregator.name()); - Ok((hash, epoch)) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_node_producing_cardano_stake_distribution, no response from `{url}`" - )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) - } - - pub async fn signer_is_signing_cardano_stake_distribution( - &self, - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, - ) -> StdResult { - let url = format!( - "{}/artifact/cardano-stake-distribution/{hash}", - aggregator.endpoint() - ); - info!( - "Asserting the aggregator is signing the Cardano stake distribution message `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_cardano_stake_distribution_message( - url: String, - expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url).await? { - Ok(stake_distribution) => match stake_distribution.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(stake_distribution)), - epoch => Err(anyhow!( - "Minimum expected Cardano stake distribution epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => Err(anyhow!(err).context("Invalid Cardano stake distribution body")), - } - } - - match attempt!(10, Duration::from_millis(1000), { - fetch_cardano_stake_distribution_message(url.clone(), expected_epoch_min).await - }) { - AttemptResult::Ok(cardano_stake_distribution) => { - info!("Signer signed a Cardano stake distribution"; "certificate_hash" => &cardano_stake_distribution.certificate_hash, "aggregator" => &aggregator.name()); - Ok(cardano_stake_distribution.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_signer_is_signing_cardano_stake_distribution, no response from `{url}`" - )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) - } - - pub async fn is_creating_certificate_with_enough_signers( - &self, - aggregator: &Aggregator, - certificate_hash: &str, - total_signers_expected: usize, - ) -> StdResult<()> { - let url = format!("{}/certificate/{certificate_hash}", aggregator.endpoint()); - info!("Waiting for the aggregator to create a certificate with enough signers"; "aggregator" => &aggregator.name()); - - async fn fetch_certificate_message(url: String) -> StdResult> { - match get_json_response::(url).await? { - Ok(certificate) => Ok(Some(certificate)), - Err(err) => Err(anyhow!(err).context("Invalid snapshot body")), - } - } - - match attempt!(10, Duration::from_millis(1000), { - fetch_certificate_message(url.clone()).await - }) { - 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 ; - "aggregator" => &aggregator.name() - ); - Ok(()) - } else { - Err(anyhow!( - "Certificate is not signed by expected number of signers: {} < {} ", - certificate.metadata.signers.len(), - total_signers_expected - )) - } - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_is_creating_certificate, no response from `{url}`" - )), - } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) - } - - pub async fn client_can_verify_cardano_database( - &self, - client: &mut Client, - hash: &str, - ) -> StdResult<()> { - client - .run(ClientCommand::CardanoDbV2(CardanoDbV2Command::List)) - .await?; - - if client.version().is_above_or_equal("0.12.34") { - client - .run(ClientCommand::CardanoDbV2( - CardanoDbV2Command::ListPerEpoch { - epoch_specifier: EpochSpecifier::LatestMinusOffset(5), - }, - )) - .await?; - } else { - warn!( - "Client version is below 0.12.34, skipping `cardano-db snapshot list --epoch latest-5` check" - ); - } - - client - .run(ClientCommand::CardanoDbV2(CardanoDbV2Command::Show { - hash: hash.to_string(), - })) - .await?; - info!("Client list & show the cardano database snapshot"; "hash" => &hash); - - client - .run(ClientCommand::CardanoDbV2(CardanoDbV2Command::Download { - hash: hash.to_string(), - })) - .await?; - info!("Client downloaded & restored the cardano database snapshot"; "hash" => &hash); - - Ok(()) - } - - pub async fn client_can_verify_mithril_stake_distribution( - &self, - client: &mut Client, - hash: &str, - ) -> StdResult<()> { - client - .run(ClientCommand::MithrilStakeDistribution( - MithrilStakeDistributionCommand::Download { - hash: hash.to_owned(), - }, - )) - .await?; - info!("Client downloaded the Mithril stake distribution"; "hash" => &hash); - - Ok(()) - } - - pub async fn client_can_verify_transactions( - &self, - client: &mut Client, - tx_hashes: Vec, - ) -> StdResult<()> { - #[allow(dead_code)] - #[derive(Debug, serde::Deserialize)] - struct ClientCardanoTransactionCertifyResult { - certified_transactions: Vec, - non_certified_transactions: Vec, - } - - let result_file = client - .run(ClientCommand::CardanoTransaction( - CardanoTransactionCommand::Certify { - tx_hashes: tx_hashes.clone(), - }, - )) - .await?; - info!("Client verified the Cardano transactions"; "tx_hashes" => ?tx_hashes); - - let file = std::fs::read_to_string(&result_file).with_context(|| { - format!( - "Failed to read client output from file `{}`", - result_file.display() - ) - })?; - let result: ClientCardanoTransactionCertifyResult = serde_json::from_str(&file) - .with_context(|| { - format!( - "Failed to parse client output as json from file `{}`", - result_file.display() - ) - })?; - - info!("Asserting that all Cardano transactions were verified by the Client..."); - if tx_hashes.iter().all(|tx| result.certified_transactions.contains(tx)) { - Ok(()) - } else { - Err(anyhow!( - "Not all transactions were certified:\n'{:#?}'", - result, - )) - } - } - - pub async fn client_can_verify_transactions_v2( - &self, - client: &mut Client, - tx_hashes: Vec, - ) -> StdResult<()> { - #[allow(dead_code)] - #[derive(Debug, serde::Deserialize)] - struct ClientCardanoTransactionCertifyResult { - certified_transactions: Vec, - non_certified_transactions: Vec, - } - - #[derive(Debug, serde::Deserialize)] - struct CertifiedTransactionV2 { - transaction_hash: String, - } - - if !client.version().is_above_or_equal("0.13.1") { - warn!( - "Client version is below 0.13.1, skipping `cardano-transaction certify --backend v2` check" - ); - return Ok(()); - } - - let result_file = client - .run(ClientCommand::CardanoTransactionV2( - CardanoTransactionV2Command::Certify { - tx_hashes: tx_hashes.clone(), - }, - )) - .await?; - info!("Client verified the Cardano transactions V2"; "tx_hashes" => ?tx_hashes); - - let file = std::fs::read_to_string(&result_file).with_context(|| { - format!( - "Failed to read client output from file `{}`", - result_file.display() - ) - })?; - let result: ClientCardanoTransactionCertifyResult = serde_json::from_str(&file) - .with_context(|| { - format!( - "Failed to parse client output as json from file `{}`", - result_file.display() - ) - })?; - - info!("Asserting that all Cardano transactions V2 were verified by the Client..."); - let certified_tx_hashes_result: Vec = result - .certified_transactions - .iter() - .map(|tx| tx.transaction_hash.clone()) - .collect(); - - if tx_hashes.iter().all(|tx| certified_tx_hashes_result.contains(tx)) { - Ok(()) - } else { - Err(anyhow!( - "Not all transactions V2 were certified:\n'{:#?}'", - result, - )) - } - } - - pub async fn client_can_verify_blocks( - &self, - client: &mut Client, - block_hashes: Vec, - ) -> StdResult<()> { - #[allow(dead_code)] - #[derive(Debug, serde::Deserialize)] - struct ClientCardanoBlockCertifyResult { - certified_blocks: Vec, - non_certified_blocks: Vec, - } - - #[derive(Debug, serde::Deserialize)] - struct CertifiedBlock { - block_hash: String, - } - - if !client.version().is_above_or_equal("0.13.1") { - warn!("Client version is below 0.13.1, skipping `cardano-block certify` check"); - return Ok(()); - } - - let result_file = client - .run(ClientCommand::CardanoBlock(CardanoBlockCommand::Certify { - block_hashes: block_hashes.clone(), - })) - .await?; - info!("Client verified the Cardano blocks"; "block_hashes" => ?block_hashes); - let file = std::fs::read_to_string(&result_file).with_context(|| { - format!( - "Failed to read client output from file `{}`", - result_file.display() - ) - })?; - let result: ClientCardanoBlockCertifyResult = - serde_json::from_str(&file).with_context(|| { - format!( - "Failed to parse client output as json from file `{}`", - result_file.display() - ) - })?; - - info!("Asserting that all Cardano blocks were verified by the Client..."); - let certified_blocks_hashes_result: Vec = result - .certified_blocks - .iter() - .map(|block| block.block_hash.clone()) - .collect(); - - if block_hashes - .iter() - .all(|block| certified_blocks_hashes_result.contains(block)) - { - Ok(()) - } else { - Err(anyhow!("Not all blocks were certified:\n'{:#?}'", result,)) - } - } - - pub async fn client_can_verify_cardano_stake_distribution( - &self, - client: &mut Client, - hash: &str, - epoch: Epoch, - ) -> StdResult<()> { - client - .run(ClientCommand::CardanoStakeDistribution( - CardanoStakeDistributionCommand::Download { - unique_identifier: epoch.to_string(), - }, - )) - .await?; - info!("Client downloaded the Cardano stake distribution by epoch"; "epoch" => epoch.to_string()); - - client - .run(ClientCommand::CardanoStakeDistribution( - CardanoStakeDistributionCommand::Download { - unique_identifier: hash.to_string(), - }, - )) - .await?; - info!("Client downloaded the Cardano stake distribution by hash"; "hash" => hash.to_string()); - - Ok(()) } pub async fn client_can_convert_the_ledger_snapshot( @@ -898,6 +134,7 @@ pub async fn assert_node_producing_mithril_stake_distribution( aggregator: &Aggregator, ) -> StdResult { CheckToolkit::default() + .mithril_stake_distribution .node_producing_mithril_stake_distribution(aggregator) .await } @@ -908,6 +145,7 @@ pub async fn assert_signer_is_signing_mithril_stake_distribution( expected_epoch_min: Epoch, ) -> StdResult { CheckToolkit::default() + .mithril_stake_distribution .signer_is_signing_mithril_stake_distribution(aggregator, hash, expected_epoch_min) .await } @@ -916,6 +154,7 @@ pub async fn assert_node_producing_cardano_database_snapshot( aggregator: &Aggregator, ) -> StdResult { CheckToolkit::default() + .cardano_database .node_producing_cardano_database_snapshot(aggregator) .await } @@ -926,6 +165,7 @@ pub async fn assert_signer_is_signing_cardano_database_snapshot( expected_epoch_min: Epoch, ) -> StdResult { CheckToolkit::default() + .cardano_database .signer_is_signing_cardano_database_snapshot(aggregator, hash, expected_epoch_min) .await } @@ -934,6 +174,7 @@ pub async fn assert_node_producing_cardano_database_digests_map( aggregator: &Aggregator, ) -> StdResult> { CheckToolkit::default() + .cardano_database .node_producing_cardano_database_digests_map(aggregator) .await } @@ -942,6 +183,7 @@ pub async fn assert_node_producing_cardano_transactions( aggregator: &Aggregator, ) -> StdResult { CheckToolkit::default() + .cardano_transactions .node_producing_cardano_transactions(aggregator) .await } @@ -952,6 +194,7 @@ pub async fn assert_signer_is_signing_cardano_transactions( expected_epoch_min: Epoch, ) -> StdResult { CheckToolkit::default() + .cardano_transactions .signer_is_signing_cardano_transactions(aggregator, hash, expected_epoch_min) .await } @@ -960,6 +203,7 @@ pub async fn assert_node_producing_cardano_blocks_transactions( aggregator: &Aggregator, ) -> StdResult { CheckToolkit::default() + .cardano_blocks_transactions .node_producing_cardano_blocks_transactions(aggregator) .await } @@ -970,6 +214,7 @@ pub async fn assert_signer_is_signing_cardano_blocks_transactions( expected_epoch_min: Epoch, ) -> StdResult { CheckToolkit::default() + .cardano_blocks_transactions .signer_is_signing_cardano_blocks_transactions(aggregator, hash, expected_epoch_min) .await } @@ -978,6 +223,7 @@ pub async fn assert_node_producing_cardano_stake_distribution( aggregator: &Aggregator, ) -> StdResult<(String, Epoch)> { CheckToolkit::default() + .cardano_stake_distribution .node_producing_cardano_stake_distribution(aggregator) .await } @@ -988,6 +234,7 @@ pub async fn assert_signer_is_signing_cardano_stake_distribution( expected_epoch_min: Epoch, ) -> StdResult { CheckToolkit::default() + .cardano_stake_distribution .signer_is_signing_cardano_stake_distribution(aggregator, hash, expected_epoch_min) .await } @@ -998,6 +245,7 @@ pub async fn assert_is_creating_certificate_with_enough_signers( total_signers_expected: usize, ) -> StdResult<()> { CheckToolkit::default() + .certificate .is_creating_certificate_with_enough_signers( aggregator, certificate_hash, @@ -1011,6 +259,7 @@ pub async fn assert_client_can_verify_cardano_database( hash: &str, ) -> StdResult<()> { CheckToolkit::default() + .cardano_database .client_can_verify_cardano_database(client, hash) .await } @@ -1020,6 +269,7 @@ pub async fn assert_client_can_verify_mithril_stake_distribution( hash: &str, ) -> StdResult<()> { CheckToolkit::default() + .mithril_stake_distribution .client_can_verify_mithril_stake_distribution(client, hash) .await } @@ -1029,6 +279,7 @@ pub async fn assert_client_can_verify_transactions( tx_hashes: Vec, ) -> StdResult<()> { CheckToolkit::default() + .cardano_transactions .client_can_verify_transactions(client, tx_hashes) .await } @@ -1038,7 +289,8 @@ pub async fn assert_client_can_verify_transactions_v2( tx_hashes: Vec, ) -> StdResult<()> { CheckToolkit::default() - .client_can_verify_transactions_v2(client, tx_hashes) + .cardano_blocks_transactions + .client_verify_transactions(client, tx_hashes) .await } @@ -1047,7 +299,8 @@ pub async fn assert_client_can_verify_blocks( block_hashes: Vec, ) -> StdResult<()> { CheckToolkit::default() - .client_can_verify_blocks(client, block_hashes) + .cardano_blocks_transactions + .client_verify_blocks(client, block_hashes) .await } @@ -1057,6 +310,7 @@ pub async fn assert_client_can_verify_cardano_stake_distribution( epoch: Epoch, ) -> StdResult<()> { CheckToolkit::default() + .cardano_stake_distribution .client_can_verify_cardano_stake_distribution(client, hash, epoch) .await } From 6311d9ff6fd84a6adc9818e4ff5448e6dfac09b2 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Fri, 5 Jun 2026 17:56:22 +0200 Subject: [PATCH 08/19] refactor(e2e): unify certification and verification methods in check toolkits * Add "all in one" `is_certified_and_verified` routines across toolkits that checks artificats productions, certificates signing, and client verification * Streamlined `client_*` verification methods into consistent naming (`verify_with_client`/`verify_transactions_with_client`/`verify_blocks_with_client`). --- .../check/cardano_blocks_transactions.rs | 38 +++++++++++++--- .../src/toolkit/check/cardano_database.rs | 38 ++++++++++++---- .../check/cardano_stake_distribution.rs | 31 +++++++++++-- .../src/toolkit/check/cardano_transactions.rs | 43 +++++++++++++++---- .../check/mithril_stake_distribution.rs | 34 +++++++++++---- .../src/toolkit/check/mod.rs | 22 +++++----- 6 files changed, 161 insertions(+), 45 deletions(-) diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs index bbae02abc2d..7a826a23737 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs @@ -12,7 +12,7 @@ use mithril_common::{ use crate::{ Aggregator, CardanoBlockCommand, CardanoTransactionV2Command, Client, ClientCommand, attempt, - toolkit::{ScenarioToolkitContext, check::get_json_response}, + toolkit::{CheckCertificateToolkit, ScenarioToolkitContext, check::get_json_response}, utils::AttemptResult, }; @@ -26,10 +26,38 @@ impl CheckCardanoBlocksTransactionsToolkit { Self { context } } - pub async fn node_producing_cardano_blocks_transactions( + pub async fn is_certified_and_verified( &self, aggregator: &Aggregator, - ) -> StdResult { + client: &mut Client, + expected_epoch_min: Epoch, + total_signers_expected: usize, + block_hashes: Vec, + tx_hashes: Vec, + ) -> StdResult<()> { + let certificate_toolkit = CheckCertificateToolkit::new(self.context.clone()); + + let hash = self.wait_for_artifact(aggregator).await?; + let certificate_hash = self + .signer_is_signing_cardano_blocks_transactions(aggregator, &hash, expected_epoch_min) + .await?; + + certificate_toolkit + .is_creating_certificate_with_enough_signers( + aggregator, + &certificate_hash, + total_signers_expected, + ) + .await?; + + self.verify_transactions_with_client(client, tx_hashes).await?; + + self.verify_blocks_with_client(client, block_hashes).await?; + + Ok(()) + } + + pub async fn wait_for_artifact(&self, aggregator: &Aggregator) -> StdResult { let url = format!( "{}/artifact/cardano-blocks-transactions", aggregator.endpoint() @@ -119,7 +147,7 @@ impl CheckCardanoBlocksTransactionsToolkit { }) } - pub async fn client_verify_transactions( + pub async fn verify_transactions_with_client( &self, client: &mut Client, tx_hashes: Vec, @@ -183,7 +211,7 @@ impl CheckCardanoBlocksTransactionsToolkit { } } - pub async fn client_verify_blocks( + pub async fn verify_blocks_with_client( &self, client: &mut Client, block_hashes: Vec, diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs index 88ddaa4c29f..872ae3835e9 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs @@ -13,7 +13,7 @@ use mithril_common::{ use crate::{ Aggregator, CardanoDbV2Command, Client, ClientCommand, attempt, - toolkit::{ScenarioToolkitContext, check::get_json_response}, + toolkit::{CheckCertificateToolkit, ScenarioToolkitContext, check::get_json_response}, utils::AttemptResult, }; @@ -27,10 +27,36 @@ impl CheckCardanoDatabaseToolkit { Self { context } } - pub async fn node_producing_cardano_database_snapshot( + pub async fn is_certified_and_verified( &self, aggregator: &Aggregator, - ) -> StdResult { + client: &mut Client, + expected_epoch_min: Epoch, + total_signers_expected: usize, + ) -> StdResult<()> { + let certificate_toolkit = CheckCertificateToolkit::new(self.context.clone()); + + let hash = self.wait_for_artifact(aggregator).await?; + let certificate_hash = self + .signer_is_signing_cardano_database_snapshot(aggregator, &hash, expected_epoch_min) + .await?; + + certificate_toolkit + .is_creating_certificate_with_enough_signers( + aggregator, + &certificate_hash, + total_signers_expected, + ) + .await?; + + self.node_producing_cardano_database_digests_map(aggregator).await?; + + self.verify_with_client(client, &hash).await?; + + Ok(()) + } + + pub async fn wait_for_artifact(&self, aggregator: &Aggregator) -> StdResult { let url = format!("{}/artifact/cardano-database", aggregator.endpoint()); info!("Waiting for the aggregator to produce a Cardano database snapshot"; "aggregator" => &aggregator.name()); @@ -155,11 +181,7 @@ impl CheckCardanoDatabaseToolkit { }) } - pub async fn client_can_verify_cardano_database( - &self, - client: &mut Client, - hash: &str, - ) -> StdResult<()> { + pub async fn verify_with_client(&self, client: &mut Client, hash: &str) -> StdResult<()> { client .run(ClientCommand::CardanoDbV2(CardanoDbV2Command::List)) .await?; diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs index 3272c99e76a..f268fca728c 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs @@ -10,7 +10,7 @@ use mithril_common::{ use crate::{ Aggregator, CardanoStakeDistributionCommand, Client, ClientCommand, attempt, - toolkit::{ScenarioToolkitContext, check::get_json_response}, + toolkit::{CheckCertificateToolkit, ScenarioToolkitContext, check::get_json_response}, utils::AttemptResult, }; @@ -24,10 +24,33 @@ impl CheckCardanoStakeDistributionToolkit { Self { context } } - pub async fn node_producing_cardano_stake_distribution( + pub async fn is_certified_and_verified( &self, aggregator: &Aggregator, - ) -> StdResult<(String, Epoch)> { + client: &mut Client, + expected_epoch_min: Epoch, + total_signers_expected: usize, + ) -> StdResult<()> { + let certificate_toolkit = CheckCertificateToolkit::new(self.context.clone()); + + let (hash, epoch) = self.wait_for_artifact(aggregator).await?; + let certificate_hash = self + .signer_is_signing_cardano_stake_distribution(aggregator, &hash, expected_epoch_min) + .await?; + certificate_toolkit + .is_creating_certificate_with_enough_signers( + aggregator, + &certificate_hash, + total_signers_expected, + ) + .await?; + + self.verify_with_client(client, &hash, epoch).await?; + + Ok(()) + } + + pub async fn wait_for_artifact(&self, aggregator: &Aggregator) -> StdResult<(String, Epoch)> { let url = format!( "{}/artifact/cardano-stake-distributions", aggregator.endpoint() @@ -120,7 +143,7 @@ impl CheckCardanoStakeDistributionToolkit { }) } - pub async fn client_can_verify_cardano_stake_distribution( + pub async fn verify_with_client( &self, client: &mut Client, hash: &str, diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs index 230a8a1a3a3..28b37e69c56 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs @@ -2,18 +2,18 @@ use anyhow::{Context, anyhow}; use slog_scope::info; use std::time::Duration; +use crate::{ + Aggregator, CardanoTransactionCommand, Client, ClientCommand, attempt, + toolkit::{CheckCertificateToolkit, ScenarioToolkitContext, check::get_json_response}, + utils::AttemptResult, +}; +use mithril_common::test::double::fake_data::transaction_hashes; use mithril_common::{ StdResult, entities::{Epoch, TransactionHash}, messages::{CardanoTransactionSnapshotListMessage, CardanoTransactionSnapshotMessage}, }; -use crate::{ - Aggregator, CardanoTransactionCommand, Client, ClientCommand, attempt, - toolkit::{ScenarioToolkitContext, check::get_json_response}, - utils::AttemptResult, -}; - #[derive(Debug, Clone, Default)] pub struct CheckCardanoTransactionsToolkit { context: ScenarioToolkitContext, @@ -24,10 +24,35 @@ impl CheckCardanoTransactionsToolkit { Self { context } } - pub async fn node_producing_cardano_transactions( + pub async fn is_certified_and_verified( &self, aggregator: &Aggregator, - ) -> StdResult { + client: &mut Client, + expected_epoch_min: Epoch, + total_signers_expected: usize, + tx_hashes: Vec, + ) -> StdResult<()> { + let certificate_toolkit = CheckCertificateToolkit::new(self.context.clone()); + + let hash = self.wait_for_artifact(aggregator).await?; + let certificate_hash = self + .signer_is_signing_cardano_transactions(aggregator, &hash, expected_epoch_min) + .await?; + + certificate_toolkit + .is_creating_certificate_with_enough_signers( + aggregator, + &certificate_hash, + total_signers_expected, + ) + .await?; + + self.verify_with_client(client, tx_hashes).await?; + + Ok(()) + } + + pub async fn wait_for_artifact(&self, aggregator: &Aggregator) -> StdResult { let url = format!("{}/artifact/cardano-transactions", aggregator.endpoint()); info!("Waiting for the aggregator to produce a Cardano transactions artifact"; "aggregator" => &aggregator.name(), "aggregator" => &aggregator.name()); @@ -110,7 +135,7 @@ impl CheckCardanoTransactionsToolkit { }) } - pub async fn client_can_verify_transactions( + pub async fn verify_with_client( &self, client: &mut Client, tx_hashes: Vec, diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs index d38a47b9d8f..bcf203d5f18 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs @@ -10,7 +10,7 @@ use mithril_common::{ use crate::{ Aggregator, Client, ClientCommand, MithrilStakeDistributionCommand, attempt, - toolkit::{ScenarioToolkitContext, check::get_json_response}, + toolkit::{CheckCertificateToolkit, ScenarioToolkitContext, check::get_json_response}, utils::AttemptResult, }; @@ -24,10 +24,32 @@ impl CheckMithrilStakeDistributionToolkit { Self { context } } - pub async fn node_producing_mithril_stake_distribution( + pub async fn is_certified_and_verified( &self, aggregator: &Aggregator, - ) -> StdResult { + client: &mut Client, + expected_epoch_min: Epoch, + total_signers_expected: usize, + ) -> StdResult<()> { + let certificate_toolkit = CheckCertificateToolkit::new(self.context.clone()); + + let hash = self.wait_for_artifact(aggregator).await?; + let certificate_hash = self + .signer_is_signing_mithril_stake_distribution(aggregator, &hash, expected_epoch_min) + .await?; + certificate_toolkit + .is_creating_certificate_with_enough_signers( + aggregator, + &certificate_hash, + total_signers_expected, + ) + .await?; + self.verify_with_client(client, &hash).await?; + + Ok(()) + } + + pub async fn wait_for_artifact(&self, aggregator: &Aggregator) -> StdResult { let url = format!( "{}/artifact/mithril-stake-distributions", aggregator.endpoint() @@ -117,11 +139,7 @@ impl CheckMithrilStakeDistributionToolkit { }) } - pub async fn client_can_verify_mithril_stake_distribution( - &self, - client: &mut Client, - hash: &str, - ) -> StdResult<()> { + pub async fn verify_with_client(&self, client: &mut Client, hash: &str) -> StdResult<()> { client .run(ClientCommand::MithrilStakeDistribution( MithrilStakeDistributionCommand::Download { diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs index 356f079eff2..27f342c6621 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs @@ -135,7 +135,7 @@ pub async fn assert_node_producing_mithril_stake_distribution( ) -> StdResult { CheckToolkit::default() .mithril_stake_distribution - .node_producing_mithril_stake_distribution(aggregator) + .wait_for_artifact(aggregator) .await } @@ -155,7 +155,7 @@ pub async fn assert_node_producing_cardano_database_snapshot( ) -> StdResult { CheckToolkit::default() .cardano_database - .node_producing_cardano_database_snapshot(aggregator) + .wait_for_artifact(aggregator) .await } @@ -184,7 +184,7 @@ pub async fn assert_node_producing_cardano_transactions( ) -> StdResult { CheckToolkit::default() .cardano_transactions - .node_producing_cardano_transactions(aggregator) + .wait_for_artifact(aggregator) .await } @@ -204,7 +204,7 @@ pub async fn assert_node_producing_cardano_blocks_transactions( ) -> StdResult { CheckToolkit::default() .cardano_blocks_transactions - .node_producing_cardano_blocks_transactions(aggregator) + .wait_for_artifact(aggregator) .await } @@ -224,7 +224,7 @@ pub async fn assert_node_producing_cardano_stake_distribution( ) -> StdResult<(String, Epoch)> { CheckToolkit::default() .cardano_stake_distribution - .node_producing_cardano_stake_distribution(aggregator) + .wait_for_artifact(aggregator) .await } @@ -260,7 +260,7 @@ pub async fn assert_client_can_verify_cardano_database( ) -> StdResult<()> { CheckToolkit::default() .cardano_database - .client_can_verify_cardano_database(client, hash) + .verify_with_client(client, hash) .await } @@ -270,7 +270,7 @@ pub async fn assert_client_can_verify_mithril_stake_distribution( ) -> StdResult<()> { CheckToolkit::default() .mithril_stake_distribution - .client_can_verify_mithril_stake_distribution(client, hash) + .verify_with_client(client, hash) .await } @@ -280,7 +280,7 @@ pub async fn assert_client_can_verify_transactions( ) -> StdResult<()> { CheckToolkit::default() .cardano_transactions - .client_can_verify_transactions(client, tx_hashes) + .verify_with_client(client, tx_hashes) .await } @@ -290,7 +290,7 @@ pub async fn assert_client_can_verify_transactions_v2( ) -> StdResult<()> { CheckToolkit::default() .cardano_blocks_transactions - .client_verify_transactions(client, tx_hashes) + .verify_transactions_with_client(client, tx_hashes) .await } @@ -300,7 +300,7 @@ pub async fn assert_client_can_verify_blocks( ) -> StdResult<()> { CheckToolkit::default() .cardano_blocks_transactions - .client_verify_blocks(client, block_hashes) + .verify_blocks_with_client(client, block_hashes) .await } @@ -311,7 +311,7 @@ pub async fn assert_client_can_verify_cardano_stake_distribution( ) -> StdResult<()> { CheckToolkit::default() .cardano_stake_distribution - .client_can_verify_cardano_stake_distribution(client, hash, epoch) + .verify_with_client(client, hash, epoch) .await } From 9957878b928829142ac450b36675cfadd23affbd Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Fri, 5 Jun 2026 18:41:08 +0200 Subject: [PATCH 09/19] refactor(e2e): integrate `ScenarioToolkit` into scenarios and infrastructure - Replaced standalone toolkit function calls with corresponding methods in `ScenarioToolkit`. - Updated `FullScenario` and `RunOnlyScenario` to use `ScenarioToolkit` for modular subcomponents. - Removed obsolete standalone toolkit methods. --- .../mithril-end-to-end/src/main.rs | 61 ++-- .../src/mithril/infrastructure.rs | 35 +-- .../mithril-end-to-end/src/scenario/full.rs | 264 ++++++++---------- .../src/scenario/run_only.rs | 39 ++- .../src/toolkit/check/mod.rs | 201 ------------- .../mithril-end-to-end/src/toolkit/exec.rs | 41 +-- .../mithril-end-to-end/src/toolkit/wait.rs | 18 -- 7 files changed, 198 insertions(+), 461 deletions(-) diff --git a/mithril-test-lab/mithril-end-to-end/src/main.rs b/mithril-test-lab/mithril-end-to-end/src/main.rs index 6b68b7d6eb9..306e86375c5 100644 --- a/mithril-test-lab/mithril-end-to-end/src/main.rs +++ b/mithril-test-lab/mithril-end-to-end/src/main.rs @@ -20,6 +20,7 @@ use tokio::{ use mithril_common::StdResult; use mithril_doc::GenerateDocCommands; use mithril_end_to_end::scenario::{FullScenario, RunOnlyScenario}; +use mithril_end_to_end::toolkit::ScenarioToolkit; use mithril_end_to_end::{ AggregateSignatureType, Aggregator, Client, CompatibilityChecker, CompatibilityCheckerError, Devnet, DevnetBootstrapArgs, DmqNodeFlavor, MithrilInfrastructure, MithrilInfrastructureConfig, @@ -425,6 +426,8 @@ impl App { ), ]))?; + let toolkit = ScenarioToolkit::default(); + let devnet = Devnet::bootstrap(&DevnetBootstrapArgs { devnet_scripts_dir: args.cardano_devnet.devnet_scripts_directory, artifacts_target_dir: work_dir.join("devnet"), @@ -444,40 +447,44 @@ impl App { *self.devnet.lock().await = Some(devnet.clone()); let infrastructure = Arc::new( - MithrilInfrastructure::start(&MithrilInfrastructureConfig { - number_of_aggregators: args.network_topology.number_of_aggregators, - number_of_signers: args.network_topology.number_of_signers, - server_port, - devnet: devnet.clone(), - work_dir, - store_dir, - artifacts_dir, - bin_dir: args.bin_directory, - cardano_node_version: args.cardano_devnet.cardano_node_version, - mithril_run_interval: args.mithril.mithril_run_interval, - mithril_era: args.mithril.mithril_era, - mithril_era_reader_adapter: args.mithril.mithril_era_reader_adapter, - signed_entity_types: args.mithril.signed_entity_types.clone(), - aggregate_signature_type: args.mithril.aggregate_signature_type, - run_only_mode, - check_client_cli_snapshot_converter, - use_dmq, - dmq_node_flavor: args.network_topology.dmq_node_flavor, - use_relays, - relay_signer_registration_mode, - relay_signature_registration_mode, - skip_signature_delayer: args.mithril.skip_signature_delayer, - use_p2p_passive_relays, - use_era_specific_work_dir: args.mithril.mithril_next_era.is_some(), - }) + MithrilInfrastructure::start( + toolkit.clone(), + &MithrilInfrastructureConfig { + number_of_aggregators: args.network_topology.number_of_aggregators, + number_of_signers: args.network_topology.number_of_signers, + server_port, + devnet: devnet.clone(), + work_dir, + store_dir, + artifacts_dir, + bin_dir: args.bin_directory, + cardano_node_version: args.cardano_devnet.cardano_node_version, + mithril_run_interval: args.mithril.mithril_run_interval, + mithril_era: args.mithril.mithril_era, + mithril_era_reader_adapter: args.mithril.mithril_era_reader_adapter, + signed_entity_types: args.mithril.signed_entity_types.clone(), + aggregate_signature_type: args.mithril.aggregate_signature_type, + run_only_mode, + check_client_cli_snapshot_converter, + use_dmq, + dmq_node_flavor: args.network_topology.dmq_node_flavor, + use_relays, + relay_signer_registration_mode, + relay_signature_registration_mode, + skip_signature_delayer: args.mithril.skip_signature_delayer, + use_p2p_passive_relays, + use_era_specific_work_dir: args.mithril.mithril_next_era.is_some(), + }, + ) .await?, ); *self.infrastructure.lock().await = Some(infrastructure.clone()); let runner: StdResult<()> = match run_only_mode { - true => RunOnlyScenario::new(infrastructure).run().await, + true => RunOnlyScenario::new(toolkit, infrastructure).run().await, false => { FullScenario::new( + toolkit, infrastructure, args.mithril.signed_entity_types, args.mithril.mithril_next_era, diff --git a/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs b/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs index 28ab2e4c574..3fd30c32a87 100644 --- a/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs +++ b/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs @@ -9,9 +9,10 @@ use mithril_common::entities::{Epoch, PartyId, ProtocolParameters}; use mithril_common::{CardanoNetwork, StdResult}; use crate::mithril::relay_signer::RelaySignerConfiguration; +use crate::toolkit::ScenarioToolkit; use crate::{ AggregateSignatureType, Aggregator, AggregatorConfig, Client, DEVNET_MAGIC_ID, Devnet, - DmqNodeFlavor, FullNode, PoolNode, RelayAggregator, RelayPassive, RelaySigner, Signer, toolkit, + DmqNodeFlavor, FullNode, PoolNode, RelayAggregator, RelayPassive, RelaySigner, Signer, }; use super::signer::SignerConfig; @@ -84,6 +85,7 @@ impl MithrilInfrastructureConfig { } pub struct MithrilInfrastructure { + toolkit: ScenarioToolkit, artifacts_dir: PathBuf, bin_dir: PathBuf, devnet: Devnet, @@ -103,7 +105,10 @@ pub struct MithrilInfrastructure { } impl MithrilInfrastructure { - pub async fn start(config: &MithrilInfrastructureConfig) -> StdResult { + pub async fn start( + toolkit: ScenarioToolkit, + config: &MithrilInfrastructureConfig, + ) -> StdResult { let chain_observer_type = "pallas"; config.devnet.run().await?; if config.use_dmq && config.dmq_node_flavor == Some(DmqNodeFlavor::Haskell) { @@ -123,7 +128,7 @@ impl MithrilInfrastructure { Self::prepare_aggregators(config, aggregator_cardano_nodes, chain_observer_type) .await?; - Self::register_startup_era(&leader_aggregator, config).await?; + Self::register_startup_era(&toolkit, &leader_aggregator, config).await?; leader_aggregator.serve().await?; let follower_aggregator_endpoints = follower_aggregators @@ -157,6 +162,7 @@ impl MithrilInfrastructure { all_aggregators.extend(follower_aggregators); Ok(Self { + toolkit, bin_dir: config.bin_dir.to_path_buf(), artifacts_dir: config.artifacts_dir.to_path_buf(), devnet: config.devnet.clone(), @@ -177,18 +183,16 @@ impl MithrilInfrastructure { } async fn register_startup_era( + toolkit: &ScenarioToolkit, aggregator: &Aggregator, config: &MithrilInfrastructureConfig, ) -> StdResult<()> { let era_epoch = Epoch(0); if config.mithril_era_reader_adapter == "cardano-chain" { - toolkit::register_era_marker( - aggregator, - &config.devnet, - &config.mithril_era, - era_epoch, - ) - .await?; + toolkit + .exec + .register_era_marker(aggregator, &config.devnet, &config.mithril_era, era_epoch) + .await?; } Ok(()) @@ -203,13 +207,10 @@ impl MithrilInfrastructure { + 1; if self.era_reader_adapter == "cardano-chain" { let devnet = self.devnet.clone(); - toolkit::register_era_marker( - self.leader_aggregator(), - &devnet, - next_era, - next_era_epoch, - ) - .await?; + self.toolkit + .exec + .register_era_marker(self.leader_aggregator(), &devnet, next_era, next_era_epoch) + .await?; } let mut current_era = self.current_era.write().await; *current_era = next_era.to_owned(); diff --git a/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs b/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs index 7fe080a2cb0..dc4f4d0e3cb 100644 --- a/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs +++ b/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs @@ -9,7 +9,8 @@ use mithril_common::{ }; use crate::{ - Aggregator, MithrilInfrastructure, NodeVersion, toolkit, + Aggregator, MithrilInfrastructure, NodeVersion, + toolkit::ScenarioToolkit, utils::{ randomly_take_blocks_hashes, randomly_take_transactions_hashes, retrieve_blocks_transactions_from_immutable_files, @@ -17,7 +18,8 @@ use crate::{ }; pub struct FullScenario { - pub infrastructure: Arc, + toolkit: ScenarioToolkit, + infrastructure: Arc, is_signing_cardano_transactions: bool, is_signing_cardano_blocks_transactions: bool, is_signing_cardano_stake_distribution: bool, @@ -28,6 +30,7 @@ pub struct FullScenario { impl FullScenario { pub fn new( + toolkit: ScenarioToolkit, infrastructure: Arc, signed_entity_types: Vec, next_era: Option, @@ -51,6 +54,7 @@ impl FullScenario { Self { infrastructure, + toolkit, is_signing_cardano_transactions: signed_entity_types .contains(&SignedEntityTypeDiscriminants::CardanoTransactions.to_string()), is_signing_cardano_blocks_transactions, @@ -71,7 +75,7 @@ impl FullScenario { // This step needs to be executed early in the process so that the transactions are available // for signing in the penultimate immutable chunk before the end of the test. // As we get closer to the tip of the chain when signing, we'll be able to relax this constraint. - toolkit::transfer_funds(spec.infrastructure.devnet()).await?; + spec.toolkit.exec.transfer_funds(spec.infrastructure.devnet()).await?; info!("Bootstrapping leader aggregator"); spec.bootstrap_leader_aggregator(&spec.infrastructure).await?; @@ -106,34 +110,44 @@ impl FullScenario { ) -> StdResult<()> { let leader_aggregator = infrastructure.leader_aggregator(); - toolkit::wait_for_enough_immutable(leader_aggregator).await?; + self.toolkit.wait.wait_for_enough_immutable(leader_aggregator).await?; let chain_observer = leader_aggregator.chain_observer(); let start_epoch = chain_observer.get_current_epoch().await?.unwrap_or_default(); // Wait 4 epochs after start epoch for the aggregator to be able to bootstrap a genesis certificate let mut target_epoch = start_epoch + 4; - toolkit::wait_for_aggregator_at_target_epoch( - leader_aggregator, - target_epoch, - "minimal epoch for the aggregator to be able to bootstrap genesis certificate" - .to_string(), - ) - .await?; - toolkit::bootstrap_genesis_certificate(leader_aggregator).await?; - toolkit::wait_for_epoch_settings(leader_aggregator).await?; + self.toolkit + .wait + .wait_for_aggregator_at_target_epoch( + leader_aggregator, + target_epoch, + "minimal epoch for the aggregator to be able to bootstrap genesis certificate" + .to_string(), + ) + .await?; + self.toolkit + .exec + .bootstrap_genesis_certificate(leader_aggregator) + .await?; + self.toolkit.wait.wait_for_epoch_settings(leader_aggregator).await?; // Wait 2 epochs before changing stake distribution, so that we use at least one original stake distribution target_epoch += 2; - toolkit::wait_for_aggregator_at_target_epoch( - leader_aggregator, - target_epoch, - "epoch after which the stake distribution will change".to_string(), - ) - .await?; + self.toolkit + .wait + .wait_for_aggregator_at_target_epoch( + leader_aggregator, + target_epoch, + "epoch after which the stake distribution will change".to_string(), + ) + .await?; // Delegate some stakes to pools let delegation_round = 1; - toolkit::delegate_stakes_to_pools(infrastructure.devnet(), delegation_round).await?; + self.toolkit + .exec + .delegate_stakes_to_pools(infrastructure.devnet(), delegation_round) + .await?; Ok(()) } @@ -148,24 +162,25 @@ impl FullScenario { // Wait 2 epochs before changing protocol parameters let mut target_epoch = start_epoch + 2; - toolkit::wait_for_aggregator_at_target_epoch( - aggregator, - target_epoch, - "epoch after which the protocol parameters will change".to_string(), - ) - .await?; - - if aggregator.is_first() || aggregator.version().is_below("0.7.94") { - toolkit::update_protocol_parameters( + self.toolkit + .wait + .wait_for_aggregator_at_target_epoch( aggregator, - infrastructure.aggregate_signature_type(), + target_epoch, + "epoch after which the protocol parameters will change".to_string(), ) .await?; + + if aggregator.is_first() || aggregator.version().is_below("0.7.94") { + self.toolkit + .exec + .update_protocol_parameters(aggregator, infrastructure.aggregate_signature_type()) + .await?; } // Wait 6 epochs after protocol parameters update, so that we make sure that we use new protocol parameters as well as new stake distribution a few times target_epoch += 6; - toolkit::wait_for_aggregator_at_target_epoch( + self.toolkit.wait.wait_for_aggregator_at_target_epoch( aggregator, target_epoch, "epoch after which the certificate chain will be long enough to catch most common troubles with stake distribution and protocol parameters".to_string(), @@ -184,23 +199,28 @@ impl FullScenario { infrastructure.register_switch_to_next_era(next_era).await?; } target_epoch += 5; - toolkit::wait_for_aggregator_at_target_epoch( - aggregator, - target_epoch, - "epoch after which the era switch will have triggered".to_string(), - ) - .await?; - - // Proceed to a re-genesis of the certificate chain - if self.regenesis_on_era_switch { - toolkit::bootstrap_genesis_certificate(aggregator).await?; - target_epoch += 5; - toolkit::wait_for_aggregator_at_target_epoch( + self.toolkit + .wait + .wait_for_aggregator_at_target_epoch( aggregator, target_epoch, - "epoch after which the re-genesis on era switch will be completed".to_string(), + "epoch after which the era switch will have triggered".to_string(), ) .await?; + + // Proceed to a re-genesis of the certificate chain + if self.regenesis_on_era_switch { + self.toolkit.exec.bootstrap_genesis_certificate(aggregator).await?; + target_epoch += 5; + self.toolkit + .wait + .wait_for_aggregator_at_target_epoch( + aggregator, + target_epoch, + "epoch after which the re-genesis on era switch will be completed" + .to_string(), + ) + .await?; } // Verify that artifacts are produced and signed correctly @@ -211,13 +231,15 @@ impl FullScenario { // Check the ledger snapshot conversion step using utxo-hd snapshot-converter if infrastructure.check_client_cli_snapshot_converter() { let mut client = infrastructure.build_client(aggregator).await?; - toolkit::assert_client_can_convert_the_ledger_snapshot( - &mut client, - aggregator.full_node(), - infrastructure.devnet().artifacts_dir(), - NodeVersion::new(infrastructure.cardano_node_version().clone()), - ) - .await?; + self.toolkit + .check + .client_can_convert_the_ledger_snapshot( + &mut client, + aggregator.full_node(), + infrastructure.devnet().artifacts_dir(), + NodeVersion::new(infrastructure.cardano_node_version().clone()), + ) + .await?; } Ok(()) @@ -229,6 +251,8 @@ impl FullScenario { aggregator: &Aggregator, infrastructure: &MithrilInfrastructure, ) -> StdResult { + let mut client = infrastructure.build_client(aggregator).await?; + let expected_epoch_min = target_epoch - 3; let immutable_files_directory = aggregator.db_directory().join("immutable"); let blocks_transactions = @@ -239,122 +263,76 @@ impl FullScenario { // Verify that mithril stake distribution artifacts are produced and signed correctly { - let hash = - toolkit::assert_node_producing_mithril_stake_distribution(aggregator).await?; - let certificate_hash = toolkit::assert_signer_is_signing_mithril_stake_distribution( - aggregator, - &hash, - expected_epoch_min, - ) - .await?; - toolkit::assert_is_creating_certificate_with_enough_signers( - aggregator, - &certificate_hash, - infrastructure.signers().len(), - ) - .await?; - let mut client = infrastructure.build_client(aggregator).await?; - toolkit::assert_client_can_verify_mithril_stake_distribution(&mut client, &hash) + self.toolkit + .check + .mithril_stake_distribution + .is_certified_and_verified( + aggregator, + &mut client, + expected_epoch_min, + infrastructure.signers().len(), + ) .await?; } // Verify that Cardano database snapshot artifacts are produced and signed correctly if self.is_signing_cardano_database { - let hash = toolkit::assert_node_producing_cardano_database_snapshot(aggregator).await?; - let certificate_hash = toolkit::assert_signer_is_signing_cardano_database_snapshot( - aggregator, - &hash, - expected_epoch_min, - ) - .await?; - - toolkit::assert_is_creating_certificate_with_enough_signers( - aggregator, - &certificate_hash, - infrastructure.signers().len(), - ) - .await?; - - toolkit::assert_node_producing_cardano_database_digests_map(aggregator).await?; - - let mut client = infrastructure.build_client(aggregator).await?; - toolkit::assert_client_can_verify_cardano_database(&mut client, &hash).await?; + self.toolkit + .check + .cardano_database + .is_certified_and_verified( + aggregator, + &mut client, + expected_epoch_min, + infrastructure.signers().len(), + ) + .await?; } // Verify that Cardano transactions artifacts are produced and signed correctly if self.is_signing_cardano_transactions { - let hash = toolkit::assert_node_producing_cardano_transactions(aggregator).await?; - let certificate_hash = toolkit::assert_signer_is_signing_cardano_transactions( - aggregator, - &hash, - expected_epoch_min, - ) - .await?; - - toolkit::assert_is_creating_certificate_with_enough_signers( - aggregator, - &certificate_hash, - infrastructure.signers().len(), - ) - .await?; - - let mut client = infrastructure.build_client(aggregator).await?; - toolkit::assert_client_can_verify_transactions(&mut client, transaction_hashes.clone()) + self.toolkit + .check + .cardano_transactions + .is_certified_and_verified( + aggregator, + &mut client, + expected_epoch_min, + infrastructure.signers().len(), + transaction_hashes.clone(), + ) .await?; } // Verify that Cardano blocks transactions artifacts are produced and signed correctly if self.is_signing_cardano_blocks_transactions { - let hash = - toolkit::assert_node_producing_cardano_blocks_transactions(aggregator).await?; - let certificate_hash = toolkit::assert_signer_is_signing_cardano_blocks_transactions( - aggregator, - &hash, - expected_epoch_min, - ) - .await?; - - toolkit::assert_is_creating_certificate_with_enough_signers( - aggregator, - &certificate_hash, - infrastructure.signers().len(), - ) - .await?; - - let mut client = infrastructure.build_client(aggregator).await?; - toolkit::assert_client_can_verify_transactions_v2(&mut client, transaction_hashes) + self.toolkit + .check + .cardano_blocks_transactions + .is_certified_and_verified( + aggregator, + &mut client, + expected_epoch_min, + infrastructure.signers().len(), + block_hashes.clone(), + transaction_hashes.clone(), + ) .await?; - - let mut client = infrastructure.build_client(aggregator).await?; - toolkit::assert_client_can_verify_blocks(&mut client, block_hashes).await?; } // Verify that Cardano stake distribution artifacts are produced and signed correctly if self.is_signing_cardano_stake_distribution { { - let (hash, epoch) = - toolkit::assert_node_producing_cardano_stake_distribution(aggregator).await?; - let certificate_hash = - toolkit::assert_signer_is_signing_cardano_stake_distribution( + self.toolkit + .check + .cardano_stake_distribution + .is_certified_and_verified( aggregator, - &hash, + &mut client, expected_epoch_min, + infrastructure.signers().len(), ) .await?; - toolkit::assert_is_creating_certificate_with_enough_signers( - aggregator, - &certificate_hash, - infrastructure.signers().len(), - ) - .await?; - - let mut client = infrastructure.build_client(aggregator).await?; - toolkit::assert_client_can_verify_cardano_stake_distribution( - &mut client, - &hash, - epoch, - ) - .await?; } } diff --git a/mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs b/mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs index e8d36a8cffe..aa89eca3cf4 100644 --- a/mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs +++ b/mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs @@ -4,15 +4,19 @@ use slog_scope::info; use mithril_common::StdResult; -use crate::{MithrilInfrastructure, toolkit}; +use crate::{MithrilInfrastructure, toolkit::ScenarioToolkit}; pub struct RunOnlyScenario { - pub infrastructure: Arc, + toolkit: ScenarioToolkit, + infrastructure: Arc, } impl RunOnlyScenario { - pub fn new(infrastructure: Arc) -> Self { - Self { infrastructure } + pub fn new(toolkit: ScenarioToolkit, infrastructure: Arc) -> Self { + Self { + toolkit, + infrastructure, + } } pub async fn run(self) -> StdResult<()> { @@ -34,24 +38,29 @@ impl RunOnlyScenario { ) -> StdResult<()> { let leader_aggregator = infrastructure.leader_aggregator(); - toolkit::wait_for_enough_immutable(leader_aggregator).await?; + self.toolkit.wait.wait_for_enough_immutable(leader_aggregator).await?; let chain_observer = leader_aggregator.chain_observer(); let start_epoch = chain_observer.get_current_epoch().await?.unwrap_or_default(); // Wait 3 epochs after start epoch for the aggregator to be able to bootstrap a genesis certificate let target_epoch = start_epoch + 3; - toolkit::wait_for_aggregator_at_target_epoch( - leader_aggregator, - target_epoch, - "minimal epoch for the aggregator to be able to bootstrap genesis certificate" - .to_string(), - ) - .await?; - toolkit::bootstrap_genesis_certificate(leader_aggregator).await?; - toolkit::wait_for_epoch_settings(leader_aggregator).await?; + self.toolkit + .wait + .wait_for_aggregator_at_target_epoch( + leader_aggregator, + target_epoch, + "minimal epoch for the aggregator to be able to bootstrap genesis certificate" + .to_string(), + ) + .await?; + self.toolkit + .exec + .bootstrap_genesis_certificate(leader_aggregator) + .await?; + self.toolkit.wait.wait_for_epoch_settings(leader_aggregator).await?; // Transfer some funds on the devnet to have some Cardano transactions to sign - toolkit::transfer_funds(infrastructure.devnet()).await?; + self.toolkit.exec.transfer_funds(infrastructure.devnet()).await?; Ok(()) } diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs index 27f342c6621..16dff141761 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs @@ -129,204 +129,3 @@ impl CheckToolkit { Ok(()) } } - -pub async fn assert_node_producing_mithril_stake_distribution( - aggregator: &Aggregator, -) -> StdResult { - CheckToolkit::default() - .mithril_stake_distribution - .wait_for_artifact(aggregator) - .await -} - -pub async fn assert_signer_is_signing_mithril_stake_distribution( - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, -) -> StdResult { - CheckToolkit::default() - .mithril_stake_distribution - .signer_is_signing_mithril_stake_distribution(aggregator, hash, expected_epoch_min) - .await -} - -pub async fn assert_node_producing_cardano_database_snapshot( - aggregator: &Aggregator, -) -> StdResult { - CheckToolkit::default() - .cardano_database - .wait_for_artifact(aggregator) - .await -} - -pub async fn assert_signer_is_signing_cardano_database_snapshot( - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, -) -> StdResult { - CheckToolkit::default() - .cardano_database - .signer_is_signing_cardano_database_snapshot(aggregator, hash, expected_epoch_min) - .await -} - -pub async fn assert_node_producing_cardano_database_digests_map( - aggregator: &Aggregator, -) -> StdResult> { - CheckToolkit::default() - .cardano_database - .node_producing_cardano_database_digests_map(aggregator) - .await -} - -pub async fn assert_node_producing_cardano_transactions( - aggregator: &Aggregator, -) -> StdResult { - CheckToolkit::default() - .cardano_transactions - .wait_for_artifact(aggregator) - .await -} - -pub async fn assert_signer_is_signing_cardano_transactions( - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, -) -> StdResult { - CheckToolkit::default() - .cardano_transactions - .signer_is_signing_cardano_transactions(aggregator, hash, expected_epoch_min) - .await -} - -pub async fn assert_node_producing_cardano_blocks_transactions( - aggregator: &Aggregator, -) -> StdResult { - CheckToolkit::default() - .cardano_blocks_transactions - .wait_for_artifact(aggregator) - .await -} - -pub async fn assert_signer_is_signing_cardano_blocks_transactions( - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, -) -> StdResult { - CheckToolkit::default() - .cardano_blocks_transactions - .signer_is_signing_cardano_blocks_transactions(aggregator, hash, expected_epoch_min) - .await -} - -pub async fn assert_node_producing_cardano_stake_distribution( - aggregator: &Aggregator, -) -> StdResult<(String, Epoch)> { - CheckToolkit::default() - .cardano_stake_distribution - .wait_for_artifact(aggregator) - .await -} - -pub async fn assert_signer_is_signing_cardano_stake_distribution( - aggregator: &Aggregator, - hash: &str, - expected_epoch_min: Epoch, -) -> StdResult { - CheckToolkit::default() - .cardano_stake_distribution - .signer_is_signing_cardano_stake_distribution(aggregator, hash, expected_epoch_min) - .await -} - -pub async fn assert_is_creating_certificate_with_enough_signers( - aggregator: &Aggregator, - certificate_hash: &str, - total_signers_expected: usize, -) -> StdResult<()> { - CheckToolkit::default() - .certificate - .is_creating_certificate_with_enough_signers( - aggregator, - certificate_hash, - total_signers_expected, - ) - .await -} - -pub async fn assert_client_can_verify_cardano_database( - client: &mut Client, - hash: &str, -) -> StdResult<()> { - CheckToolkit::default() - .cardano_database - .verify_with_client(client, hash) - .await -} - -pub async fn assert_client_can_verify_mithril_stake_distribution( - client: &mut Client, - hash: &str, -) -> StdResult<()> { - CheckToolkit::default() - .mithril_stake_distribution - .verify_with_client(client, hash) - .await -} - -pub async fn assert_client_can_verify_transactions( - client: &mut Client, - tx_hashes: Vec, -) -> StdResult<()> { - CheckToolkit::default() - .cardano_transactions - .verify_with_client(client, tx_hashes) - .await -} - -pub async fn assert_client_can_verify_transactions_v2( - client: &mut Client, - tx_hashes: Vec, -) -> StdResult<()> { - CheckToolkit::default() - .cardano_blocks_transactions - .verify_transactions_with_client(client, tx_hashes) - .await -} - -pub async fn assert_client_can_verify_blocks( - client: &mut Client, - block_hashes: Vec, -) -> StdResult<()> { - CheckToolkit::default() - .cardano_blocks_transactions - .verify_blocks_with_client(client, block_hashes) - .await -} - -pub async fn assert_client_can_verify_cardano_stake_distribution( - client: &mut Client, - hash: &str, - epoch: Epoch, -) -> StdResult<()> { - CheckToolkit::default() - .cardano_stake_distribution - .verify_with_client(client, hash, epoch) - .await -} - -pub async fn assert_client_can_convert_the_ledger_snapshot( - client: &mut Client, - full_node: &FullNode, - artifacts_dir: PathBuf, - cardano_node_version: NodeVersion, -) -> StdResult<()> { - CheckToolkit::default() - .client_can_convert_the_ledger_snapshot( - client, - full_node, - artifacts_dir, - cardano_node_version, - ) - .await -} diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs index a9b9298cc74..ff53bb5d3fd 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs @@ -37,7 +37,7 @@ impl ExecToolkit { pub async fn bootstrap_genesis_certificate(&self, aggregator: &Aggregator) -> StdResult<()> { info!("Bootstrap genesis certificate"; "aggregator" => &aggregator.name()); info!("> retrieving current era from aggregator"; "aggregator" => &aggregator.name()); - let mithril_era = retrieve_current_era(aggregator).await?; + let mithril_era = self.retrieve_current_era(aggregator).await?; info!("> stopping aggregator"; "aggregator" => &aggregator.name()); aggregator.stop().await?; info!("> bootstrapping genesis using signers registered two epochs ago..."; "aggregator" => &aggregator.name()); @@ -123,42 +123,3 @@ impl ExecToolkit { Ok(()) } } - -/// Retrieve the current Mithril era from a running aggregator by querying its `/status` route. -pub async fn retrieve_current_era(aggregator: &Aggregator) -> StdResult { - ExecToolkit::default().retrieve_current_era(aggregator).await -} - -pub async fn bootstrap_genesis_certificate(aggregator: &Aggregator) -> StdResult<()> { - ExecToolkit::default().bootstrap_genesis_certificate(aggregator).await -} - -pub async fn register_era_marker( - aggregator: &Aggregator, - devnet: &Devnet, - mithril_era: &str, - era_epoch: Epoch, -) -> StdResult<()> { - ExecToolkit::default() - .register_era_marker(aggregator, devnet, mithril_era, era_epoch) - .await -} - -pub async fn delegate_stakes_to_pools(devnet: &Devnet, delegation_round: u16) -> StdResult<()> { - ExecToolkit::default() - .delegate_stakes_to_pools(devnet, delegation_round) - .await -} - -pub async fn transfer_funds(devnet: &Devnet) -> StdResult<()> { - ExecToolkit::default().transfer_funds(devnet).await -} - -pub async fn update_protocol_parameters( - aggregator: &Aggregator, - aggregate_signature_type: AggregateSignatureType, -) -> StdResult<()> { - ExecToolkit::default() - .update_protocol_parameters(aggregator, aggregate_signature_type) - .await -} diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs index a1495c07d3c..d7da8e3bb51 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs @@ -123,21 +123,3 @@ impl WaitToolkit { Ok(()) } } - -pub async fn wait_for_enough_immutable(aggregator: &Aggregator) -> StdResult<()> { - WaitToolkit::default().wait_for_enough_immutable(aggregator).await -} - -pub async fn wait_for_epoch_settings(aggregator: &Aggregator) -> StdResult { - WaitToolkit::default().wait_for_epoch_settings(aggregator).await -} - -pub async fn wait_for_aggregator_at_target_epoch( - aggregator: &Aggregator, - target_epoch: Epoch, - wait_reason: String, -) -> StdResult<()> { - WaitToolkit::default() - .wait_for_aggregator_at_target_epoch(aggregator, target_epoch, wait_reason) - .await -} From 9bce508cd027d539bae74e435b750836e9a6440e Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 8 Jun 2026 12:38:48 +0200 Subject: [PATCH 10/19] refactor(e2e): simplify artifact handling in check toolkits Unified artifact-related methods across check toolkits by replacing repetitive methods with reusable `wait_for_artifact` and `check_artifact` utilities. --- .../check/cardano_blocks_transactions.rs | 124 ++++------------- .../src/toolkit/check/cardano_database.rs | 111 ++++----------- .../check/cardano_stake_distribution.rs | 129 ++++-------------- .../src/toolkit/check/cardano_transactions.rs | 120 ++++------------ .../check/mithril_stake_distribution.rs | 124 ++++------------- .../src/toolkit/check/mod.rs | 71 +++++++--- 6 files changed, 185 insertions(+), 494 deletions(-) diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs index 7a826a23737..eef0ced4af8 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs @@ -1,19 +1,18 @@ use anyhow::{Context, anyhow}; use slog_scope::{info, warn}; -use std::time::Duration; use mithril_common::{ StdResult, entities::{BlockHash, Epoch, TransactionHash}, - messages::{ - CardanoBlocksTransactionsSnapshotListMessage, CardanoBlocksTransactionsSnapshotMessage, - }, + messages::CardanoBlocksTransactionsSnapshotListItemMessage, }; use crate::{ - Aggregator, CardanoBlockCommand, CardanoTransactionV2Command, Client, ClientCommand, attempt, - toolkit::{CheckCertificateToolkit, ScenarioToolkitContext, check::get_json_response}, - utils::AttemptResult, + Aggregator, CardanoBlockCommand, CardanoTransactionV2Command, Client, ClientCommand, + toolkit::{ + CheckCertificateToolkit, ScenarioToolkitContext, + check::{assert_minimal_epoch, wait_for_artifact}, + }, }; #[derive(Debug, Clone, Default)] @@ -37,114 +36,41 @@ impl CheckCardanoBlocksTransactionsToolkit { ) -> StdResult<()> { let certificate_toolkit = CheckCertificateToolkit::new(self.context.clone()); - let hash = self.wait_for_artifact(aggregator).await?; - let certificate_hash = self - .signer_is_signing_cardano_blocks_transactions(aggregator, &hash, expected_epoch_min) - .await?; - + let artifact = self.wait_for_artifact(aggregator).await?; + self.check_artifact(&artifact, expected_epoch_min)?; certificate_toolkit .is_creating_certificate_with_enough_signers( aggregator, - &certificate_hash, + &artifact.certificate_hash, total_signers_expected, ) .await?; - self.verify_transactions_with_client(client, tx_hashes).await?; - self.verify_blocks_with_client(client, block_hashes).await?; Ok(()) } - pub async fn wait_for_artifact(&self, aggregator: &Aggregator) -> StdResult { - let url = format!( - "{}/artifact/cardano-blocks-transactions", - aggregator.endpoint() - ); - info!("Waiting for the aggregator to produce a Cardano blocks transactions artifact"; "aggregator" => &aggregator.name()); - - async fn fetch_last_cardano_blocks_transactions_snapshot_hash( - url: String, - ) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([artifact, ..]) => Ok(Some(artifact.hash.clone())), - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!( - "Invalid Cardano blocks transactions artifact body: {err}", - )), - } - } - - match attempt!(30, Duration::from_millis(2000), { - fetch_last_cardano_blocks_transactions_snapshot_hash(url.clone()).await - }) { - AttemptResult::Ok(hash) => { - info!("Aggregator produced a Cardano blocks transactions artifact"; "hash" => &hash, "aggregator" => &aggregator.name()); - Ok(hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_node_producing_cardano_blocks_transactions, no response from `{url}`" - )), - } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) + pub async fn wait_for_artifact( + &self, + aggregator: &Aggregator, + ) -> StdResult { + wait_for_artifact::( + "Cardano blocks transactions", + "/artifact/cardano-blocks-transactions", + |a| a.hash.clone(), + &self.context, + aggregator, + ) + .await } - pub async fn signer_is_signing_cardano_blocks_transactions( + pub fn check_artifact( &self, - aggregator: &Aggregator, - hash: &str, + artifact: &CardanoBlocksTransactionsSnapshotListItemMessage, expected_epoch_min: Epoch, - ) -> StdResult { - let url = format!( - "{}/artifact/cardano-blocks-transactions/{hash}", - aggregator.endpoint() - ); - info!( - "Asserting the aggregator is signing the Cardano blocks transactions artifact `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_cardano_blocks_transactions_snapshot_message( - url: String, - expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url).await? { - Ok(artifact) => match artifact.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(artifact)), - epoch => Err(anyhow!( - "Minimum expected artifact epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => { - Err(anyhow!(err).context("Invalid Cardano blocks transactions artifact body")) - } - } - } - - match attempt!(10, Duration::from_millis(1000), { - fetch_cardano_blocks_transactions_snapshot_message(url.clone(), expected_epoch_min).await - }) { - AttemptResult::Ok(artifact) => { - info!("Signer signed a Cardano blocks transactions artifact"; "certificate_hash" => &artifact.certificate_hash, "aggregator" => &aggregator.name()); - Ok(artifact.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_signer_is_signing_cardano_blocks_transactions, no response from `{url}`" - )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) + ) -> StdResult<()> { + assert_minimal_epoch(artifact, |a| a.epoch, expected_epoch_min) } pub async fn verify_transactions_with_client( diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs index 872ae3835e9..889f7fb6cdb 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs @@ -5,15 +5,15 @@ use std::time::Duration; use mithril_common::{ StdResult, entities::{Epoch, EpochSpecifier}, - messages::{ - CardanoDatabaseDigestListMessage, CardanoDatabaseSnapshotListMessage, - CardanoDatabaseSnapshotMessage, - }, + messages::{CardanoDatabaseDigestListMessage, CardanoDatabaseSnapshotListItemMessage}, }; use crate::{ Aggregator, CardanoDbV2Command, Client, ClientCommand, attempt, - toolkit::{CheckCertificateToolkit, ScenarioToolkitContext, check::get_json_response}, + toolkit::{ + CheckCertificateToolkit, ScenarioToolkitContext, + check::{assert_minimal_epoch, get_json_response, wait_for_artifact}, + }, utils::AttemptResult, }; @@ -36,102 +36,41 @@ impl CheckCardanoDatabaseToolkit { ) -> StdResult<()> { let certificate_toolkit = CheckCertificateToolkit::new(self.context.clone()); - let hash = self.wait_for_artifact(aggregator).await?; - let certificate_hash = self - .signer_is_signing_cardano_database_snapshot(aggregator, &hash, expected_epoch_min) - .await?; - + let artifact = self.wait_for_artifact(aggregator).await?; + self.check_artifact(&artifact, expected_epoch_min)?; certificate_toolkit .is_creating_certificate_with_enough_signers( aggregator, - &certificate_hash, + &artifact.certificate_hash, total_signers_expected, ) .await?; - self.node_producing_cardano_database_digests_map(aggregator).await?; - - self.verify_with_client(client, &hash).await?; + self.verify_with_client(client, &artifact.hash).await?; Ok(()) } - pub async fn wait_for_artifact(&self, aggregator: &Aggregator) -> StdResult { - let url = format!("{}/artifact/cardano-database", aggregator.endpoint()); - info!("Waiting for the aggregator to produce a Cardano database snapshot"; "aggregator" => &aggregator.name()); - - async fn fetch_last_cardano_database_snapshot_hash( - url: String, - ) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([cardano_database_snapshot, ..]) => { - Ok(Some(cardano_database_snapshot.hash.clone())) - } - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!("Invalid Cardano database snapshot body: {err}",)), - } - } - - match attempt!(30, Duration::from_millis(2000), { - fetch_last_cardano_database_snapshot_hash(url.clone()).await - }) { - AttemptResult::Ok(hash) => { - info!("Aggregator produced a Cardano database snapshot"; "hash" => &hash, "aggregator" => &aggregator.name()); - Ok(hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_node_producing_snapshot, no response from `{url}`" - )), - } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) + pub async fn wait_for_artifact( + &self, + aggregator: &Aggregator, + ) -> StdResult { + wait_for_artifact::( + "Cardano database snapshot", + "/artifact/cardano-database", + |a| a.hash.clone(), + &self.context, + aggregator, + ) + .await } - pub async fn signer_is_signing_cardano_database_snapshot( + pub fn check_artifact( &self, - aggregator: &Aggregator, - hash: &str, + artifact: &CardanoDatabaseSnapshotListItemMessage, expected_epoch_min: Epoch, - ) -> StdResult { - let url = format!("{}/artifact/cardano-database/{hash}", aggregator.endpoint()); - info!( - "Asserting the aggregator is signing the Cardano database snapshot message `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_cardano_database_snapshot_message( - url: String, - expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url).await? { - Ok(cardano_database_snapshot) => match cardano_database_snapshot.beacon.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(cardano_database_snapshot)), - epoch => Err(anyhow!( - "Minimum expected Cardano database snapshot epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => Err(anyhow!(err).context("Invalid Cardano database snapshot body")), - } - } - - match attempt!(10, Duration::from_millis(1000), { - fetch_cardano_database_snapshot_message(url.clone(), expected_epoch_min).await - }) { - AttemptResult::Ok(snapshot) => { - info!("Signer signed a snapshot"; "certificate_hash" => &snapshot.certificate_hash, "aggregator" => &aggregator.name()); - Ok(snapshot.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_signer_is_signing_snapshot, no response from `{url}`" - )), - } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) + ) -> StdResult<()> { + assert_minimal_epoch(artifact, |a| a.beacon.epoch, expected_epoch_min) } pub async fn node_producing_cardano_database_digests_map( diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs index f268fca728c..96040ff6317 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs @@ -1,17 +1,15 @@ -use anyhow::{Context, anyhow}; use slog_scope::info; -use std::time::Duration; use mithril_common::{ - StdResult, - entities::Epoch, - messages::{CardanoStakeDistributionListMessage, CardanoStakeDistributionMessage}, + StdResult, entities::Epoch, messages::CardanoStakeDistributionListItemMessage, }; use crate::{ - Aggregator, CardanoStakeDistributionCommand, Client, ClientCommand, attempt, - toolkit::{CheckCertificateToolkit, ScenarioToolkitContext, check::get_json_response}, - utils::AttemptResult, + Aggregator, CardanoStakeDistributionCommand, Client, ClientCommand, + toolkit::{ + CheckCertificateToolkit, ScenarioToolkitContext, + check::{assert_minimal_epoch, wait_for_artifact}, + }, }; #[derive(Debug, Clone, Default)] @@ -33,114 +31,41 @@ impl CheckCardanoStakeDistributionToolkit { ) -> StdResult<()> { let certificate_toolkit = CheckCertificateToolkit::new(self.context.clone()); - let (hash, epoch) = self.wait_for_artifact(aggregator).await?; - let certificate_hash = self - .signer_is_signing_cardano_stake_distribution(aggregator, &hash, expected_epoch_min) - .await?; + let artifact = self.wait_for_artifact(aggregator).await?; + self.check_artifact(&artifact, expected_epoch_min)?; certificate_toolkit .is_creating_certificate_with_enough_signers( aggregator, - &certificate_hash, + &artifact.certificate_hash, total_signers_expected, ) .await?; - - self.verify_with_client(client, &hash, epoch).await?; + self.verify_with_client(client, &artifact.hash, artifact.epoch) + .await?; Ok(()) } - pub async fn wait_for_artifact(&self, aggregator: &Aggregator) -> StdResult<(String, Epoch)> { - let url = format!( - "{}/artifact/cardano-stake-distributions", - aggregator.endpoint() - ); - info!("Waiting for the aggregator to produce a Cardano stake distribution"; "aggregator" => &aggregator.name()); - - async fn fetch_last_cardano_stake_distribution_message( - url: String, - ) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([stake_distribution, ..]) => Ok(Some(( - stake_distribution.hash.clone(), - stake_distribution.epoch, - ))), - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!("Invalid Cardano stake distribution body: {err}",)), - } - } - - match attempt!(30, Duration::from_millis(2000), { - fetch_last_cardano_stake_distribution_message(url.clone()).await - }) { - AttemptResult::Ok((hash, epoch)) => { - info!("Aggregator produced a Cardano stake distribution"; "hash" => &hash, "epoch" => #?epoch, "aggregator" => &aggregator.name()); - Ok((hash, epoch)) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_node_producing_cardano_stake_distribution, no response from `{url}`" - )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) + pub async fn wait_for_artifact( + &self, + aggregator: &Aggregator, + ) -> StdResult { + wait_for_artifact::( + "Cardano stake distribution", + "/artifact/cardano-stake-distributions", + |a| a.hash.clone(), + &self.context, + aggregator, + ) + .await } - pub async fn signer_is_signing_cardano_stake_distribution( + pub fn check_artifact( &self, - aggregator: &Aggregator, - hash: &str, + artifact: &CardanoStakeDistributionListItemMessage, expected_epoch_min: Epoch, - ) -> StdResult { - let url = format!( - "{}/artifact/cardano-stake-distribution/{hash}", - aggregator.endpoint() - ); - info!( - "Asserting the aggregator is signing the Cardano stake distribution message `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_cardano_stake_distribution_message( - url: String, - expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url).await? { - Ok(stake_distribution) => match stake_distribution.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(stake_distribution)), - epoch => Err(anyhow!( - "Minimum expected Cardano stake distribution epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => Err(anyhow!(err).context("Invalid Cardano stake distribution body")), - } - } - - match attempt!(10, Duration::from_millis(1000), { - fetch_cardano_stake_distribution_message(url.clone(), expected_epoch_min).await - }) { - AttemptResult::Ok(cardano_stake_distribution) => { - info!("Signer signed a Cardano stake distribution"; "certificate_hash" => &cardano_stake_distribution.certificate_hash, "aggregator" => &aggregator.name()); - Ok(cardano_stake_distribution.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_signer_is_signing_cardano_stake_distribution, no response from `{url}`" - )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) + ) -> StdResult<()> { + assert_minimal_epoch(artifact, |a| a.epoch, expected_epoch_min) } pub async fn verify_with_client( diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs index 28b37e69c56..85b8e69dd74 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs @@ -1,17 +1,18 @@ use anyhow::{Context, anyhow}; use slog_scope::info; -use std::time::Duration; -use crate::{ - Aggregator, CardanoTransactionCommand, Client, ClientCommand, attempt, - toolkit::{CheckCertificateToolkit, ScenarioToolkitContext, check::get_json_response}, - utils::AttemptResult, -}; -use mithril_common::test::double::fake_data::transaction_hashes; use mithril_common::{ StdResult, entities::{Epoch, TransactionHash}, - messages::{CardanoTransactionSnapshotListMessage, CardanoTransactionSnapshotMessage}, + messages::CardanoTransactionSnapshotListItemMessage, +}; + +use crate::{ + Aggregator, CardanoTransactionCommand, Client, ClientCommand, + toolkit::{ + CheckCertificateToolkit, ScenarioToolkitContext, + check::{assert_minimal_epoch, wait_for_artifact}, + }, }; #[derive(Debug, Clone, Default)] @@ -34,105 +35,40 @@ impl CheckCardanoTransactionsToolkit { ) -> StdResult<()> { let certificate_toolkit = CheckCertificateToolkit::new(self.context.clone()); - let hash = self.wait_for_artifact(aggregator).await?; - let certificate_hash = self - .signer_is_signing_cardano_transactions(aggregator, &hash, expected_epoch_min) - .await?; - + let artifact = self.wait_for_artifact(aggregator).await?; + self.check_artifact(&artifact, expected_epoch_min)?; certificate_toolkit .is_creating_certificate_with_enough_signers( aggregator, - &certificate_hash, + &artifact.certificate_hash, total_signers_expected, ) .await?; - self.verify_with_client(client, tx_hashes).await?; Ok(()) } - pub async fn wait_for_artifact(&self, aggregator: &Aggregator) -> StdResult { - let url = format!("{}/artifact/cardano-transactions", aggregator.endpoint()); - info!("Waiting for the aggregator to produce a Cardano transactions artifact"; "aggregator" => &aggregator.name(), "aggregator" => &aggregator.name()); - - async fn fetch_last_cardano_transaction_snapshot_hash( - url: String, - ) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([artifact, ..]) => Ok(Some(artifact.hash.clone())), - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!("Invalid Cardano transactions artifact body: {err}",)), - } - } - - match attempt!(30, Duration::from_millis(2000), { - fetch_last_cardano_transaction_snapshot_hash(url.clone()).await - }) { - AttemptResult::Ok(hash) => { - info!("Aggregator produced a Cardano transactions artifact"; "hash" => &hash, "aggregator" => &aggregator.name()); - Ok(hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_node_producing_cardano_transactions, no response from `{url}`" - )), - } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) + pub async fn wait_for_artifact( + &self, + aggregator: &Aggregator, + ) -> StdResult { + wait_for_artifact::( + "Cardano transactions", + "/artifact/cardano-transactions", + |a| a.hash.clone(), + &self.context, + aggregator, + ) + .await } - pub async fn signer_is_signing_cardano_transactions( + pub fn check_artifact( &self, - aggregator: &Aggregator, - hash: &str, + artifact: &CardanoTransactionSnapshotListItemMessage, expected_epoch_min: Epoch, - ) -> StdResult { - let url = format!( - "{}/artifact/cardano-transaction/{hash}", - aggregator.endpoint() - ); - info!( - "Asserting the aggregator is signing the Cardano transactions artifact `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_cardano_transaction_snapshot_message( - url: String, - expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url).await? { - Ok(artifact) => match artifact.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(artifact)), - epoch => Err(anyhow!( - "Minimum expected artifact epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => Err(anyhow!(err).context("Invalid Cardano transactions artifact body")), - } - } - - match attempt!(10, Duration::from_millis(1000), { - fetch_cardano_transaction_snapshot_message(url.clone(), expected_epoch_min).await - }) { - AttemptResult::Ok(artifact) => { - info!("Signer signed a Cardano transactions artifact"; "certificate_hash" => &artifact.certificate_hash, "aggregator" => &aggregator.name()); - Ok(artifact.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_signer_is_signing_cardano_transactions, no response from `{url}`" - )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) + ) -> StdResult<()> { + assert_minimal_epoch(artifact, |a| a.epoch, expected_epoch_min) } pub async fn verify_with_client( diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs index bcf203d5f18..bc1b9c980f1 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs @@ -1,17 +1,15 @@ -use anyhow::{Context, anyhow}; use slog_scope::info; -use std::time::Duration; use mithril_common::{ - StdResult, - entities::Epoch, - messages::{MithrilStakeDistributionListMessage, MithrilStakeDistributionMessage}, + StdResult, entities::Epoch, messages::MithrilStakeDistributionListItemMessage, }; use crate::{ - Aggregator, Client, ClientCommand, MithrilStakeDistributionCommand, attempt, - toolkit::{CheckCertificateToolkit, ScenarioToolkitContext, check::get_json_response}, - utils::AttemptResult, + Aggregator, Client, ClientCommand, MithrilStakeDistributionCommand, + toolkit::{ + CheckCertificateToolkit, ScenarioToolkitContext, + check::{assert_minimal_epoch, wait_for_artifact}, + }, }; #[derive(Debug, Clone, Default)] @@ -33,110 +31,40 @@ impl CheckMithrilStakeDistributionToolkit { ) -> StdResult<()> { let certificate_toolkit = CheckCertificateToolkit::new(self.context.clone()); - let hash = self.wait_for_artifact(aggregator).await?; - let certificate_hash = self - .signer_is_signing_mithril_stake_distribution(aggregator, &hash, expected_epoch_min) - .await?; + let artifact = self.wait_for_artifact(aggregator).await?; + self.check_artifact(&artifact, expected_epoch_min)?; certificate_toolkit .is_creating_certificate_with_enough_signers( aggregator, - &certificate_hash, + &artifact.certificate_hash, total_signers_expected, ) .await?; - self.verify_with_client(client, &hash).await?; + self.verify_with_client(client, &artifact.hash).await?; Ok(()) } - pub async fn wait_for_artifact(&self, aggregator: &Aggregator) -> StdResult { - let url = format!( - "{}/artifact/mithril-stake-distributions", - aggregator.endpoint() - ); - info!("Waiting for the aggregator to produce a mithril stake distribution"; "aggregator" => &aggregator.name()); - - async fn fetch_last_mithril_stake_distribution_hash( - url: String, - ) -> StdResult> { - match get_json_response::(url) - .await? - .as_deref() - { - Ok([stake_distribution, ..]) => Ok(Some(stake_distribution.hash.clone())), - Ok(&[]) => Ok(None), - Err(err) => Err(anyhow!("Invalid mithril stake distribution body: {err}",)), - } - } - - match attempt!(30, Duration::from_secs(3), { - fetch_last_mithril_stake_distribution_hash(url.clone()).await - }) { - AttemptResult::Ok(hash) => { - info!("Aggregator produced a mithril stake distribution"; "hash" => &hash, "aggregator" => &aggregator.name()); - Ok(hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_node_producing_mithril_stake_distribution, no response from `{url}`" - )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) + pub async fn wait_for_artifact( + &self, + aggregator: &Aggregator, + ) -> StdResult { + wait_for_artifact::( + "Mithril stake distribution", + "/artifact/mithril-stake-distributions", + |a| a.hash.clone(), + &self.context, + aggregator, + ) + .await } - pub async fn signer_is_signing_mithril_stake_distribution( + pub fn check_artifact( &self, - aggregator: &Aggregator, - hash: &str, + artifact: &MithrilStakeDistributionListItemMessage, expected_epoch_min: Epoch, - ) -> StdResult { - let url = format!( - "{}/artifact/mithril-stake-distribution/{hash}", - aggregator.endpoint() - ); - info!( - "Asserting the aggregator is signing the mithril stake distribution message `{}` with an expected min epoch of `{}`", - hash, - expected_epoch_min; - "aggregator" => &aggregator.name() - ); - - async fn fetch_mithril_stake_distribution_message( - url: String, - expected_epoch_min: Epoch, - ) -> StdResult> { - match get_json_response::(url.clone()).await? { - Ok(stake_distribution) => match stake_distribution.epoch { - epoch if epoch >= expected_epoch_min => Ok(Some(stake_distribution)), - epoch => Err(anyhow!( - "Minimum expected mithril stake distribution epoch not reached: {epoch} < {expected_epoch_min}" - )), - }, - Err(err) => Err(anyhow!("Invalid mithril stake distribution body: {err}",)), - } - } - - match attempt!(10, Duration::from_millis(1000), { - fetch_mithril_stake_distribution_message(url.clone(), expected_epoch_min).await - }) { - AttemptResult::Ok(stake_distribution) => { - info!("Signer signed a mithril stake distribution"; "certificate_hash" => &stake_distribution.certificate_hash, "aggregator" => &aggregator.name()); - Ok(stake_distribution.certificate_hash) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted assert_signer_is_signing_mithril_stake_distribution, no response from `{url}`" - )), - }.with_context(|| { - format!( - "Requesting aggregator `{}`", - aggregator.name() - ) - }) + ) -> StdResult<()> { + assert_minimal_epoch(artifact, |a| a.epoch, expected_epoch_min) } pub async fn verify_with_client(&self, client: &mut Client, hash: &str) -> StdResult<()> { diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs index 16dff141761..cefb35200fa 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs @@ -1,5 +1,3 @@ -#![allow(unused_imports)] - mod cardano_blocks_transactions; mod cardano_database; mod cardano_stake_distribution; @@ -21,23 +19,10 @@ use reqwest::StatusCode; use serde::de::DeserializeOwned; use slog_scope::{info, warn}; -use mithril_common::{ - StdResult, - entities::{BlockHash, Epoch, EpochSpecifier, TransactionHash}, - messages::{ - CardanoBlocksTransactionsSnapshotListMessage, CardanoBlocksTransactionsSnapshotMessage, - CardanoDatabaseDigestListMessage, CardanoDatabaseSnapshotListMessage, - CardanoDatabaseSnapshotMessage, CardanoStakeDistributionListMessage, - CardanoStakeDistributionMessage, CardanoTransactionSnapshotListMessage, - CardanoTransactionSnapshotMessage, CertificateMessage, MithrilStakeDistributionListMessage, - MithrilStakeDistributionMessage, - }, -}; +use mithril_common::{StdResult, entities::Epoch}; use crate::{ - Aggregator, CardanoBlockCommand, CardanoDbV2Command, CardanoStakeDistributionCommand, - CardanoTransactionCommand, CardanoTransactionV2Command, Client, ClientCommand, FullNode, - MithrilStakeDistributionCommand, NodeVersion, ToolsCommand, UtxoHdCommand, attempt, + Aggregator, Client, ClientCommand, FullNode, NodeVersion, ToolsCommand, UtxoHdCommand, attempt, toolkit::ScenarioToolkitContext, utils::{AttemptResult, file_utils::copy_dir_all}, }; @@ -55,6 +40,58 @@ async fn get_json_response(url: String) -> StdResult( + artifact_name: &str, + artifact_list_url: &str, + hash_extractor: fn(&T) -> String, + _context: &ScenarioToolkitContext, + aggregator: &Aggregator, +) -> StdResult { + let url = format!("{}{artifact_list_url}", aggregator.endpoint()); + info!("Waiting for the aggregator to produce a {artifact_name} artifact"; "aggregator" => &aggregator.name()); + + async fn fetch_last_artifact( + artifact_name: &str, + url: String, + ) -> StdResult> { + match get_json_response::>(url).await? { + // Artifact lists are sorted from newest to oldest, so the first item is the latest + Ok(list) => Ok(list.into_iter().next()), + Err(err) => Err(anyhow!("Invalid {artifact_name} artifact body: {err}",)), + } + } + + match attempt!(30, Duration::from_millis(2000), { + fetch_last_artifact(artifact_name, url.clone()).await + }) { + AttemptResult::Ok(last_artifact) => { + info!("Aggregator produced a {artifact_name} artifact"; "hash" => hash_extractor(&last_artifact), "aggregator" => &aggregator.name()); + Ok(last_artifact) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted waiting for {artifact_name}, no response from `{url}`" + )), + } + .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) +} + +fn assert_minimal_epoch( + artifact: &T, + epoch_extractor: fn(&T) -> Epoch, + expected_epoch_min: Epoch, +) -> StdResult<()> { + match epoch_extractor(artifact) { + epoch if epoch >= expected_epoch_min => Ok(()), + epoch => Err(anyhow!( + "Minimum expected artifact epoch not reached: {epoch} < {expected_epoch_min}" + )), + } +} + #[derive(Debug, Clone, Default)] pub struct CheckToolkit { pub cardano_blocks_transactions: CheckCardanoBlocksTransactionsToolkit, From 6f0ad4baa4c8307ed2fe726824b350cf3b09cc64 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 8 Jun 2026 12:47:34 +0200 Subject: [PATCH 11/19] refactor(e2e): move struct and utils from `toolkit/check/mod.rs` to dedicated module --- .../check/cardano_blocks_transactions.rs | 11 +- .../src/toolkit/check/cardano_database.rs | 13 +- .../check/cardano_stake_distribution.rs | 11 +- .../src/toolkit/check/cardano_transactions.rs | 11 +- .../src/toolkit/check/certificate.rs | 10 +- .../check/mithril_stake_distribution.rs | 11 +- .../src/toolkit/check/mod.rs | 158 +----------------- .../src/toolkit/check/toolkit.rs | 88 ++++++++++ .../src/toolkit/check/utils.rs | 75 +++++++++ 9 files changed, 196 insertions(+), 192 deletions(-) create mode 100644 mithril-test-lab/mithril-end-to-end/src/toolkit/check/toolkit.rs create mode 100644 mithril-test-lab/mithril-end-to-end/src/toolkit/check/utils.rs diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs index eef0ced4af8..45ffa06392e 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs @@ -9,12 +9,11 @@ use mithril_common::{ use crate::{ Aggregator, CardanoBlockCommand, CardanoTransactionV2Command, Client, ClientCommand, - toolkit::{ - CheckCertificateToolkit, ScenarioToolkitContext, - check::{assert_minimal_epoch, wait_for_artifact}, - }, + toolkit::{CheckCertificateToolkit, ScenarioToolkitContext}, }; +use super::utils; + #[derive(Debug, Clone, Default)] pub struct CheckCardanoBlocksTransactionsToolkit { context: ScenarioToolkitContext, @@ -55,7 +54,7 @@ impl CheckCardanoBlocksTransactionsToolkit { &self, aggregator: &Aggregator, ) -> StdResult { - wait_for_artifact::( + utils::wait_for_artifact::( "Cardano blocks transactions", "/artifact/cardano-blocks-transactions", |a| a.hash.clone(), @@ -70,7 +69,7 @@ impl CheckCardanoBlocksTransactionsToolkit { artifact: &CardanoBlocksTransactionsSnapshotListItemMessage, expected_epoch_min: Epoch, ) -> StdResult<()> { - assert_minimal_epoch(artifact, |a| a.epoch, expected_epoch_min) + utils::assert_minimal_epoch(artifact, |a| a.epoch, expected_epoch_min) } pub async fn verify_transactions_with_client( diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs index 889f7fb6cdb..05acf3681b9 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs @@ -10,13 +10,12 @@ use mithril_common::{ use crate::{ Aggregator, CardanoDbV2Command, Client, ClientCommand, attempt, - toolkit::{ - CheckCertificateToolkit, ScenarioToolkitContext, - check::{assert_minimal_epoch, get_json_response, wait_for_artifact}, - }, + toolkit::{CheckCertificateToolkit, ScenarioToolkitContext}, utils::AttemptResult, }; +use super::utils; + #[derive(Debug, Clone, Default)] pub struct CheckCardanoDatabaseToolkit { context: ScenarioToolkitContext, @@ -55,7 +54,7 @@ impl CheckCardanoDatabaseToolkit { &self, aggregator: &Aggregator, ) -> StdResult { - wait_for_artifact::( + utils::wait_for_artifact::( "Cardano database snapshot", "/artifact/cardano-database", |a| a.hash.clone(), @@ -70,7 +69,7 @@ impl CheckCardanoDatabaseToolkit { artifact: &CardanoDatabaseSnapshotListItemMessage, expected_epoch_min: Epoch, ) -> StdResult<()> { - assert_minimal_epoch(artifact, |a| a.beacon.epoch, expected_epoch_min) + utils::assert_minimal_epoch(artifact, |a| a.beacon.epoch, expected_epoch_min) } pub async fn node_producing_cardano_database_digests_map( @@ -86,7 +85,7 @@ impl CheckCardanoDatabaseToolkit { async fn fetch_cardano_database_digests_map( url: String, ) -> StdResult>> { - match get_json_response::(url) + match utils::get_json_response::(url) .await? .as_deref() { diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs index 96040ff6317..42cd88ca63e 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs @@ -6,12 +6,11 @@ use mithril_common::{ use crate::{ Aggregator, CardanoStakeDistributionCommand, Client, ClientCommand, - toolkit::{ - CheckCertificateToolkit, ScenarioToolkitContext, - check::{assert_minimal_epoch, wait_for_artifact}, - }, + toolkit::{CheckCertificateToolkit, ScenarioToolkitContext}, }; +use super::utils; + #[derive(Debug, Clone, Default)] pub struct CheckCardanoStakeDistributionToolkit { context: ScenarioToolkitContext, @@ -50,7 +49,7 @@ impl CheckCardanoStakeDistributionToolkit { &self, aggregator: &Aggregator, ) -> StdResult { - wait_for_artifact::( + utils::wait_for_artifact::( "Cardano stake distribution", "/artifact/cardano-stake-distributions", |a| a.hash.clone(), @@ -65,7 +64,7 @@ impl CheckCardanoStakeDistributionToolkit { artifact: &CardanoStakeDistributionListItemMessage, expected_epoch_min: Epoch, ) -> StdResult<()> { - assert_minimal_epoch(artifact, |a| a.epoch, expected_epoch_min) + utils::assert_minimal_epoch(artifact, |a| a.epoch, expected_epoch_min) } pub async fn verify_with_client( diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs index 85b8e69dd74..b64e6c5b487 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs @@ -9,12 +9,11 @@ use mithril_common::{ use crate::{ Aggregator, CardanoTransactionCommand, Client, ClientCommand, - toolkit::{ - CheckCertificateToolkit, ScenarioToolkitContext, - check::{assert_minimal_epoch, wait_for_artifact}, - }, + toolkit::{CheckCertificateToolkit, ScenarioToolkitContext}, }; +use super::utils; + #[derive(Debug, Clone, Default)] pub struct CheckCardanoTransactionsToolkit { context: ScenarioToolkitContext, @@ -53,7 +52,7 @@ impl CheckCardanoTransactionsToolkit { &self, aggregator: &Aggregator, ) -> StdResult { - wait_for_artifact::( + utils::wait_for_artifact::( "Cardano transactions", "/artifact/cardano-transactions", |a| a.hash.clone(), @@ -68,7 +67,7 @@ impl CheckCardanoTransactionsToolkit { artifact: &CardanoTransactionSnapshotListItemMessage, expected_epoch_min: Epoch, ) -> StdResult<()> { - assert_minimal_epoch(artifact, |a| a.epoch, expected_epoch_min) + utils::assert_minimal_epoch(artifact, |a| a.epoch, expected_epoch_min) } pub async fn verify_with_client( diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs index 20ab637bdac..468735f193c 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs @@ -4,11 +4,9 @@ use std::time::Duration; use mithril_common::{StdResult, messages::CertificateMessage}; -use crate::{ - Aggregator, attempt, - toolkit::{ScenarioToolkitContext, check::get_json_response}, - utils::AttemptResult, -}; +use crate::{Aggregator, attempt, toolkit::ScenarioToolkitContext, utils::AttemptResult}; + +use super::utils; #[derive(Debug, Clone, Default)] pub struct CheckCertificateToolkit { @@ -30,7 +28,7 @@ impl CheckCertificateToolkit { info!("Waiting for the aggregator to create a certificate with enough signers"; "aggregator" => &aggregator.name()); async fn fetch_certificate_message(url: String) -> StdResult> { - match get_json_response::(url).await? { + match utils::get_json_response::(url).await? { Ok(certificate) => Ok(Some(certificate)), Err(err) => Err(anyhow!(err).context("Invalid snapshot body")), } diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs index bc1b9c980f1..b4c5b593cac 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs @@ -6,12 +6,11 @@ use mithril_common::{ use crate::{ Aggregator, Client, ClientCommand, MithrilStakeDistributionCommand, - toolkit::{ - CheckCertificateToolkit, ScenarioToolkitContext, - check::{assert_minimal_epoch, wait_for_artifact}, - }, + toolkit::{CheckCertificateToolkit, ScenarioToolkitContext}, }; +use super::utils; + #[derive(Debug, Clone, Default)] pub struct CheckMithrilStakeDistributionToolkit { context: ScenarioToolkitContext, @@ -49,7 +48,7 @@ impl CheckMithrilStakeDistributionToolkit { &self, aggregator: &Aggregator, ) -> StdResult { - wait_for_artifact::( + utils::wait_for_artifact::( "Mithril stake distribution", "/artifact/mithril-stake-distributions", |a| a.hash.clone(), @@ -64,7 +63,7 @@ impl CheckMithrilStakeDistributionToolkit { artifact: &MithrilStakeDistributionListItemMessage, expected_epoch_min: Epoch, ) -> StdResult<()> { - assert_minimal_epoch(artifact, |a| a.epoch, expected_epoch_min) + utils::assert_minimal_epoch(artifact, |a| a.epoch, expected_epoch_min) } pub async fn verify_with_client(&self, client: &mut Client, hash: &str) -> StdResult<()> { diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs index cefb35200fa..c197484fb40 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mod.rs @@ -4,6 +4,8 @@ mod cardano_stake_distribution; mod cardano_transactions; mod certificate; mod mithril_stake_distribution; +mod toolkit; +mod utils; pub use cardano_blocks_transactions::*; pub use cardano_database::*; @@ -11,158 +13,4 @@ pub use cardano_stake_distribution::*; pub use cardano_transactions::*; pub use certificate::*; pub use mithril_stake_distribution::*; - -use std::{path::PathBuf, time::Duration}; - -use anyhow::{Context, anyhow}; -use reqwest::StatusCode; -use serde::de::DeserializeOwned; -use slog_scope::{info, warn}; - -use mithril_common::{StdResult, entities::Epoch}; - -use crate::{ - Aggregator, Client, ClientCommand, FullNode, NodeVersion, ToolsCommand, UtxoHdCommand, attempt, - toolkit::ScenarioToolkitContext, - utils::{AttemptResult, file_utils::copy_dir_all}, -}; - -async fn get_json_response(url: String) -> StdResult> { - match reqwest::get(url.clone()).await { - Ok(response) => { - let r = response.status(); - match r { - StatusCode::OK => Ok(response.json::().await), - s => Err(anyhow!("Unexpected status code from Aggregator: {s}")), - } - } - Err(err) => Err(anyhow!(err).context(format!("Request to `{url}` failed"))), - } -} - -/// Wait until the aggregator produces an artifact, returning the latest one -/// -/// Note: the `artifact_list_url` must start with a `/` -async fn wait_for_artifact( - artifact_name: &str, - artifact_list_url: &str, - hash_extractor: fn(&T) -> String, - _context: &ScenarioToolkitContext, - aggregator: &Aggregator, -) -> StdResult { - let url = format!("{}{artifact_list_url}", aggregator.endpoint()); - info!("Waiting for the aggregator to produce a {artifact_name} artifact"; "aggregator" => &aggregator.name()); - - async fn fetch_last_artifact( - artifact_name: &str, - url: String, - ) -> StdResult> { - match get_json_response::>(url).await? { - // Artifact lists are sorted from newest to oldest, so the first item is the latest - Ok(list) => Ok(list.into_iter().next()), - Err(err) => Err(anyhow!("Invalid {artifact_name} artifact body: {err}",)), - } - } - - match attempt!(30, Duration::from_millis(2000), { - fetch_last_artifact(artifact_name, url.clone()).await - }) { - AttemptResult::Ok(last_artifact) => { - info!("Aggregator produced a {artifact_name} artifact"; "hash" => hash_extractor(&last_artifact), "aggregator" => &aggregator.name()); - Ok(last_artifact) - } - AttemptResult::Err(error) => Err(error), - AttemptResult::Timeout() => Err(anyhow!( - "Timeout exhausted waiting for {artifact_name}, no response from `{url}`" - )), - } - .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) -} - -fn assert_minimal_epoch( - artifact: &T, - epoch_extractor: fn(&T) -> Epoch, - expected_epoch_min: Epoch, -) -> StdResult<()> { - match epoch_extractor(artifact) { - epoch if epoch >= expected_epoch_min => Ok(()), - epoch => Err(anyhow!( - "Minimum expected artifact epoch not reached: {epoch} < {expected_epoch_min}" - )), - } -} - -#[derive(Debug, Clone, Default)] -pub struct CheckToolkit { - pub cardano_blocks_transactions: CheckCardanoBlocksTransactionsToolkit, - pub cardano_database: CheckCardanoDatabaseToolkit, - pub cardano_stake_distribution: CheckCardanoStakeDistributionToolkit, - pub cardano_transactions: CheckCardanoTransactionsToolkit, - pub certificate: CheckCertificateToolkit, - pub mithril_stake_distribution: CheckMithrilStakeDistributionToolkit, -} - -impl CheckToolkit { - pub fn new(context: ScenarioToolkitContext) -> Self { - Self { - cardano_blocks_transactions: CheckCardanoBlocksTransactionsToolkit::new( - context.clone(), - ), - cardano_database: CheckCardanoDatabaseToolkit::new(context.clone()), - cardano_stake_distribution: CheckCardanoStakeDistributionToolkit::new(context.clone()), - cardano_transactions: CheckCardanoTransactionsToolkit::new(context.clone()), - certificate: CheckCertificateToolkit::new(context.clone()), - mithril_stake_distribution: CheckMithrilStakeDistributionToolkit::new(context.clone()), - } - } - - pub async fn client_can_convert_the_ledger_snapshot( - &self, - client: &mut Client, - full_node: &FullNode, - artifacts_dir: PathBuf, - cardano_node_version: NodeVersion, - ) -> StdResult<()> { - if client.version().is_below("0.13.10") { - warn!("Client version is below 0.13.10, skipping snapshot conversion check"); - return Ok(()); - } - - let utxo_hd_flavor = if cardano_node_version.is_below("10.7.0") { - "LMDB" - } else { - "LSM" - }; - - let binary_path = artifacts_dir.join("bin").join("snapshot-converter"); - - //copy the db to another temporary location to avoid any risk of modifying the original one during the conversion process - let db_to_convert = artifacts_dir.join("db_to_convert"); - copy_dir_all(&full_node.db_path, &db_to_convert).with_context(|| { - format!( - "Failed to copy the ledger state database from `{}` to `{}` for the snapshot conversion process", - full_node.db_path.display(), - db_to_convert.display() - ) - })?; - - client - .run(ClientCommand::Tools(ToolsCommand::UtxoHd( - UtxoHdCommand::SnapshotConverter { - db_directory: db_to_convert.to_string_lossy().to_string(), - cardano_node_version: cardano_node_version.to_string(), - binary_path: binary_path.to_string_lossy().to_string(), - config_path: full_node - .snapshot_converter_config_path - .to_string_lossy() - .to_string(), - utxo_hd_flavor: utxo_hd_flavor.to_string(), - commit: true, - }, - ))) - .await?; - info!("Client converted the ledger state into {utxo_hd_flavor} format"); - - Ok(()) - } -} +pub use toolkit::*; diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/toolkit.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/toolkit.rs new file mode 100644 index 00000000000..c567cf6b334 --- /dev/null +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/toolkit.rs @@ -0,0 +1,88 @@ +use std::path::PathBuf; + +use anyhow::Context; +use mithril_common::StdResult; +use slog_scope::{info, warn}; + +use crate::toolkit::{ + CheckCardanoBlocksTransactionsToolkit, CheckCardanoDatabaseToolkit, + CheckCardanoStakeDistributionToolkit, CheckCardanoTransactionsToolkit, CheckCertificateToolkit, + CheckMithrilStakeDistributionToolkit, ScenarioToolkitContext, +}; +use crate::utils::file_utils::copy_dir_all; +use crate::{Client, ClientCommand, FullNode, NodeVersion, ToolsCommand, UtxoHdCommand}; + +#[derive(Debug, Clone, Default)] +pub struct CheckToolkit { + pub cardano_blocks_transactions: CheckCardanoBlocksTransactionsToolkit, + pub cardano_database: CheckCardanoDatabaseToolkit, + pub cardano_stake_distribution: CheckCardanoStakeDistributionToolkit, + pub cardano_transactions: CheckCardanoTransactionsToolkit, + pub certificate: CheckCertificateToolkit, + pub mithril_stake_distribution: CheckMithrilStakeDistributionToolkit, +} + +impl CheckToolkit { + pub fn new(context: ScenarioToolkitContext) -> Self { + Self { + cardano_blocks_transactions: CheckCardanoBlocksTransactionsToolkit::new( + context.clone(), + ), + cardano_database: CheckCardanoDatabaseToolkit::new(context.clone()), + cardano_stake_distribution: CheckCardanoStakeDistributionToolkit::new(context.clone()), + cardano_transactions: CheckCardanoTransactionsToolkit::new(context.clone()), + certificate: CheckCertificateToolkit::new(context.clone()), + mithril_stake_distribution: CheckMithrilStakeDistributionToolkit::new(context.clone()), + } + } + + pub async fn client_can_convert_the_ledger_snapshot( + &self, + client: &mut Client, + full_node: &FullNode, + artifacts_dir: PathBuf, + cardano_node_version: NodeVersion, + ) -> StdResult<()> { + if client.version().is_below("0.13.10") { + warn!("Client version is below 0.13.10, skipping snapshot conversion check"); + return Ok(()); + } + + let utxo_hd_flavor = if cardano_node_version.is_below("10.7.0") { + "LMDB" + } else { + "LSM" + }; + + let binary_path = artifacts_dir.join("bin").join("snapshot-converter"); + + // copy the db to another temporary location to avoid any risk of modifying the original one during the conversion process + let db_to_convert = artifacts_dir.join("db_to_convert"); + copy_dir_all(&full_node.db_path, &db_to_convert).with_context(|| { + format!( + "Failed to copy the ledger state database from `{}` to `{}` for the snapshot conversion process", + full_node.db_path.display(), + db_to_convert.display() + ) + })?; + + client + .run(ClientCommand::Tools(ToolsCommand::UtxoHd( + UtxoHdCommand::SnapshotConverter { + db_directory: db_to_convert.to_string_lossy().to_string(), + cardano_node_version: cardano_node_version.to_string(), + binary_path: binary_path.to_string_lossy().to_string(), + config_path: full_node + .snapshot_converter_config_path + .to_string_lossy() + .to_string(), + utxo_hd_flavor: utxo_hd_flavor.to_string(), + commit: true, + }, + ))) + .await?; + info!("Client converted the ledger state into {utxo_hd_flavor} format"); + + Ok(()) + } +} diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/utils.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/utils.rs new file mode 100644 index 00000000000..e0d3fc2d214 --- /dev/null +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/utils.rs @@ -0,0 +1,75 @@ +use std::time::Duration; + +use anyhow::{Context, anyhow}; +use reqwest::StatusCode; +use serde::de::DeserializeOwned; +use slog_scope::info; + +use mithril_common::{StdResult, entities::Epoch}; + +use crate::{Aggregator, attempt, toolkit::ScenarioToolkitContext, utils::AttemptResult}; + +pub async fn get_json_response(url: String) -> StdResult> { + match reqwest::get(url.clone()).await { + Ok(response) => { + let r = response.status(); + match r { + StatusCode::OK => Ok(response.json::().await), + s => Err(anyhow!("Unexpected status code from Aggregator: {s}")), + } + } + Err(err) => Err(anyhow!(err).context(format!("Request to `{url}` failed"))), + } +} + +/// Wait until the aggregator produces an artifact, returning the latest one +/// +/// Note: the `artifact_list_url` must start with a `/` +pub async fn wait_for_artifact( + artifact_name: &str, + artifact_list_url: &str, + hash_extractor: fn(&T) -> String, + _context: &ScenarioToolkitContext, + aggregator: &Aggregator, +) -> StdResult { + let url = format!("{}{artifact_list_url}", aggregator.endpoint()); + info!("Waiting for the aggregator to produce a {artifact_name} artifact"; "aggregator" => &aggregator.name()); + + async fn fetch_last_artifact( + artifact_name: &str, + url: String, + ) -> StdResult> { + match get_json_response::>(url).await? { + // Artifact lists are sorted from newest to oldest, so the first item is the latest + Ok(list) => Ok(list.into_iter().next()), + Err(err) => Err(anyhow!("Invalid {artifact_name} artifact body: {err}",)), + } + } + + match attempt!(30, Duration::from_millis(2000), { + fetch_last_artifact(artifact_name, url.clone()).await + }) { + AttemptResult::Ok(last_artifact) => { + info!("Aggregator produced a {artifact_name} artifact"; "hash" => hash_extractor(&last_artifact), "aggregator" => &aggregator.name()); + Ok(last_artifact) + } + AttemptResult::Err(error) => Err(error), + AttemptResult::Timeout() => Err(anyhow!( + "Timeout exhausted waiting for {artifact_name}, no response from `{url}`" + )), + } + .with_context(|| format!("Requesting aggregator `{}`", aggregator.name())) +} + +pub fn assert_minimal_epoch( + artifact: &T, + epoch_extractor: fn(&T) -> Epoch, + expected_epoch_min: Epoch, +) -> StdResult<()> { + match epoch_extractor(artifact) { + epoch if epoch >= expected_epoch_min => Ok(()), + epoch => Err(anyhow!( + "Minimum expected artifact epoch not reached: {epoch} < {expected_epoch_min}" + )), + } +} From ba165033be54fa1c6cf2d7f4072e62fabd3414cc Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:18:38 +0200 Subject: [PATCH 12/19] refactor(e2e): rename `wait_for_artifact` to `wait_for_latest_artifact` across check toolkits --- .../src/toolkit/check/cardano_blocks_transactions.rs | 2 +- .../mithril-end-to-end/src/toolkit/check/cardano_database.rs | 2 +- .../src/toolkit/check/cardano_stake_distribution.rs | 2 +- .../src/toolkit/check/cardano_transactions.rs | 2 +- .../src/toolkit/check/mithril_stake_distribution.rs | 2 +- mithril-test-lab/mithril-end-to-end/src/toolkit/check/utils.rs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs index 45ffa06392e..85190c60d66 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs @@ -54,7 +54,7 @@ impl CheckCardanoBlocksTransactionsToolkit { &self, aggregator: &Aggregator, ) -> StdResult { - utils::wait_for_artifact::( + utils::wait_for_latest_artifact::( "Cardano blocks transactions", "/artifact/cardano-blocks-transactions", |a| a.hash.clone(), diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs index 05acf3681b9..ded3c89d3dc 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs @@ -54,7 +54,7 @@ impl CheckCardanoDatabaseToolkit { &self, aggregator: &Aggregator, ) -> StdResult { - utils::wait_for_artifact::( + utils::wait_for_latest_artifact::( "Cardano database snapshot", "/artifact/cardano-database", |a| a.hash.clone(), diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs index 42cd88ca63e..e88866dcfca 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs @@ -49,7 +49,7 @@ impl CheckCardanoStakeDistributionToolkit { &self, aggregator: &Aggregator, ) -> StdResult { - utils::wait_for_artifact::( + utils::wait_for_latest_artifact::( "Cardano stake distribution", "/artifact/cardano-stake-distributions", |a| a.hash.clone(), diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs index b64e6c5b487..a0e69725881 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs @@ -52,7 +52,7 @@ impl CheckCardanoTransactionsToolkit { &self, aggregator: &Aggregator, ) -> StdResult { - utils::wait_for_artifact::( + utils::wait_for_latest_artifact::( "Cardano transactions", "/artifact/cardano-transactions", |a| a.hash.clone(), diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs index b4c5b593cac..ae09ab58b46 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs @@ -48,7 +48,7 @@ impl CheckMithrilStakeDistributionToolkit { &self, aggregator: &Aggregator, ) -> StdResult { - utils::wait_for_artifact::( + utils::wait_for_latest_artifact::( "Mithril stake distribution", "/artifact/mithril-stake-distributions", |a| a.hash.clone(), diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/utils.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/utils.rs index e0d3fc2d214..2626bffc033 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/utils.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/utils.rs @@ -25,7 +25,7 @@ pub async fn get_json_response(url: String) -> StdResult( +pub async fn wait_for_latest_artifact( artifact_name: &str, artifact_list_url: &str, hash_extractor: fn(&T) -> String, From 0c95e9d755f6ab6501c9c61d230db47431b6051c Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:20:32 +0200 Subject: [PATCH 13/19] refactor(e2e): fix logging message in certificate check utility `is_creating_certificate_with_enough_signers` wait for any certificate, not for a certificate with a given minimum of signers. --- .../mithril-end-to-end/src/toolkit/check/certificate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs index 468735f193c..feefe2e4fef 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs @@ -25,7 +25,7 @@ impl CheckCertificateToolkit { total_signers_expected: usize, ) -> StdResult<()> { let url = format!("{}/certificate/{certificate_hash}", aggregator.endpoint()); - info!("Waiting for the aggregator to create a certificate with enough signers"; "aggregator" => &aggregator.name()); + info!("Waiting for the aggregator to create a certificate"; "aggregator" => &aggregator.name()); async fn fetch_certificate_message(url: String) -> StdResult> { match utils::get_json_response::(url).await? { From 143c5eabee5e941f71c244a226198c2acf49a650 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:14:34 +0200 Subject: [PATCH 14/19] refactor(e2e): use cardano epoch length in `ScenarioToolkitContext` duration utilities - Remove `Default` trait implementations across various toolkits to force user to provide an epoch length - Refactored `ScenarioToolkitContext` to support Cardano epoch-based duration calculations with new helper methods and constructors. --- .../mithril-end-to-end/src/main.rs | 9 ++- .../check/cardano_blocks_transactions.rs | 2 +- .../src/toolkit/check/cardano_database.rs | 2 +- .../check/cardano_stake_distribution.rs | 2 +- .../src/toolkit/check/cardano_transactions.rs | 2 +- .../src/toolkit/check/certificate.rs | 2 +- .../check/mithril_stake_distribution.rs | 2 +- .../src/toolkit/check/toolkit.rs | 2 +- .../mithril-end-to-end/src/toolkit/context.rs | 59 ++++++++++++++----- .../mithril-end-to-end/src/toolkit/exec.rs | 2 +- .../mithril-end-to-end/src/toolkit/mod.rs | 8 +-- .../mithril-end-to-end/src/toolkit/wait.rs | 2 +- 12 files changed, 59 insertions(+), 35 deletions(-) diff --git a/mithril-test-lab/mithril-end-to-end/src/main.rs b/mithril-test-lab/mithril-end-to-end/src/main.rs index 306e86375c5..a059732eb0e 100644 --- a/mithril-test-lab/mithril-end-to-end/src/main.rs +++ b/mithril-test-lab/mithril-end-to-end/src/main.rs @@ -20,7 +20,7 @@ use tokio::{ use mithril_common::StdResult; use mithril_doc::GenerateDocCommands; use mithril_end_to_end::scenario::{FullScenario, RunOnlyScenario}; -use mithril_end_to_end::toolkit::ScenarioToolkit; +use mithril_end_to_end::toolkit::{ScenarioToolkit, ScenarioToolkitContext}; use mithril_end_to_end::{ AggregateSignatureType, Aggregator, Client, CompatibilityChecker, CompatibilityCheckerError, Devnet, DevnetBootstrapArgs, DmqNodeFlavor, MithrilInfrastructure, MithrilInfrastructureConfig, @@ -175,7 +175,7 @@ struct CardanoDevnetArgs { #[clap(long, default_value_t = 0.10)] cardano_slot_length: f64, - /// Length of a Cardano epoch in the devnet (in s) + /// Length of a Cardano epoch in the devnet (multiple of the slot length) #[clap(long, default_value_t = 30.0)] cardano_epoch_length: f64, @@ -426,7 +426,10 @@ impl App { ), ]))?; - let toolkit = ScenarioToolkit::default(); + let toolkit = ScenarioToolkit::new(ScenarioToolkitContext::new_from_cardano_epoch( + args.cardano_devnet.cardano_slot_length, + args.cardano_devnet.cardano_epoch_length, + )); let devnet = Devnet::bootstrap(&DevnetBootstrapArgs { devnet_scripts_dir: args.cardano_devnet.devnet_scripts_directory, diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs index 85190c60d66..e08d6975d2d 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_blocks_transactions.rs @@ -14,7 +14,7 @@ use crate::{ use super::utils; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct CheckCardanoBlocksTransactionsToolkit { context: ScenarioToolkitContext, } diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs index ded3c89d3dc..18d625159b2 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs @@ -16,7 +16,7 @@ use crate::{ use super::utils; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct CheckCardanoDatabaseToolkit { context: ScenarioToolkitContext, } diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs index e88866dcfca..e9b5cf1a5bf 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_stake_distribution.rs @@ -11,7 +11,7 @@ use crate::{ use super::utils; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct CheckCardanoStakeDistributionToolkit { context: ScenarioToolkitContext, } diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs index a0e69725881..9fe96d7b5bd 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_transactions.rs @@ -14,7 +14,7 @@ use crate::{ use super::utils; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct CheckCardanoTransactionsToolkit { context: ScenarioToolkitContext, } diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs index feefe2e4fef..ff746850348 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs @@ -8,7 +8,7 @@ use crate::{Aggregator, attempt, toolkit::ScenarioToolkitContext, utils::Attempt use super::utils; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct CheckCertificateToolkit { context: ScenarioToolkitContext, } diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs index ae09ab58b46..757b177a577 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/mithril_stake_distribution.rs @@ -11,7 +11,7 @@ use crate::{ use super::utils; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct CheckMithrilStakeDistributionToolkit { context: ScenarioToolkitContext, } diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/toolkit.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/toolkit.rs index c567cf6b334..0c273a20f6d 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/toolkit.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/toolkit.rs @@ -12,7 +12,7 @@ use crate::toolkit::{ use crate::utils::file_utils::copy_dir_all; use crate::{Client, ClientCommand, FullNode, NodeVersion, ToolsCommand, UtxoHdCommand}; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct CheckToolkit { pub cardano_blocks_transactions: CheckCardanoBlocksTransactionsToolkit, pub cardano_database: CheckCardanoDatabaseToolkit, diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/context.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/context.rs index 487e95ce40e..0ad57309833 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/context.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/context.rs @@ -1,6 +1,6 @@ use std::time::Duration; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct ScenarioToolkitContext { attempt_policy: AttemptPolicy, } @@ -10,44 +10,71 @@ impl ScenarioToolkitContext { Self { attempt_policy } } - pub fn with_base_duration(base_duration: Duration) -> Self { - Self::new(AttemptPolicy::new(base_duration)) + pub fn new_from_cardano_epoch(slot_length_in_s: f64, number_of_slot_per_epoch: f64) -> Self { + Self { + attempt_policy: AttemptPolicy::from_cardano_epoch( + slot_length_in_s, + number_of_slot_per_epoch, + ), + } } pub fn attempt_policy(&self) -> AttemptPolicy { self.attempt_policy } - pub fn short_delay(self) -> Duration { - self.attempt_policy.delay(1) + pub fn tenth_epoch_delay(&self) -> Duration { + self.attempt_policy.delay(0.10) } - pub fn artifact_delay(self) -> Duration { - self.attempt_policy.delay(2) + pub fn half_epoch_delay(&self) -> Duration { + self.attempt_policy.delay(0.5) } - pub fn long_delay(self) -> Duration { - self.attempt_policy.delay(5) + pub fn full_epoch_delay(&self) -> Duration { + self.attempt_policy.epoch_duration } } #[derive(Debug, Clone, Copy)] pub struct AttemptPolicy { - base_duration: Duration, + epoch_duration: Duration, } impl AttemptPolicy { pub const fn new(base_duration: Duration) -> Self { - Self { base_duration } + Self { + epoch_duration: base_duration, + } } - pub fn delay(self, multiplier: u32) -> Duration { - self.base_duration * multiplier + pub fn from_cardano_epoch(slot_length_in_s: f64, number_of_slot_per_epoch: f64) -> Self { + Self::new(Duration::from_secs_f64( + slot_length_in_s * number_of_slot_per_epoch, + )) + } + + pub fn delay(self, multiplier: f32) -> Duration { + self.epoch_duration.mul_f32(multiplier) } } -impl Default for AttemptPolicy { - fn default() -> Self { - Self::new(Duration::from_secs(1)) +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn building_attempt_policy_from_cardano_epoch() { + let slot_length_in_s = 0.5; + let number_of_slot_per_epoch = 10.0; + let policy = AttemptPolicy::from_cardano_epoch(slot_length_in_s, number_of_slot_per_epoch); + assert_eq!(policy.epoch_duration, Duration::from_secs(5)); + } + + #[test] + fn delay_calculation() { + let policy = AttemptPolicy::new(Duration::from_secs(10)); + assert_eq!(policy.delay(0.5), Duration::from_secs(5)); + assert_eq!(policy.delay(2.0), Duration::from_secs(20)); } } diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs index ff53bb5d3fd..34dfb16d104 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/exec.rs @@ -10,7 +10,7 @@ use mithril_common::messages::AggregatorStatusMessage; use crate::toolkit::ScenarioToolkitContext; use crate::{AggregateSignatureType, Aggregator, Devnet}; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct ExecToolkit { _context: ScenarioToolkitContext, } diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/mod.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/mod.rs index 02e79f0b167..64513f3855e 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/mod.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/mod.rs @@ -8,9 +8,7 @@ pub use context::*; pub use exec::*; pub use wait::*; -use std::time::Duration; - -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct ScenarioToolkit { pub check: CheckToolkit, pub exec: ExecToolkit, @@ -25,8 +23,4 @@ impl ScenarioToolkit { wait: WaitToolkit::new(context), } } - - pub fn with_base_duration(base_duration: Duration) -> Self { - Self::new(ScenarioToolkitContext::with_base_duration(base_duration)) - } } diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs index d7da8e3bb51..016cfd5b435 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs @@ -8,7 +8,7 @@ use mithril_common::{StdResult, entities::Epoch, messages::EpochSettingsMessage} use crate::{Aggregator, attempt, toolkit::ScenarioToolkitContext, utils::AttemptResult}; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct WaitToolkit { context: ScenarioToolkitContext, } From 5fd74593ebb1855b80d125917553c27b568da4d9 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:23:12 +0200 Subject: [PATCH 15/19] refactor(e2e): replace hardcoded durations with context-based delay utilities - Updated all `attempt!` calls to use `ScenarioToolkitContext` delay helpers (`tenth_epoch_delay` and `half_epoch_delay`) for consistency and configurability. --- .../src/toolkit/check/cardano_database.rs | 3 +-- .../mithril-end-to-end/src/toolkit/check/certificate.rs | 3 +-- .../mithril-end-to-end/src/toolkit/check/utils.rs | 6 ++---- mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs | 7 +++---- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs index 18d625159b2..5899aa5c707 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/cardano_database.rs @@ -1,6 +1,5 @@ use anyhow::{Context, anyhow}; use slog_scope::{info, warn}; -use std::time::Duration; use mithril_common::{ StdResult, @@ -100,7 +99,7 @@ impl CheckCardanoDatabaseToolkit { } } - match attempt!(30, Duration::from_millis(2000), { + match attempt!(30, self.context.tenth_epoch_delay(), { fetch_cardano_database_digests_map(url.clone()).await }) { AttemptResult::Ok(cardano_database_digests_map) => { diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs index ff746850348..9bccb0b9071 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/certificate.rs @@ -1,6 +1,5 @@ use anyhow::{Context, anyhow}; use slog_scope::info; -use std::time::Duration; use mithril_common::{StdResult, messages::CertificateMessage}; @@ -34,7 +33,7 @@ impl CheckCertificateToolkit { } } - match attempt!(10, Duration::from_millis(1000), { + match attempt!(10, self.context.tenth_epoch_delay(), { fetch_certificate_message(url.clone()).await }) { AttemptResult::Ok(certificate) => { diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/utils.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/utils.rs index 2626bffc033..1d2afcf3bce 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/check/utils.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/check/utils.rs @@ -1,5 +1,3 @@ -use std::time::Duration; - use anyhow::{Context, anyhow}; use reqwest::StatusCode; use serde::de::DeserializeOwned; @@ -29,7 +27,7 @@ pub async fn wait_for_latest_artifact( artifact_name: &str, artifact_list_url: &str, hash_extractor: fn(&T) -> String, - _context: &ScenarioToolkitContext, + context: &ScenarioToolkitContext, aggregator: &Aggregator, ) -> StdResult { let url = format!("{}{artifact_list_url}", aggregator.endpoint()); @@ -46,7 +44,7 @@ pub async fn wait_for_latest_artifact( } } - match attempt!(30, Duration::from_millis(2000), { + match attempt!(30, context.tenth_epoch_delay(), { fetch_last_artifact(artifact_name, url.clone()).await }) { AttemptResult::Ok(last_artifact) => { diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs index 016cfd5b435..a280472fb26 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs @@ -1,7 +1,6 @@ use anyhow::{Context, anyhow}; use reqwest::StatusCode; use slog_scope::{info, warn}; -use std::time::Duration; use mithril_cardano_node_internal_database::entities::ImmutableFile; use mithril_common::{StdResult, entities::Epoch, messages::EpochSettingsMessage}; @@ -22,7 +21,7 @@ impl WaitToolkit { info!("Waiting that enough immutable have been written in the devnet"; "aggregator" => aggregator.name()); let db_directory = aggregator.db_directory(); - match attempt!(24, Duration::from_secs(5), { + match attempt!(20, self.context.half_epoch_delay(), { match ImmutableFile::list_completed_in_dir(db_directory) .with_context(|| { format!( @@ -53,7 +52,7 @@ impl WaitToolkit { let url = format!("{aggregator_endpoint}/epoch-settings"); info!("Waiting for the aggregator to expose epoch settings"; "aggregator" => aggregator.name()); - match attempt!(20, Duration::from_millis(1000), { + match attempt!(20, self.context.half_epoch_delay(), { match reqwest::get(url.clone()).await { Ok(response) => match response.status() { StatusCode::OK => { @@ -93,7 +92,7 @@ impl WaitToolkit { "target_epoch" => ?target_epoch ); - match attempt!(90, Duration::from_millis(1000), { + match attempt!(90, self.context.half_epoch_delay(), { match aggregator .chain_observer() .get_current_epoch() From 2dbd5fc8900518b3ea9350f31afe528c07e77265 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:20:35 +0200 Subject: [PATCH 16/19] refactor(e2e): restructure and improve scenario handling - `Full` and `RunOnly` Scenarios are now subcommands - Default scenario to `full` - Moved `check_client_cli_snapshot_converter` to full subcommand as it's only relevant here - Moved `signed_entity_types` to both full and runonly subcommands (note: upcoming "minimal" scenario will only allow one signed entity type, this give the flexibility to parametrize that) - Simplified infrastructure and CLI parsing logic by removing redundant arguments. - Updated `README.md` and CI to reflect changes in Mithril execution commands. --- .github/workflows/backward-compatibility.yml | 1 + .github/workflows/ci.yml | 2 +- mithril-test-lab/cardano-devnet/README.md | 4 +- .../mithril-end-to-end/src/main.rs | 134 ++++++++++++------ .../src/mithril/infrastructure.rs | 18 +-- .../mithril-end-to-end/src/scenario/full.rs | 5 +- 6 files changed, 103 insertions(+), 61 deletions(-) diff --git a/.github/workflows/backward-compatibility.yml b/.github/workflows/backward-compatibility.yml index 40ffacb7cc3..ebfff8af52d 100644 --- a/.github/workflows/backward-compatibility.yml +++ b/.github/workflows/backward-compatibility.yml @@ -155,6 +155,7 @@ jobs: --cardano-node-version ${{ matrix.cardano_node_version }} \ --cardano-slot-length 0.25 \ --cardano-epoch-length 45.0 \ + full \ --signed-entity-types ${{ inputs.signed-entity-types }} EXIT_CODE=$? if [ $EXIT_CODE -eq 0 ]; then diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c380730f339..1d83bf68d60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -345,7 +345,7 @@ jobs: run_id: ["#1"] extra_args: [ - "--aggregate-signature-type=Concatenation --check-client-cli-snapshot-converter", + "--aggregate-signature-type=Concatenation full --check-client-cli-snapshot-converter", ] include: diff --git a/mithril-test-lab/cardano-devnet/README.md b/mithril-test-lab/cardano-devnet/README.md index 382a4e04bba..a6cb977c987 100644 --- a/mithril-test-lab/cardano-devnet/README.md +++ b/mithril-test-lab/cardano-devnet/README.md @@ -228,8 +228,8 @@ pool1n6sxl7cfe9j9mf6jv228nluvy3k3xdu62chqk2wfaazrsenz4jz 5.258e-4 # Running Mihtril -Mithril can be run from the E2E test with --run-only argument +Mithril can be run from the E2E test with the `run-only` command -`cargo run -p mithril-end-to-end -- -vvv --bin-directory binaries_location/ --devnet-scripts-directory=mithril-test-lab/cardano-devnet/ --run-only` +`cargo run -p mithril-end-to-end -- -vvv --bin-directory binaries_location/ --devnet-scripts-directory=mithril-test-lab/cardano-devnet/ run-only` See more about Mithril E2E testing in mithril-end-to-end [README.md](../mithril-end-to-end/README.md) diff --git a/mithril-test-lab/mithril-end-to-end/src/main.rs b/mithril-test-lab/mithril-end-to-end/src/main.rs index a059732eb0e..db6417a091b 100644 --- a/mithril-test-lab/mithril-end-to-end/src/main.rs +++ b/mithril-test-lab/mithril-end-to-end/src/main.rs @@ -27,12 +27,16 @@ use mithril_end_to_end::{ NodeVersion, RelaySigner, RetryableDevnetError, Signer, }; +/// Default signed entity types used by scenarios that support multiple entities, such as Full and RunOnly. +const DEFAULT_SIGNED_ENTITY_TYPES: &str = + "CardanoTransactions,CardanoBlocksTransactions,CardanoStakeDistribution,CardanoDatabase"; + /// Tests args #[derive(Parser, Debug, Clone)] pub struct Cli { - /// Available commands + /// Test scenario to run #[command(subcommand)] - command: Option, + scenario: Option, /// A directory where all logs, generated devnet artifacts, snapshots and store folder /// will be located. @@ -60,9 +64,6 @@ pub struct Cli { #[command(flatten)] network_topology: NetworkTopologyArgs, - #[command(flatten)] - scenario: ScenarioArgs, - /// Verbosity level #[clap( short, @@ -73,15 +74,60 @@ pub struct Cli { verbose: u8, } -#[derive(Args, Debug, Clone)] -struct ScenarioArgs { - /// Enable 'run-only' mode - #[clap(long)] - run_only: bool, +#[derive(Subcommand, Debug, Clone, PartialEq)] +enum ScenarioArgs { + /// Run the full scenario (bootstrap, run, shutdown) [default] + Full { + /// Will check the ledger snapshot conversion step using utxo-hd snapshot-converter + #[clap(long)] + check_client_cli_snapshot_converter: bool, + + /// Signed entity types parameters (discriminants names in an ordered comma separated list). + #[clap( + long, + value_delimiter = ',', + default_value = DEFAULT_SIGNED_ENTITY_TYPES + )] + signed_entity_types: Vec, + }, + /// Bootstrap a Cardano devnet, Mithril Aggregators and Signers, and run continuously + RunOnly { + /// Signed entity types parameters (discriminants names in an ordered comma separated list). + #[clap( + long, + value_delimiter = ',', + default_value = DEFAULT_SIGNED_ENTITY_TYPES + )] + signed_entity_types: Vec, + }, + // Note: not a scenario, but clap doesn't support more than one subcommand per command + #[clap(alias("doc"), hide(true))] + GenerateDoc(GenerateDocCommands), +} - /// Will check the ledger snapshot conversion step using utxo-hd snapshot-converter - #[clap(long)] - check_client_cli_snapshot_converter: bool, +impl Default for ScenarioArgs { + fn default() -> Self { + Self::Full { + check_client_cli_snapshot_converter: false, + signed_entity_types: DEFAULT_SIGNED_ENTITY_TYPES.split(',').map(String::from).collect(), + } + } +} + +impl ScenarioArgs { + fn signed_entity_types(&self) -> Vec { + match self { + ScenarioArgs::Full { + signed_entity_types, + .. + } => signed_entity_types.clone(), + ScenarioArgs::RunOnly { + signed_entity_types, + .. + } => signed_entity_types.clone(), + ScenarioArgs::GenerateDoc(..) => vec![], + } + } } #[derive(Args, Debug, Clone)] @@ -148,14 +194,6 @@ struct MithrilArgs { #[clap(long, default_value = "cardano-chain")] mithril_era_reader_adapter: String, - /// Signed entity types parameters (discriminants names in an ordered comma separated list). - #[clap( - long, - value_delimiter = ',', - default_value = "CardanoTransactions,CardanoBlocksTransactions,CardanoStakeDistribution,CardanoDatabase" - )] - signed_entity_types: Vec, - /// Aggregate signature type used to create the certificates #[clap(long, value_enum, default_value = "Concatenation")] aggregate_signature_type: AggregateSignatureType, @@ -219,12 +257,6 @@ impl Cli { } } -#[derive(Subcommand, Debug, Clone)] -enum EndToEndCommands { - #[clap(alias("doc"), hide(true))] - GenerateDoc(GenerateDocCommands), -} - fn main() -> AppResult { tokio::runtime::Builder::new_multi_thread() .enable_all() @@ -238,7 +270,7 @@ async fn main_exec() -> StdResult<()> { let args = Cli::parse(); let _guard = slog_scope::set_global_logger(build_logger(&args)); - if let Some(EndToEndCommands::GenerateDoc(cmd)) = &args.command { + if let Some(ScenarioArgs::GenerateDoc(cmd)) = &args.scenario { return cmd.execute(&mut Cli::command()).map_err(|message| anyhow!(message)); } @@ -393,8 +425,7 @@ impl App { ) -> StdResult<()> { let server_port = 8080; args.validate()?; - let run_only_mode = args.scenario.run_only; - let check_client_cli_snapshot_converter = args.scenario.check_client_cli_snapshot_converter; + let scenario = args.scenario.unwrap_or_default(); let use_relays = args.network_topology.use_relays; let relay_signer_registration_mode = args.network_topology.relay_signer_registration_mode; let relay_signature_registration_mode = @@ -465,10 +496,8 @@ impl App { mithril_run_interval: args.mithril.mithril_run_interval, mithril_era: args.mithril.mithril_era, mithril_era_reader_adapter: args.mithril.mithril_era_reader_adapter, - signed_entity_types: args.mithril.signed_entity_types.clone(), + signed_entity_types: scenario.signed_entity_types(), aggregate_signature_type: args.mithril.aggregate_signature_type, - run_only_mode, - check_client_cli_snapshot_converter, use_dmq, dmq_node_flavor: args.network_topology.dmq_node_flavor, use_relays, @@ -483,23 +512,34 @@ impl App { ); *self.infrastructure.lock().await = Some(infrastructure.clone()); - let runner: StdResult<()> = match run_only_mode { - true => RunOnlyScenario::new(toolkit, infrastructure).run().await, - false => { - FullScenario::new( + let (runner, run_forever): (StdResult<()>, bool) = match scenario { + ScenarioArgs::Full { + check_client_cli_snapshot_converter, + signed_entity_types, + } => { + let runner = FullScenario::new( toolkit, infrastructure, - args.mithril.signed_entity_types, + signed_entity_types, + check_client_cli_snapshot_converter, args.mithril.mithril_next_era, args.mithril.mithril_era_regenesis_on_switch, ) .run() - .await + .await; + (runner, false) + } + ScenarioArgs::RunOnly { .. } => ( + RunOnlyScenario::new(toolkit, infrastructure).run().await, + true, + ), + ScenarioArgs::GenerateDoc(_) => { + unreachable!("ScenarioArgs::GenerateDoc should not be used here") } }; match runner.with_context(|| "Mithril End to End test failed") { - Ok(()) if run_only_mode => loop { + Ok(()) if run_forever => loop { info!( "Mithril end to end is running and will remain active until manually stopped..." ); @@ -589,6 +629,20 @@ fn with_graceful_shutdown(join_set: &mut JoinSet>) { mod tests { use super::*; + #[test] + fn default_scenario_is_full_with_expected_signed_entity_types() { + assert_eq!( + ScenarioArgs::default(), + ScenarioArgs::Full { + check_client_cli_snapshot_converter: false, + signed_entity_types: DEFAULT_SIGNED_ENTITY_TYPES + .split(',') + .map(String::from) + .collect(), + } + ) + } + #[test] fn app_result_exit_code() { let expected_exit_code = ExitCode::SUCCESS; diff --git a/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs b/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs index 3fd30c32a87..00e1e1dcc3a 100644 --- a/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs +++ b/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs @@ -32,8 +32,6 @@ pub struct MithrilInfrastructureConfig { pub mithril_era_reader_adapter: String, pub signed_entity_types: Vec, pub aggregate_signature_type: AggregateSignatureType, - pub run_only_mode: bool, - pub check_client_cli_snapshot_converter: bool, pub use_relays: bool, pub relay_signer_registration_mode: String, pub relay_signature_registration_mode: String, @@ -70,8 +68,6 @@ impl MithrilInfrastructureConfig { mithril_era_reader_adapter: "adapter1".to_string(), signed_entity_types: vec!["type1".to_string()], aggregate_signature_type: AggregateSignatureType::Concatenation, - run_only_mode: false, - check_client_cli_snapshot_converter: false, use_relays: false, relay_signer_registration_mode: "passthrough".to_string(), relay_signature_registration_mode: "passthrough".to_string(), @@ -96,8 +92,6 @@ pub struct MithrilInfrastructure { relay_passives: Vec, cardano_node_version: semver::Version, cardano_chain_observer: Arc, - run_only_mode: bool, - check_client_cli_snapshot_converter: bool, current_era: RwLock, era_reader_adapter: String, use_era_specific_work_dir: bool, @@ -173,8 +167,6 @@ impl MithrilInfrastructure { relay_passives, cardano_chain_observer, cardano_node_version: config.cardano_node_version.clone(), - run_only_mode: config.run_only_mode, - check_client_cli_snapshot_converter: config.check_client_cli_snapshot_converter, current_era: RwLock::new(config.mithril_era.clone()), era_reader_adapter: config.mithril_era_reader_adapter.clone(), use_era_specific_work_dir: config.use_era_specific_work_dir, @@ -434,7 +426,7 @@ impl MithrilInfrastructure { } pub async fn stop_nodes(&self) -> StdResult<()> { - // Note: The aggregators should be stopped *last* since signers depends on it + // Note: The aggregators should be stopped *last* since signers depend on it info!("Stopping Mithril infrastructure"); for signer in &self.signers { signer.stop().await?; @@ -528,14 +520,6 @@ impl MithrilInfrastructure { Client::new(aggregator.endpoint(), &work_dir, &self.bin_dir) } - pub fn run_only_mode(&self) -> bool { - self.run_only_mode - } - - pub fn check_client_cli_snapshot_converter(&self) -> bool { - self.check_client_cli_snapshot_converter - } - pub async fn tail_logs(&self, number_of_line: u64) -> StdResult<()> { for aggregator in self.aggregators() { aggregator.tail_logs(number_of_line).await?; diff --git a/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs b/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs index dc4f4d0e3cb..f4e38625e91 100644 --- a/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs +++ b/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs @@ -24,6 +24,7 @@ pub struct FullScenario { is_signing_cardano_blocks_transactions: bool, is_signing_cardano_stake_distribution: bool, is_signing_cardano_database: bool, + check_client_cli_snapshot_converter: bool, next_era: Option, regenesis_on_era_switch: bool, } @@ -33,6 +34,7 @@ impl FullScenario { toolkit: ScenarioToolkit, infrastructure: Arc, signed_entity_types: Vec, + check_client_cli_snapshot_converter: bool, next_era: Option, regenesis_on_era_switch: bool, ) -> Self { @@ -62,6 +64,7 @@ impl FullScenario { .contains(&SignedEntityTypeDiscriminants::CardanoStakeDistribution.to_string()), is_signing_cardano_database: signed_entity_types .contains(&SignedEntityTypeDiscriminants::CardanoDatabase.to_string()), + check_client_cli_snapshot_converter, next_era, regenesis_on_era_switch, } @@ -229,7 +232,7 @@ impl FullScenario { } // Check the ledger snapshot conversion step using utxo-hd snapshot-converter - if infrastructure.check_client_cli_snapshot_converter() { + if self.check_client_cli_snapshot_converter { let mut client = infrastructure.build_client(aggregator).await?; self.toolkit .check From 65e00975d1a436951214ed12db5b61c8f3d0d230 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:38:51 +0200 Subject: [PATCH 17/19] refactor(e2e): rename `wait_for_*` methods to improve readability and consistency Has those functions are in the `WaitContext` we do not need to repeat `wait_` in their names. --- .../mithril-end-to-end/src/scenario/full.rs | 16 ++++++++-------- .../mithril-end-to-end/src/scenario/run_only.rs | 6 +++--- .../mithril-end-to-end/src/toolkit/wait.rs | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs b/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs index f4e38625e91..42aaccfd921 100644 --- a/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs +++ b/mithril-test-lab/mithril-end-to-end/src/scenario/full.rs @@ -113,7 +113,7 @@ impl FullScenario { ) -> StdResult<()> { let leader_aggregator = infrastructure.leader_aggregator(); - self.toolkit.wait.wait_for_enough_immutable(leader_aggregator).await?; + self.toolkit.wait.for_enough_immutable(leader_aggregator).await?; let chain_observer = leader_aggregator.chain_observer(); let start_epoch = chain_observer.get_current_epoch().await?.unwrap_or_default(); @@ -121,7 +121,7 @@ impl FullScenario { let mut target_epoch = start_epoch + 4; self.toolkit .wait - .wait_for_aggregator_at_target_epoch( + .for_aggregator_at_target_epoch( leader_aggregator, target_epoch, "minimal epoch for the aggregator to be able to bootstrap genesis certificate" @@ -132,13 +132,13 @@ impl FullScenario { .exec .bootstrap_genesis_certificate(leader_aggregator) .await?; - self.toolkit.wait.wait_for_epoch_settings(leader_aggregator).await?; + self.toolkit.wait.for_epoch_settings(leader_aggregator).await?; // Wait 2 epochs before changing stake distribution, so that we use at least one original stake distribution target_epoch += 2; self.toolkit .wait - .wait_for_aggregator_at_target_epoch( + .for_aggregator_at_target_epoch( leader_aggregator, target_epoch, "epoch after which the stake distribution will change".to_string(), @@ -167,7 +167,7 @@ impl FullScenario { let mut target_epoch = start_epoch + 2; self.toolkit .wait - .wait_for_aggregator_at_target_epoch( + .for_aggregator_at_target_epoch( aggregator, target_epoch, "epoch after which the protocol parameters will change".to_string(), @@ -183,7 +183,7 @@ impl FullScenario { // Wait 6 epochs after protocol parameters update, so that we make sure that we use new protocol parameters as well as new stake distribution a few times target_epoch += 6; - self.toolkit.wait.wait_for_aggregator_at_target_epoch( + self.toolkit.wait.for_aggregator_at_target_epoch( aggregator, target_epoch, "epoch after which the certificate chain will be long enough to catch most common troubles with stake distribution and protocol parameters".to_string(), @@ -204,7 +204,7 @@ impl FullScenario { target_epoch += 5; self.toolkit .wait - .wait_for_aggregator_at_target_epoch( + .for_aggregator_at_target_epoch( aggregator, target_epoch, "epoch after which the era switch will have triggered".to_string(), @@ -217,7 +217,7 @@ impl FullScenario { target_epoch += 5; self.toolkit .wait - .wait_for_aggregator_at_target_epoch( + .for_aggregator_at_target_epoch( aggregator, target_epoch, "epoch after which the re-genesis on era switch will be completed" diff --git a/mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs b/mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs index aa89eca3cf4..180a2b4a68f 100644 --- a/mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs +++ b/mithril-test-lab/mithril-end-to-end/src/scenario/run_only.rs @@ -38,7 +38,7 @@ impl RunOnlyScenario { ) -> StdResult<()> { let leader_aggregator = infrastructure.leader_aggregator(); - self.toolkit.wait.wait_for_enough_immutable(leader_aggregator).await?; + self.toolkit.wait.for_enough_immutable(leader_aggregator).await?; let chain_observer = leader_aggregator.chain_observer(); let start_epoch = chain_observer.get_current_epoch().await?.unwrap_or_default(); @@ -46,7 +46,7 @@ impl RunOnlyScenario { let target_epoch = start_epoch + 3; self.toolkit .wait - .wait_for_aggregator_at_target_epoch( + .for_aggregator_at_target_epoch( leader_aggregator, target_epoch, "minimal epoch for the aggregator to be able to bootstrap genesis certificate" @@ -57,7 +57,7 @@ impl RunOnlyScenario { .exec .bootstrap_genesis_certificate(leader_aggregator) .await?; - self.toolkit.wait.wait_for_epoch_settings(leader_aggregator).await?; + self.toolkit.wait.for_epoch_settings(leader_aggregator).await?; // Transfer some funds on the devnet to have some Cardano transactions to sign self.toolkit.exec.transfer_funds(infrastructure.devnet()).await?; diff --git a/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs b/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs index a280472fb26..d345620169a 100644 --- a/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs +++ b/mithril-test-lab/mithril-end-to-end/src/toolkit/wait.rs @@ -17,7 +17,7 @@ impl WaitToolkit { Self { context } } - pub async fn wait_for_enough_immutable(&self, aggregator: &Aggregator) -> StdResult<()> { + pub async fn for_enough_immutable(&self, aggregator: &Aggregator) -> StdResult<()> { info!("Waiting that enough immutable have been written in the devnet"; "aggregator" => aggregator.name()); let db_directory = aggregator.db_directory(); @@ -44,7 +44,7 @@ impl WaitToolkit { } } - pub async fn wait_for_epoch_settings( + pub async fn for_epoch_settings( &self, aggregator: &Aggregator, ) -> StdResult { @@ -80,7 +80,7 @@ impl WaitToolkit { } } - pub async fn wait_for_aggregator_at_target_epoch( + pub async fn for_aggregator_at_target_epoch( &self, aggregator: &Aggregator, target_epoch: Epoch, From 11935119fc449bb385459e4562548a30fc933173 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:41:00 +0200 Subject: [PATCH 18/19] feat(e2e): logs command line arguments at startup --- mithril-test-lab/mithril-end-to-end/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mithril-test-lab/mithril-end-to-end/src/main.rs b/mithril-test-lab/mithril-end-to-end/src/main.rs index db6417a091b..ffddbc612b9 100644 --- a/mithril-test-lab/mithril-end-to-end/src/main.rs +++ b/mithril-test-lab/mithril-end-to-end/src/main.rs @@ -274,6 +274,8 @@ async fn main_exec() -> StdResult<()> { return cmd.execute(&mut Cli::command()).map_err(|message| anyhow!(message)); } + info!("Starting Mithril End-to-End test suite"; "args" => ?args); + let work_dir = { let dir = match &args.work_directory { Some(path) => path.to_owned(), From 0e64a243fa325fa9dfb441f83da17afce00f0d60 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:02:37 +0200 Subject: [PATCH 19/19] chore: bump mithril-end-to-end from `0.4.139` to `0.5.0` --- Cargo.lock | 2 +- mithril-test-lab/mithril-end-to-end/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ba381d72b3..f5670132b8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4444,7 +4444,7 @@ dependencies = [ [[package]] name = "mithril-end-to-end" -version = "0.4.139" +version = "0.5.0" dependencies = [ "anyhow", "async-recursion", diff --git a/mithril-test-lab/mithril-end-to-end/Cargo.toml b/mithril-test-lab/mithril-end-to-end/Cargo.toml index 112e203524c..8bec7dbb81a 100644 --- a/mithril-test-lab/mithril-end-to-end/Cargo.toml +++ b/mithril-test-lab/mithril-end-to-end/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-end-to-end" -version = "0.4.139" +version = "0.5.0" authors = { workspace = true } edition = { workspace = true } documentation = { workspace = true }