//! Commit to a secret log and agree to its hash. //! //! This example demonstrates how to build an application that employs [commonware_consensus::simplex]. //! Whenever it is a participant's turn to build a block, they randomly generate a 16-byte secret message and send the //! hashed message to other participants. Participants use consensus to ensure everyone agrees on the same hash in the same //! view. //! //! # Persistence //! //! All consensus data is persisted to disk in the `storage-dir` directory. If you shutdown (whether unclean or not), //! consensus will resume where it left off when you restart. //! //! # Broadcast and Backfilling //! //! This example demonstrates how [commonware_consensus::simplex] can minimally be used. It purposely avoids //! introducing logic to handle broadcasting secret messages and/or backfilling old hashes/messages. Think of this as //! an exercise for the reader. //! //! # Usage (Run at Least 3 to Make Progress) //! //! _To run this example, you must first install [Rust](https://www.rust-lang.org/tools/install)._ //! //! ## Participant 0 (Bootstrapper) //! //! ```sh //! cargo run --release -- --me 0@3000 --participants 0,1,2,3 --storage-dir /tmp/commonware-log/0 //! ``` //! //! ## Participant 1 //! //! ```sh //! cargo run --release -- --bootstrappers 0@127.0.0.1:3000 --me 1@3001 --participants 0,1,2,3 --storage-dir /tmp/commonware-log/1 //! ``` //! //! ## Participant 2 //! //! ```sh //! cargo run --release -- --bootstrappers 0@127.0.0.1:3000 --me 2@3002 --participants 0,1,2,3 --storage-dir /tmp/commonware-log/2 //! ``` //! //! ## Participant 3 //! //! ```sh //! cargo run --release -- --bootstrappers 0@127.0.0.1:3000 --me 3@3003 --participants 0,1,2,3 --storage-dir /tmp/commonware-log/3 //! ``` mod application; mod gui; use clap::{value_parser, Arg, Command}; use commonware_consensus::{ simplex::{self, elector::RoundRobin}, types::{Epoch, ViewDelta}, }; use commonware_cryptography::{ed25519, Sha256, Signer as _}; use commonware_p2p::{authenticated::discovery, Manager}; use commonware_runtime::{buffer::PoolRef, tokio, Metrics, Quota, Runner}; use commonware_utils::{ordered::Set, union, NZUsize, TryCollect, NZU32}; use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, str::FromStr, time::Duration, }; /// Unique namespace to avoid message replay attacks. const APPLICATION_NAMESPACE: &[u8] = b"_COMMONWARE_EXAMPLES_LOG"; fn main() { // Parse arguments let matches = Command::new("commonware-log") .about("generate secret logs and agree on their hash") .arg( Arg::new("bootstrappers") .long("bootstrappers") .required(false) .value_delimiter(',') .value_parser(value_parser!(String)), ) .arg(Arg::new("me").long("me").required(true)) .arg( Arg::new("participants") .long("participants") .required(true) .value_delimiter(',') .value_parser(value_parser!(u64)) .help("All participants (arbiter and contributors)"), ) .arg(Arg::new("storage-dir").long("storage-dir").required(true)) .get_matches(); // Configure my identity let me = matches .get_one::("me") .expect("Please provide identity"); let parts = me.split('@').collect::>(); if parts.len() != 2 { panic!("Identity not well-formed"); } let key = parts[0].parse::().expect("Key not well-formed"); let signer = ed25519::PrivateKey::from_seed(key); tracing::info!(key = ?signer.public_key(), "loaded signer"); // Configure my port let port = parts[1].parse::().expect("Port not well-formed"); tracing::info!(port, "loaded port"); // Configure allowed peers let participants = matches .get_many::("participants") .expect("Please provide allowed keys") .cloned() .collect::>(); if participants.is_empty() { panic!("Please provide at least one participant"); } let validators: Set<_> = participants .iter() .map(|peer| { let verifier = ed25519::PrivateKey::from_seed(*peer).public_key(); tracing::info!(key = ?verifier, "registered authorized key",); verifier }) .try_collect() .expect("public keys are unique"); // Configure bootstrappers (if provided) let bootstrappers = matches.get_many::("bootstrappers"); let mut bootstrapper_identities = Vec::new(); if let Some(bootstrappers) = bootstrappers { for bootstrapper in bootstrappers { let parts = bootstrapper.split('@').collect::>(); let bootstrapper_key = parts[0] .parse::() .expect("Bootstrapper key not well-formed"); let verifier = ed25519::PrivateKey::from_seed(bootstrapper_key).public_key(); let bootstrapper_address = SocketAddr::from_str(parts[1]).expect("Bootstrapper address not well-formed"); bootstrapper_identities.push((verifier, bootstrapper_address.into())); } } // Configure storage directory let storage_directory = matches .get_one::("storage-dir") .expect("Please provide storage directory"); // Initialize context let runtime_cfg = tokio::Config::new().with_storage_directory(storage_directory); let executor = tokio::Runner::new(runtime_cfg); // Configure network let p2p_cfg = discovery::Config::local( signer.clone(), &union(APPLICATION_NAMESPACE, b"_P2P"), SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port), SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port), bootstrapper_identities.clone(), 1024 * 1024, // 1MB ); // Start context executor.start(async |context| { // Initialize network let (mut network, mut oracle) = discovery::Network::new(context.with_label("network"), p2p_cfg); // Provide authorized peers // // In a real-world scenario, this would be updated as new peer sets are created (like when // the composition of a validator set changes). oracle.update(0, validators.clone()).await; // Register consensus channels // // If you want to maximize the number of views per second, increase the rate limit // for this channel. let (vote_sender, vote_receiver) = network.register( 0, Quota::per_second(NZU32!(10)), 256, // 256 messages in flight ); let (certificate_sender, certificate_receiver) = network.register( 1, Quota::per_second(NZU32!(10)), 256, // 256 messages in flight ); let (resolver_sender, resolver_receiver) = network.register( 2, Quota::per_second(NZU32!(10)), 256, // 256 messages in flight ); // Initialize application let namespace = union(APPLICATION_NAMESPACE, b"_CONSENSUS"); let (application, scheme, reporter, mailbox) = application::Application::new( context.with_label("application"), application::Config { hasher: Sha256::default(), mailbox_size: 1024, participants: validators.clone(), private_key: signer.clone(), }, ); // Initialize consensus let cfg = simplex::Config { scheme, elector: RoundRobin::::default(), blocker: oracle, automaton: mailbox.clone(), relay: mailbox.clone(), reporter: reporter.clone(), namespace, partition: String::from("log"), mailbox_size: 1024, epoch: Epoch::zero(), replay_buffer: NZUsize!(1024 * 1024), write_buffer: NZUsize!(1024 * 1024), leader_timeout: Duration::from_secs(1), notarization_timeout: Duration::from_secs(2), nullify_retry: Duration::from_secs(10), fetch_timeout: Duration::from_secs(1), activity_timeout: ViewDelta::new(10), skip_timeout: ViewDelta::new(5), fetch_concurrent: 32, buffer_pool: PoolRef::new(NZUsize!(16_384), NZUsize!(10_000)), }; let engine = simplex::Engine::new(context.with_label("engine"), cfg); // Start consensus application.start(); network.start(); engine.start( (vote_sender, vote_receiver), (certificate_sender, certificate_receiver), (resolver_sender, resolver_receiver), ); // Block on GUI let gui = gui::Gui::new(context.with_label("gui")); gui.run().await; }); }