From 6bd353151ec61b9d5360a0635e274b0b96d57603 Mon Sep 17 00:00:00 2001 From: Damien LACHAUME / PALO-IT Date: Mon, 12 Feb 2024 20:13:19 +0100 Subject: [PATCH 1/3] feat: add `client-cardano-transaction` example --- Cargo.lock | 13 ++ Cargo.toml | 1 + .../client-cardano-transaction/.gitignore | 3 + .../client-cardano-transaction/Cargo.toml | 19 +++ examples/client-cardano-transaction/README.md | 33 +++++ .../client-cardano-transaction/src/main.rs | 119 ++++++++++++++++++ 6 files changed, 188 insertions(+) create mode 100644 examples/client-cardano-transaction/.gitignore create mode 100644 examples/client-cardano-transaction/Cargo.toml create mode 100644 examples/client-cardano-transaction/README.md create mode 100644 examples/client-cardano-transaction/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 79a14854ce9..947e064504c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -944,6 +944,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "client-cardano-transaction" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "mithril-client", + "slog", + "slog-async", + "slog-term", + "tokio", +] + [[package]] name = "client-mithril-stake-distribution" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index ce2c2262b9e..3dd7dcf83e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ resolver = "2" members = [ "demo/protocol-demo", + "examples/client-cardano-transaction", "examples/client-mithril-stake-distribution", "examples/client-snapshot", "mithril-aggregator", diff --git a/examples/client-cardano-transaction/.gitignore b/examples/client-cardano-transaction/.gitignore new file mode 100644 index 00000000000..e34cd6ae196 --- /dev/null +++ b/examples/client-cardano-transaction/.gitignore @@ -0,0 +1,3 @@ +target/ +client-cardano-transaction +.DS_Store diff --git a/examples/client-cardano-transaction/Cargo.toml b/examples/client-cardano-transaction/Cargo.toml new file mode 100644 index 00000000000..6e7fbadb39b --- /dev/null +++ b/examples/client-cardano-transaction/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "client-cardano-transaction" +description = "Mithril client cardano-transaction example" +version = "0.1.0" +authors = ["dev@iohk.io", "mithril-dev@iohk.io"] +documentation = "https://mithril.network/doc" +edition = "2021" +homepage = "https://mithril.network" +license = "Apache-2.0" +repository = "https://github.com/input-output-hk/mithril/" + +[dependencies] +anyhow = "1.0.79" +clap = { version = "4.4.18", features = ["derive", "env"] } +mithril-client = { path = "../../mithril-client", features = ["unstable"] } +slog = "2.7.0" +slog-async = "2.8.0" +slog-term = "2.9.0" +tokio = { version = "1.35.1", features = ["full"] } diff --git a/examples/client-cardano-transaction/README.md b/examples/client-cardano-transaction/README.md new file mode 100644 index 00000000000..ca7500adcc2 --- /dev/null +++ b/examples/client-cardano-transaction/README.md @@ -0,0 +1,33 @@ +# Mithril client library example: Cardano transaction + +> [!WARNING] +> This example makes use of unstable features of the Mithril client library. +> Use them at your own risk, and expect possible modifications in future releases. +> Please refer to the links provided at the end of this file for the most up-to-date developer documentation. + +## Description + +This example shows how to implement a Mithril client and use its features related to the `Cardano Transaction` type. + +In this example, the client interacts with an aggregator and performs the following operations: +- Retrieve cryptographic proofs of membership of the Cardano transactions set for a list of transactions passed as arguments +- Verify the validity of the proofs +- Verify the validity of the validity of the certificate chain attached to the proofs +- Verify that the certificate chain signs a message computed from the proof + +## Build and run the example + +```bash +# Build from the crate directory +cargo build + +# Run from the crate directory +AGGREGATOR_ENDPOINT=YOUR_AGGREGATOR_ENDPOINT GENESIS_VERIFICATION_KEY=YOUR_GENESIS_VERIFICATION_KEY cargo run CARDANO_TX_HASH1,CARDANO_TX_HASH2,CARDANO_TX_HASH3 + +# Example with from 'testing-sanchonet' network +AGGREGATOR_ENDPOINT=https://aggregator.testing-sanchonet.api.mithril.network/aggregator GENESIS_VERIFICATION_KEY=$(curl -s https://raw.githubusercontent.com/input-output-hk/mithril/main/mithril-infra/configuration/testing-sanchonet/genesis.vkey) cargo run db0dfab664045b117375a743a925385a7a3fa6a104f8bd95fa0f748088bcaff0,b457a094439cc5e371474f5758b4ecded3e1b035fe0717e39d78080e6fe169b2 +``` + +## Links +- **Developer documentation**: https://docs.rs/mithril-client/latest/mithril_client/ +- **Crates.io**: https://crates.io/crates/mithril-client \ No newline at end of file diff --git a/examples/client-cardano-transaction/src/main.rs b/examples/client-cardano-transaction/src/main.rs new file mode 100644 index 00000000000..7fad34359fc --- /dev/null +++ b/examples/client-cardano-transaction/src/main.rs @@ -0,0 +1,119 @@ +//! This example shows how to implement a Mithril client and use its features. +//! +//! In this example, the client interacts with an aggregator whose URL must be specified in the command to get the data. + +use anyhow::anyhow; +use clap::Parser; +use slog::info; +use std::sync::Arc; + +use mithril_client::common::TransactionHash; +use mithril_client::{ClientBuilder, MessageBuilder, MithrilResult, VerifiedCardanoTransactions}; + +#[derive(Parser, Debug)] +#[command(version)] +pub struct Args { + /// Genesis verification key. + #[clap( + long, + env = "GENESIS_VERIFICATION_KEY", + default_value = "5b3132372c37332c3132342c3136312c362c3133372c3133312c3231332c3230372c3131372c3139382c38352c3137362c3139392c3136322c3234312c36382c3132332c3131392c3134352c31332c3233322c3234332c34392c3232392c322c3234392c3230352c3230352c33392c3233352c34345d" + )] + genesis_verification_key: String, + + /// Aggregator endpoint URL. + #[clap( + long, + env = "AGGREGATOR_ENDPOINT", + default_value = "https://aggregator.testing-sanchonet.api.mithril.network/aggregator" + )] + aggregator_endpoint: String, + + /// Hashes of the transactions to certify. + #[clap(value_delimiter = ',', required = true)] + transactions_hashes: Vec, +} + +#[tokio::main] +async fn main() -> MithrilResult<()> { + let args = Args::parse(); + let transactions_hashes = &args + .transactions_hashes + .iter() + .map(|s| s.as_str()) + .collect::>(); + let logger = build_logger(); + let client = + ClientBuilder::aggregator(&args.aggregator_endpoint, &args.genesis_verification_key) + .with_logger(logger.clone()) + .build()?; + + info!(logger, "Fetching a proof for the given transactions...",); + let cardano_transaction_proof = client + .cardano_transaction_proof() + .get_proofs(transactions_hashes) + .await + .unwrap(); + + info!(logger, "Verifying the proof…",); + let verified_transactions = cardano_transaction_proof.verify().unwrap(); + + info!( + logger, + "Fetching the associated certificate and verifying the certificate chain…", + ); + let certificate = client + .certificate() + .verify_chain(&cardano_transaction_proof.certificate_hash) + .await + .unwrap(); + + info!( + logger, + "Verify that the proof is signed in the associated certificate", + ); + let message = MessageBuilder::new() + .compute_cardano_transactions_proofs_message(&certificate, &verified_transactions); + if !certificate.match_message(&message) { + return Err(anyhow!( + "Proof and certificate doesn't match (certificate hash = '{}').", + certificate.hash + )); + } + + log_certify_information( + &verified_transactions, + &cardano_transaction_proof.non_certified_transactions, + ); + + Ok(()) +} + +pub fn log_certify_information( + verified_transactions: &VerifiedCardanoTransactions, + non_certified_transactions: &[TransactionHash], +) { + println!( + r###"Cardano transactions with hashes "'{}'" have been successfully certified by Mithril."###, + verified_transactions.certified_transactions().join(","), + ); + + if !non_certified_transactions.is_empty() { + println!( + r###"No proof could be computed for Cardano transactions with hashes "'{}'". + + Mithril may not have signed those transactions yet, please try again later."###, + non_certified_transactions.join(","), + ); + } +} + +fn build_logger() -> slog::Logger { + use slog::Drain; + + let decorator = slog_term::TermDecorator::new().build(); + let drain = slog_term::FullFormat::new(decorator).build().fuse(); + let drain = slog_async::Async::new(drain).build().fuse(); + + slog::Logger::root(Arc::new(drain), slog::o!()) +} From 7cb352f0f91d0ab4d43c7737e8f4d8b79f9605fd Mon Sep 17 00:00:00 2001 From: Damien LACHAUME / PALO-IT Date: Thu, 15 Feb 2024 09:11:47 +0100 Subject: [PATCH 2/3] feat: add config parameters for snapshot & msd examples --- Cargo.lock | 2 + .../Cargo.toml | 1 + .../README.md | 8 +++- .../src/main.rs | 39 ++++++++++++++---- examples/client-snapshot/Cargo.toml | 1 + examples/client-snapshot/README.md | 8 +++- examples/client-snapshot/src/main.rs | 41 +++++++++++++++---- 7 files changed, 81 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 947e064504c..630f504c3f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -962,6 +962,7 @@ name = "client-mithril-stake-distribution" version = "0.1.6" dependencies = [ "anyhow", + "clap", "mithril-client", "slog", "slog-async", @@ -975,6 +976,7 @@ version = "0.1.6" dependencies = [ "anyhow", "async-trait", + "clap", "futures", "indicatif", "mithril-client", diff --git a/examples/client-mithril-stake-distribution/Cargo.toml b/examples/client-mithril-stake-distribution/Cargo.toml index 73373f98ac1..318ead74724 100644 --- a/examples/client-mithril-stake-distribution/Cargo.toml +++ b/examples/client-mithril-stake-distribution/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/input-output-hk/mithril/" [dependencies] anyhow = "1.0.79" +clap = { version = "4.4.18", features = ["derive", "env"] } mithril-client = { path = "../../mithril-client" } slog = "2.7.0" slog-async = "2.8.0" diff --git a/examples/client-mithril-stake-distribution/README.md b/examples/client-mithril-stake-distribution/README.md index 465b4d90f81..3427527ce9e 100644 --- a/examples/client-mithril-stake-distribution/README.md +++ b/examples/client-mithril-stake-distribution/README.md @@ -4,7 +4,7 @@ This example shows how to implement a Mithril client and use the features related to the `Mithril stake distribution` type. -In this example, the client interacts with a real aggregator on the network `testing-preview` to: +In this example, the client interacts by default with a real aggregator on the network `testing-preview` to: - list the available Mithril stake distributions - get a single Mithril stake distribution - verify a certificate chain @@ -21,6 +21,12 @@ cargo build # Run from the crate directory cargo run + +# Run with your custom network configuration +AGGREGATOR_ENDPOINT=YOUR_AGGREGATOR_ENDPOINT GENESIS_VERIFICATION_KEY=YOUR_GENESIS_VERIFICATION_KEY cargo run + +# Example with 'pre-release-preview' network +AGGREGATOR_ENDPOINT=https://aggregator.pre-release-preview.api.mithril.network/aggregator GENESIS_VERIFICATION_KEY=$(curl -s https://raw.githubusercontent.com/input-output-hk/mithril/main/mithril-infra/configuration/pre-release-preview/genesis.vkey) cargo run ``` ## Links diff --git a/examples/client-mithril-stake-distribution/src/main.rs b/examples/client-mithril-stake-distribution/src/main.rs index 1be177a8ad8..fb1fcf1295a 100644 --- a/examples/client-mithril-stake-distribution/src/main.rs +++ b/examples/client-mithril-stake-distribution/src/main.rs @@ -1,22 +1,44 @@ //! This example shows how to implement a Mithril client and use its features. //! -//! In this example, the client interacts with a real aggregator (`testing-preview`) to get the data. +//! In this example, the client interacts by default with a real aggregator (`testing-preview`) to get the data. //! //! The [SlogFeedbackReceiver] is used to report the progress to the console. use anyhow::anyhow; -use mithril_client::{ClientBuilder, MessageBuilder, MithrilResult}; +use clap::Parser; use slog::info; use std::sync::Arc; +use mithril_client::{ClientBuilder, MessageBuilder, MithrilResult}; + +#[derive(Parser, Debug)] +#[command(version)] +pub struct Args { + /// Genesis verification key. + #[clap( + long, + env = "GENESIS_VERIFICATION_KEY", + default_value = "5b3132372c37332c3132342c3136312c362c3133372c3133312c3231332c3230372c3131372c3139382c38352c3137362c3139392c3136322c3234312c36382c3132332c3131392c3134352c31332c3233322c3234332c34392c3232392c322c3234392c3230352c3230352c33392c3233352c34345d" + )] + genesis_verification_key: String, + + /// Aggregator endpoint URL. + #[clap( + long, + env = "AGGREGATOR_ENDPOINT", + default_value = "https://aggregator.testing-preview.api.mithril.network/aggregator" + )] + aggregator_endpoint: String, +} + #[tokio::main] async fn main() -> MithrilResult<()> { - let aggregator_endpoint = "https://aggregator.testing-preview.api.mithril.network/aggregator"; - let genesis_verification_key = "5b3132372c37332c3132342c3136312c362c3133372c3133312c3231332c3230372c3131372c3139382c38352c3137362c3139392c3136322c3234312c36382c3132332c3131392c3134352c31332c3233322c3234332c34392c3232392c322c3234392c3230352c3230352c33392c3233352c34345d"; + let args = Args::parse(); let logger = build_logger(); - let client = ClientBuilder::aggregator(aggregator_endpoint, genesis_verification_key) - .with_logger(logger.clone()) - .build()?; + let client = + ClientBuilder::aggregator(&args.aggregator_endpoint, &args.genesis_verification_key) + .with_logger(logger.clone()) + .build()?; let mithril_stake_distributions = client.mithril_stake_distribution().list().await?; info!( @@ -27,7 +49,8 @@ async fn main() -> MithrilResult<()> { let last_hash = mithril_stake_distributions .first() .ok_or(anyhow!( - "No Mithril stake distributions could be listed from aggregator: '{aggregator_endpoint}'" + "No Mithril stake distributions could be listed from aggregator: '{}'", + args.aggregator_endpoint ))? .hash .as_ref(); diff --git a/examples/client-snapshot/Cargo.toml b/examples/client-snapshot/Cargo.toml index 4d1587ea309..25342f6fede 100644 --- a/examples/client-snapshot/Cargo.toml +++ b/examples/client-snapshot/Cargo.toml @@ -12,6 +12,7 @@ repository = "https://github.com/input-output-hk/mithril/" [dependencies] anyhow = "1.0.79" async-trait = "0.1.77" +clap = { version = "4.4.18", features = ["derive", "env"] } futures = "0.3.30" indicatif = "0.17.7" mithril-client = { path = "../../mithril-client", features = ["fs"] } diff --git a/examples/client-snapshot/README.md b/examples/client-snapshot/README.md index 17d72616c28..b3afdb8d755 100644 --- a/examples/client-snapshot/README.md +++ b/examples/client-snapshot/README.md @@ -4,7 +4,7 @@ This example shows how to implement a Mithril client and use its features related to the `Snapshot` type. -In this example, the client interacts with a real aggregator on the network `testing-preview` to: +In this example, the client interacts by default with a real aggregator on the network `testing-preview` to: - list the available snapshots - get a single snapshot - download and unpack a snapshot archive @@ -23,6 +23,12 @@ cargo build # Run from the crate directory cargo run + +# Run with your custom network configuration +AGGREGATOR_ENDPOINT=YOUR_AGGREGATOR_ENDPOINT GENESIS_VERIFICATION_KEY=YOUR_GENESIS_VERIFICATION_KEY cargo run + +# Example with 'pre-release-preview' network +AGGREGATOR_ENDPOINT=https://aggregator.pre-release-preview.api.mithril.network/aggregator GENESIS_VERIFICATION_KEY=$(curl -s https://raw.githubusercontent.com/input-output-hk/mithril/main/mithril-infra/configuration/pre-release-preview/genesis.vkey) cargo run ``` ## Links diff --git a/examples/client-snapshot/src/main.rs b/examples/client-snapshot/src/main.rs index 49ff194acba..2f39c67258b 100644 --- a/examples/client-snapshot/src/main.rs +++ b/examples/client-snapshot/src/main.rs @@ -1,37 +1,60 @@ //! This example shows how to implement a Mithril client and use its features. //! -//! In this example, the client interacts with a real aggregator (`testing-preview`) to get the data. +//! In this example, the client interacts by default with a real aggregator (`testing-preview`) to get the data. //! //! A [FeedbackReceiver] using [indicatif] is used to nicely report the progress to the console. use anyhow::{anyhow, Context}; use async_trait::async_trait; +use clap::Parser; use futures::Future; use indicatif::{MultiProgress, ProgressBar, ProgressState, ProgressStyle}; -use mithril_client::feedback::{FeedbackReceiver, MithrilEvent}; -use mithril_client::{ClientBuilder, MessageBuilder, MithrilResult}; use std::fmt::Write; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; use tokio::sync::RwLock; +use mithril_client::feedback::{FeedbackReceiver, MithrilEvent}; +use mithril_client::{ClientBuilder, MessageBuilder, MithrilResult}; + +#[derive(Parser, Debug)] +#[command(version)] +pub struct Args { + /// Genesis verification key. + #[clap( + long, + env = "GENESIS_VERIFICATION_KEY", + default_value = "5b3132372c37332c3132342c3136312c362c3133372c3133312c3231332c3230372c3131372c3139382c38352c3137362c3139392c3136322c3234312c36382c3132332c3131392c3134352c31332c3233322c3234332c34392c3232392c322c3234392c3230352c3230352c33392c3233352c34345d" + )] + genesis_verification_key: String, + + /// Aggregator endpoint URL. + #[clap( + long, + env = "AGGREGATOR_ENDPOINT", + default_value = "https://aggregator.testing-preview.api.mithril.network/aggregator" + )] + aggregator_endpoint: String, +} + #[tokio::main] async fn main() -> MithrilResult<()> { - let aggregator_endpoint = "https://aggregator.testing-preview.api.mithril.network/aggregator"; - let genesis_verification_key = "5b3132372c37332c3132342c3136312c362c3133372c3133312c3231332c3230372c3131372c3139382c38352c3137362c3139392c3136322c3234312c36382c3132332c3131392c3134352c31332c3233322c3234332c34392c3232392c322c3234392c3230352c3230352c33392c3233352c34345d"; + let args = Args::parse(); let work_dir = get_temp_dir()?; let progress_bar = indicatif::MultiProgress::new(); - let client = ClientBuilder::aggregator(aggregator_endpoint, genesis_verification_key) - .add_feedback_receiver(Arc::new(IndicatifFeedbackReceiver::new(&progress_bar))) - .build()?; + let client = + ClientBuilder::aggregator(&args.aggregator_endpoint, &args.genesis_verification_key) + .add_feedback_receiver(Arc::new(IndicatifFeedbackReceiver::new(&progress_bar))) + .build()?; let snapshots = client.snapshot().list().await?; let last_digest = snapshots .first() .ok_or(anyhow!( - "No snapshots could be listed from aggregator: '{aggregator_endpoint}'" + "No snapshots could be listed from aggregator: '{}'", + args.aggregator_endpoint ))? .digest .as_ref(); From bc4933e11505b5ff6d2462978811e49a93783456 Mon Sep 17 00:00:00 2001 From: Damien LACHAUME / PALO-IT Date: Mon, 19 Feb 2024 21:41:20 +0100 Subject: [PATCH 3/3] chore: update crates versions --- Cargo.lock | 4 ++-- examples/client-mithril-stake-distribution/Cargo.toml | 2 +- examples/client-snapshot/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 630f504c3f9..c8d62ed466f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -959,7 +959,7 @@ dependencies = [ [[package]] name = "client-mithril-stake-distribution" -version = "0.1.6" +version = "0.1.7" dependencies = [ "anyhow", "clap", @@ -972,7 +972,7 @@ dependencies = [ [[package]] name = "client-snapshot" -version = "0.1.6" +version = "0.1.7" dependencies = [ "anyhow", "async-trait", diff --git a/examples/client-mithril-stake-distribution/Cargo.toml b/examples/client-mithril-stake-distribution/Cargo.toml index 318ead74724..c533a27748c 100644 --- a/examples/client-mithril-stake-distribution/Cargo.toml +++ b/examples/client-mithril-stake-distribution/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "client-mithril-stake-distribution" description = "Mithril client stake distribution example" -version = "0.1.6" +version = "0.1.7" authors = ["dev@iohk.io", "mithril-dev@iohk.io"] documentation = "https://mithril.network/doc" edition = "2021" diff --git a/examples/client-snapshot/Cargo.toml b/examples/client-snapshot/Cargo.toml index 25342f6fede..9cbe5408014 100644 --- a/examples/client-snapshot/Cargo.toml +++ b/examples/client-snapshot/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "client-snapshot" description = "Mithril client snapshot example" -version = "0.1.6" +version = "0.1.7" authors = ["dev@iohk.io", "mithril-dev@iohk.io"] documentation = "https://mithril.network/doc" edition = "2021"