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 as _};
use commonware_parallel::Sequential;
use commonware_runtime::{buffer::paged::CacheRef, tokio, Metrics, Quota, Runner};
use commonware_utils::{ordered::Set, union, NZUsize, TryCollect, NZU16, NZU32};
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
str::FromStr,
time::Duration,
};
const APPLICATION_NAMESPACE: &[u8] = b"_COMMONWARE_EXAMPLES_LOG";
fn main() {
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();
let me = matches
.get_one::<String>("me")
.expect("Please provide identity");
let parts = me.split('@').collect::<Vec<&str>>();
if parts.len() != 2 {
panic!("Identity not well-formed");
}
let key = parts[0].parse::<u64>().expect("Key not well-formed");
let signer = ed25519::PrivateKey::from_seed(key);
tracing::info!(key = ?signer.public_key(), "loaded signer");
let port = parts[1].parse::<u16>().expect("Port not well-formed");
tracing::info!(port, "loaded port");
let participants = matches
.get_many::<u64>("participants")
.expect("Please provide allowed keys")
.cloned()
.collect::<Vec<_>>();
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");
let bootstrappers = matches.get_many::<String>("bootstrappers");
let mut bootstrapper_identities = Vec::new();
if let Some(bootstrappers) = bootstrappers {
for bootstrapper in bootstrappers {
let parts = bootstrapper.split('@').collect::<Vec<&str>>();
let bootstrapper_key = parts[0]
.parse::<u64>()
.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()));
}
}
let storage_directory = matches
.get_one::<String>("storage-dir")
.expect("Please provide storage directory");
let runtime_cfg = tokio::Config::new().with_storage_directory(storage_directory);
let executor = tokio::Runner::new(runtime_cfg);
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, );
executor.start(async |context| {
let (mut network, mut oracle) =
discovery::Network::new(context.with_label("network"), p2p_cfg);
oracle.track(0, validators.clone()).await;
let (vote_sender, vote_receiver) = network.register(
0,
Quota::per_second(NZU32!(10)),
256, );
let (certificate_sender, certificate_receiver) = network.register(
1,
Quota::per_second(NZU32!(10)),
256, );
let (resolver_sender, resolver_receiver) = network.register(
2,
Quota::per_second(NZU32!(10)),
256, );
let namespace = union(APPLICATION_NAMESPACE, b"_CONSENSUS");
let scheme = application::Scheme::signer(&namespace, validators.clone(), signer.clone())
.expect("private key must be in participants");
let (application, scheme, reporter, mailbox) = application::Application::new(
context.with_label("application"),
application::Config {
hasher: Sha256::default(),
scheme,
mailbox_size: 1024,
},
);
let cfg = simplex::Config {
scheme,
elector: RoundRobin::<Sha256>::default(),
blocker: oracle,
automaton: mailbox.clone(),
relay: mailbox.clone(),
reporter: reporter.clone(),
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),
certification_timeout: Duration::from_secs(2),
timeout_retry: Duration::from_secs(10),
fetch_timeout: Duration::from_secs(1),
activity_timeout: ViewDelta::new(10),
skip_timeout: ViewDelta::new(5),
fetch_concurrent: 32,
page_cache: CacheRef::from_pooler(&context, NZU16!(16_384), NZUsize!(10_000)),
strategy: Sequential,
forwarding: simplex::ForwardingPolicy::Disabled,
};
let engine = simplex::Engine::new(context.with_label("engine"), cfg);
application.start();
network.start();
engine.start(
(vote_sender, vote_receiver),
(certificate_sender, certificate_receiver),
(resolver_sender, resolver_receiver),
);
let gui = gui::Gui::new(context.with_label("gui"));
gui.run().await;
});
}