diff --git a/.gitignore b/.gitignore index 18dfef8f..4281e36d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ /target *.vscode /chain/assets +/chain/test /types/pkg \ No newline at end of file diff --git a/chain/README.md b/chain/README.md index bdb0f9af..cf4e211d 100644 --- a/chain/README.md +++ b/chain/README.md @@ -11,23 +11,74 @@ A minimal (and wicked fast) blockchain built with the [Commonware Library](https ## Setup +### Local + +_To run this example, you must first install [Rust](https://www.rust-lang.org/tools/install)._ + +#### Create Artifacts + +```bash +cargo run --bin setup -- generate --peers 5 --bootstrappers 1 --worker-threads 3 --log-level info --message-backlog 16384 --mailbox-size 16384 --deque-size 10 --output test local --start-port 3000 +``` + +_If setup succeeds, you should see the following output:_ + +``` +2025-04-30T01:39:42.495691Z INFO setup: generated network key identity=aaadf87ccd821dec083ada0cfaf494ef33c180458fc69e5804b523200d4ef90b469fda59a50504922942f71feffbd6bf +2025-04-30T01:39:42.497013Z INFO setup: wrote peer configuration file path="10cf8d03daca2332213981adee2a4bfffe4a1782bb5cce036c1d5689c6090997.yaml" +2025-04-30T01:39:42.497236Z INFO setup: wrote peer configuration file path="3ed17734da2f5f718f3386cd73d69b93f3d421da6dc216ef8064fc93161cc75a.yaml" +2025-04-30T01:39:42.497455Z INFO setup: wrote peer configuration file path="82f2bdca758f7ecae48ea21678d121938e1c9d98a3853ef402c4612548ce7141.yaml" +2025-04-30T01:39:42.497684Z INFO setup: wrote peer configuration file path="888ba06f969372f0471b9593b054bef8ad0dcdd8690257eee1424b67af2157f2.yaml" +2025-04-30T01:39:42.497899Z INFO setup: wrote peer configuration file path="a77b691b016ae389cf1b4765c2bdc12060548b525cae3986b5953d5da74593e0.yaml" +2025-04-30T01:39:42.497907Z INFO setup: emitting start commands bootstrappers=["888ba06f969372f0471b9593b054bef8ad0dcdd8690257eee1424b67af2157f2"] +cargo run --bin validator -- --peers=/alto/chain/test/peers.yaml --config=/alto/chain/test/10cf8d03daca2332213981adee2a4bfffe4a1782bb5cce036c1d5689c6090997.yaml +cargo run --bin validator -- --peers=/alto/chain/test/peers.yaml --config=/alto/chain/test/3ed17734da2f5f718f3386cd73d69b93f3d421da6dc216ef8064fc93161cc75a.yaml +cargo run --bin validator -- --peers=/alto/chain/test/peers.yaml --config=/alto/chain/test/82f2bdca758f7ecae48ea21678d121938e1c9d98a3853ef402c4612548ce7141.yaml +cargo run --bin validator -- --peers=/alto/chain/test/peers.yaml --config=/alto/chain/test/888ba06f969372f0471b9593b054bef8ad0dcdd8690257eee1424b67af2157f2.yaml +cargo run --bin validator -- --peers=/alto/chain/test/peers.yaml --config=/alto/chain/test/a77b691b016ae389cf1b4765c2bdc12060548b525cae3986b5953d5da74593e0.yaml +``` + +#### Start Validators + +Run the emitted start commands in separate terminals: + +```bash +cargo run --bin validator -- --peers=/alto/chain/test/peers.yaml --config=/alto/chain/test/10cf8d03daca2332213981adee2a4bfffe4a1782bb5cce036c1d5689c6090997.yaml +``` + +_It is necessary to start at least one bootstrapper for any other peers to connect (used to exchange IPs to dial, not as a relay)._ + +#### Debugging + +##### Too Many Open Files + +If you see an error like `unable to append to journal: Runtime(BlobOpenFailed("engine-consensus", "00000000000000ee", Os { code: 24, kind: Uncategorized, message: "Too many open files" }))`, you may need to increase the maximum number of open files. You can do this by running: + +```bash +ulimit -n 65536 +``` + +_MacOS defaults to 256 open files, which is too low for the default settings (where 1 journal file is maintained per recent view)._ + +### Remote + _To run this example, you must first install [Rust](https://www.rust-lang.org/tools/install) and [Docker](https://www.docker.com/get-started/)._ -### Install `commonware-deployer` +#### Install `commonware-deployer` ```bash cargo install commonware-deployer ``` -### Create Deployer Artifacts +#### Create Artifacts ```bash -cargo run --bin setup -- generate --peers 50 --bootstrappers 5 --regions us-west-1,us-east-1,eu-west-1,ap-northeast-1,eu-north-1,ap-south-1,sa-east-1,eu-central-1,ap-northeast-2,ap-southeast-2 --monitoring-instance-type c7g.4xlarge --monitoring-storage-size 100 --instance-type c7g.xlarge --storage-size 40 --worker-threads 3 --log-level info --message-backlog 16384 --mailbox-size 16384 --deque-size 10 --dashboard dashboard.json --output assets +cargo run --bin setup -- generate --peers 50 --bootstrappers 5 --worker-threads 3 --log-level info --message-backlog 16384 --mailbox-size 16384 --deque-size 10 --output assets remote --regions us-west-1,us-east-1,eu-west-1,ap-northeast-1,eu-north-1,ap-south-1,sa-east-1,eu-central-1,ap-northeast-2,ap-southeast-2 --monitoring-instance-type c7g.4xlarge --monitoring-storage-size 100 --instance-type c7g.xlarge --storage-size 40 --dashboard dashboard.json ``` _We use 1 less `worker-threads` than the number of `vCPUs` to leave a core for `blocking-threads`._ -### [Optional] Configure Indexer Upload +#### [Optional] Configure Indexer Upload ```bash cargo run --bin setup -- indexer --count --dir assets --url @@ -35,7 +86,7 @@ cargo run --bin setup -- indexer --count --dir assets --url @@ -43,15 +94,15 @@ cargo run --bin setup -- explorer --dir assets --backend-url _The backend URL should be a WebSocket endpoint (with a `ws://` or `wss://` prefix)._ -### Build Validator Binary +#### Build Validator Binary -#### Build Cross-Platform Compiler +##### Build Cross-Platform Compiler ```bash docker build -t validator-builder . ``` -#### Compile Binary for ARM64 +##### Compile Binary for ARM64 ```bash docker run -it -v ${PWD}/..:/alto validator-builder @@ -59,42 +110,42 @@ docker run -it -v ${PWD}/..:/alto validator-builder _Emitted binary `validator` is placed in `assets`._ -### Deploy Validator Binary +#### Deploy Validator Binary ```bash cd assets deployer ec2 create --config config.yaml ``` -## Monitor Performance on Grafana +#### Monitor Performance on Grafana Visit `http://:3000/d/chain` _This dashboard is only accessible from the IP used to deploy the infrastructure._ -## [Optional] Update Validator Binary +#### [Optional] Update Validator Binary -### Re-Compile Binary for ARM64 +##### Re-Compile Binary for ARM64 ```bash docker run -it -v ${PWD}/..:/alto validator-builder ``` -### Restart Validator Binary on EC2 Instances +##### Restart Validator Binary on EC2 Instances ```bash deployer ec2 update --config config.yaml ``` -## Destroy Infrastructure +#### Destroy Infrastructure ```bash deployer ec2 destroy --config config.yaml ``` -## Debugging +#### Debugging -### Missing AWS Credentials +##### Missing AWS Credentials If `commonware-deployer` can't detect your AWS credentials, you'll see a "Request has expired." error: @@ -103,7 +154,7 @@ If `commonware-deployer` can't detect your AWS credentials, you'll see a "Reques 2025-03-05T01:36:48.268330Z ERROR deployer: failed to create EC2 deployment error=AwsEc2(Unhandled(Unhandled { source: ErrorMetadata { code: Some("RequestExpired"), message: Some("Request has expired."), extras: Some({"aws_request_id": "006f6b92-4965-470d-8eac-7c9644744bdf"}) }, meta: ErrorMetadata { code: Some("RequestExpired"), message: Some("Request has expired."), extras: Some({"aws_request_id": "006f6b92-4965-470d-8eac-7c9644744bdf"}) } })) ``` -### EC2 Throttling +##### EC2 Throttling EC2 instances may throttle network traffic if a workload exceeds the allocation for a particular instance type. To check if an instance is throttled, SSH into the instance and run: diff --git a/chain/src/bin/setup.rs b/chain/src/bin/setup.rs index 83e4f252..cd795f06 100644 --- a/chain/src/bin/setup.rs +++ b/chain/src/bin/setup.rs @@ -1,4 +1,4 @@ -use alto_chain::Config; +use alto_chain::{Config, Peers}; use clap::{value_parser, Arg, ArgMatches, Command}; use commonware_codec::{Decode, DecodeExt, Encode}; use commonware_cryptography::{ @@ -6,10 +6,15 @@ use commonware_cryptography::{ ed25519::PublicKey, Ed25519, Signer, }; -use commonware_deployer::ec2; +use commonware_deployer::ec2::{self, METRICS_PORT}; use commonware_utils::{from_hex_formatted, hex, quorum}; use rand::{rngs::OsRng, seq::IteratorRandom}; -use std::{collections::BTreeMap, fs, ops::AddAssign}; +use std::{ + collections::{BTreeMap, HashMap}, + fs, + net::{IpAddr, Ipv4Addr, SocketAddr}, + ops::AddAssign, +}; use tracing::{error, info}; use uuid::Uuid; @@ -27,7 +32,7 @@ fn main() { .about("Manage configuration files for an alto chain.") .subcommand( Command::new("generate") - .about("Generate configuration files for an alto chain") + .about("Generate configuration files for an alto chain deploy") .arg( Arg::new("peers") .long("peers") @@ -40,37 +45,6 @@ fn main() { .required(true) .value_parser(value_parser!(usize)), ) - .arg( - Arg::new("regions") - .long("regions") - .required(true) - .value_delimiter(',') - .value_parser(value_parser!(String)), - ) - .arg( - Arg::new("instance_type") - .long("instance-type") - .required(true) - .value_parser(value_parser!(String)), - ) - .arg( - Arg::new("storage_size") - .long("storage-size") - .required(true) - .value_parser(value_parser!(i32)), - ) - .arg( - Arg::new("monitoring_instance_type") - .long("monitoring-instance-type") - .required(true) - .value_parser(value_parser!(String)), - ) - .arg( - Arg::new("monitoring_storage_size") - .long("monitoring-storage-size") - .required(true) - .value_parser(value_parser!(i32)), - ) .arg( Arg::new("worker_threads") .long("worker-threads") @@ -101,17 +75,60 @@ fn main() { .required(true) .value_parser(value_parser!(usize)), ) - .arg( - Arg::new("dashboard") - .long("dashboard") - .required(true) - .value_parser(value_parser!(String)), - ) .arg( Arg::new("output") .long("output") .required(true) .value_parser(value_parser!(String)), + ) + .subcommand(Command::new("local").about("Generate configuration files for local deployment") + .arg( + Arg::new("start_port") + .long("start-port") + .required(true) + .value_parser(value_parser!(u16)), + ) + ) + .subcommand( + Command::new("remote") + .about("Generate configuration files for `commonware-deployer`-managed deployment") + .arg( + Arg::new("regions") + .long("regions") + .required(true) + .value_delimiter(',') + .value_parser(value_parser!(String)), + ) + .arg( + Arg::new("instance_type") + .long("instance-type") + .required(true) + .value_parser(value_parser!(String)), + ) + .arg( + Arg::new("storage_size") + .long("storage-size") + .required(true) + .value_parser(value_parser!(i32)), + ) + .arg( + Arg::new("monitoring_instance_type") + .long("monitoring-instance-type") + .required(true) + .value_parser(value_parser!(String)), + ) + .arg( + Arg::new("monitoring_storage_size") + .long("monitoring-storage-size") + .required(true) + .value_parser(value_parser!(i32)), + ) + .arg( + Arg::new("dashboard") + .long("dashboard") + .required(true) + .value_parser(value_parser!(String)), + ), ), ) .subcommand( @@ -158,7 +175,44 @@ fn main() { // Handle subcommands match matches.subcommand() { - Some(("generate", sub_matches)) => generate(sub_matches), + Some(("generate", sub_matches)) => { + let peers = *sub_matches.get_one::("peers").unwrap(); + let bootstrappers = *sub_matches.get_one::("bootstrappers").unwrap(); + let worker_threads = *sub_matches.get_one::("worker_threads").unwrap(); + let log_level = sub_matches.get_one::("log_level").unwrap().clone(); + let message_backlog = *sub_matches.get_one::("message_backlog").unwrap(); + let mailbox_size = *sub_matches.get_one::("mailbox_size").unwrap(); + let deque_size = *sub_matches.get_one::("deque_size").unwrap(); + let output = sub_matches.get_one::("output").unwrap().clone(); + match sub_matches.subcommand() { + Some(("local", sub_matches)) => generate_local( + sub_matches, + peers, + bootstrappers, + worker_threads, + log_level, + message_backlog, + mailbox_size, + deque_size, + output, + ), + Some(("remote", sub_matches)) => generate_remote( + sub_matches, + peers, + bootstrappers, + worker_threads, + log_level, + message_backlog, + mailbox_size, + deque_size, + output, + ), + _ => { + eprintln!("Invalid subcommand. Use 'local' or 'remote'."); + std::process::exit(1); + } + } + } Some(("indexer", sub_matches)) => indexer(sub_matches), Some(("explorer", sub_matches)) => explorer(sub_matches), _ => { @@ -168,10 +222,138 @@ fn main() { } } -fn generate(sub_matches: &ArgMatches) { +#[allow(clippy::too_many_arguments)] +fn generate_local( + sub_matches: &ArgMatches, + peers: usize, + bootstrappers: usize, + worker_threads: usize, + log_level: String, + message_backlog: usize, + mailbox_size: usize, + deque_size: usize, + output: String, +) { + // Extract arguments + let start_port = *sub_matches.get_one::("start_port").unwrap(); + + // Construct output path + let raw_current_dir = std::env::current_dir().unwrap(); + let current_dir = raw_current_dir.to_str().unwrap(); + let output = format!("{}/{}", current_dir, output); + let storage_output = format!("{}/storage", output); + + // Check if output directory exists + if fs::metadata(&output).is_ok() { + error!("output directory already exists: {}", output); + std::process::exit(1); + } + + // Generate peers + assert!( + bootstrappers <= peers, + "bootstrappers must be less than or equal to peers" + ); + let mut peer_schemes = (0..peers) + .map(|_| Ed25519::new(&mut OsRng)) + .collect::>(); + peer_schemes.sort_by_key(|scheme| scheme.public_key()); + let allowed_peers: Vec = peer_schemes + .iter() + .map(|scheme| scheme.public_key().to_string()) + .collect(); + let bootstrappers = allowed_peers + .iter() + .choose_multiple(&mut OsRng, bootstrappers) + .into_iter() + .cloned() + .collect::>(); + + // Generate consensus key + let peers_u32 = peers as u32; + let threshold = quorum(peers_u32); + let (identity, shares) = ops::generate_shares(&mut OsRng, None, peers_u32, threshold); + info!(identity = ?poly::public(&identity), "generated network key"); + + // Generate instance configurations + let mut port = start_port; + let mut addresses = HashMap::new(); + let mut configurations = Vec::new(); + for (scheme, share) in peer_schemes.iter().zip(shares.iter()) { + // Create peer config + let name = scheme.public_key().to_string(); + addresses.insert( + name.clone(), + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port), + ); + let peer_config_file = format!("{}.yaml", name); + let directory = format!("{}/{}", storage_output, name); + let peer_config = Config { + private_key: scheme.private_key().to_string(), + share: hex(&share.encode()), + identity: hex(&identity.encode()), + + port, + metrics_port: None, + directory, + worker_threads, + log_level: log_level.clone(), + + allowed_peers: allowed_peers.clone(), + bootstrappers: bootstrappers.clone(), + + message_backlog, + mailbox_size, + deque_size, + + indexer: None, + }; + configurations.push((peer_config_file.clone(), peer_config)); + port += 1; + } + + // Create required output directories + fs::create_dir_all(&output).unwrap(); + fs::create_dir_all(&storage_output).unwrap(); + + // Write peers file + let peers_path = format!("{}/peers.yaml", output); + let file = fs::File::create(&peers_path).unwrap(); + serde_yaml::to_writer(file, &Peers { addresses }).unwrap(); + + // Write configuration files + for (peer_config_file, peer_config) in &configurations { + let path = format!("{}/{}", output, peer_config_file); + let file = fs::File::create(&path).unwrap(); + serde_yaml::to_writer(file, peer_config).unwrap(); + info!(path = peer_config_file, "wrote peer configuration file"); + } + + // Emit start commands + info!(?bootstrappers, "emitting start commands"); + for (peer_config_file, _) in configurations { + let path = format!("{}/{}", output, peer_config_file); + let command = format!( + "cargo run --bin {} -- --peers={} --config={}", + BINARY_NAME, peers_path, path + ); + println!("{}", command); + } +} + +#[allow(clippy::too_many_arguments)] +fn generate_remote( + sub_matches: &ArgMatches, + peers: usize, + bootstrappers: usize, + worker_threads: usize, + log_level: String, + message_backlog: usize, + mailbox_size: usize, + deque_size: usize, + output: String, +) { // Extract arguments - let peers = *sub_matches.get_one::("peers").unwrap(); - let bootstrappers = *sub_matches.get_one::("bootstrappers").unwrap(); let regions = sub_matches .get_many::("regions") .unwrap() @@ -189,13 +371,7 @@ fn generate(sub_matches: &ArgMatches) { let monitoring_storage_size = *sub_matches .get_one::("monitoring_storage_size") .unwrap(); - let worker_threads = *sub_matches.get_one::("worker_threads").unwrap(); - let log_level = sub_matches.get_one::("log_level").unwrap().clone(); - let message_backlog = *sub_matches.get_one::("message_backlog").unwrap(); - let mailbox_size = *sub_matches.get_one::("mailbox_size").unwrap(); - let deque_size = *sub_matches.get_one::("deque_size").unwrap(); let dashboard = sub_matches.get_one::("dashboard").unwrap().clone(); - let output = sub_matches.get_one::("output").unwrap().clone(); // Construct output path let raw_current_dir = std::env::current_dir().unwrap(); @@ -255,6 +431,7 @@ fn generate(sub_matches: &ArgMatches) { identity: hex(&identity.encode()), port: PORT, + metrics_port: Some(METRICS_PORT), directory: "/home/ubuntu/data".to_string(), worker_threads, log_level: log_level.clone(), diff --git a/chain/src/bin/validator.rs b/chain/src/bin/validator.rs index 78bf45f8..f7ae54ac 100644 --- a/chain/src/bin/validator.rs +++ b/chain/src/bin/validator.rs @@ -1,4 +1,4 @@ -use alto_chain::{engine, Config}; +use alto_chain::{engine, Config, Peers}; use alto_client::Client; use alto_types::NAMESPACE; use clap::{Arg, Command}; @@ -8,7 +8,7 @@ use commonware_cryptography::{ ed25519::{PrivateKey, PublicKey}, Ed25519, Signer, }; -use commonware_deployer::ec2::{Hosts, METRICS_PORT}; +use commonware_deployer::ec2::Hosts; use commonware_p2p::authenticated; use commonware_runtime::{tokio, Metrics, Runner}; use commonware_utils::{from_hex_formatted, quorum, union_unique}; @@ -44,14 +44,27 @@ fn main() { // Parse arguments let matches = Command::new("validator") .about("Validator for an alto chain.") - .arg(Arg::new("hosts").long("hosts").required(true)) + .arg(Arg::new("hosts").long("hosts").required(false)) + .arg(Arg::new("peers").long("peers").required(false)) .arg(Arg::new("config").long("config").required(true)) .get_matches(); + // Load ip file + let hosts_file = matches.get_one::("hosts"); + let peers_file = matches.get_one::("peers"); + assert!( + hosts_file.is_some() || peers_file.is_some(), + "Either --hosts or --peers must be provided" + ); + // Load config let config_file = matches.get_one::("config").unwrap(); let config_file = std::fs::read_to_string(config_file).expect("Could not read config file"); let config: Config = serde_yaml::from_str(&config_file).expect("Could not parse config file"); + let key = from_hex_formatted(&config.private_key).expect("Could not parse private key"); + let key = PrivateKey::decode(key.as_ref()).expect("Private key is invalid"); + let signer = ::from(key).expect("Could not create signer"); + let public_key = signer.public_key(); // Initialize runtime let cfg = tokio::Config { @@ -66,37 +79,88 @@ fn main() { // Start runtime executor.start(|context| async move { // Configure telemetry + // + // If we are using a hosts file (e.g. on EC2), we want to use the telemetry + // server to send metrics to. Otherwise, we just want to log to stdout. let log_level = Level::from_str(&config.log_level).expect("Invalid log level"); - tokio::telemetry::init( - context.with_label("telemetry"), - log_level, - Some(SocketAddr::new( - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - METRICS_PORT, - )), - None, - ); + if hosts_file.is_some() { + let metrics_socket = config + .metrics_port + .map(|port| SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port)); + tokio::telemetry::init( + context.with_label("telemetry"), + log_level, + metrics_socket, + None, + ); + } else { + // TODO: add a dedicated CLI telemetry helper for CLI-optimized logging + // while still offering metrics (https://github.com/commonwarexyz/monorepo/issues/864) + tracing_subscriber::fmt().with_max_level(log_level).init(); + } // Load peers - let hosts_file = matches.get_one::("hosts").unwrap(); - let hosts_file = std::fs::read_to_string(hosts_file).expect("Could not read hosts file"); - let hosts: Hosts = serde_yaml::from_str(&hosts_file).expect("Could not parse peers file"); - let peers: HashMap = hosts - .hosts - .into_iter() - .map(|peer| { - let key = from_hex_formatted(&peer.name).expect("Could not parse peer key"); - let key = PublicKey::decode(key.as_ref()).expect("Peer key is invalid"); - (key, peer.ip) - }) - .collect(); + let (ip, peers, bootstrappers) = if let Some(hosts_file) = hosts_file { + let hosts_file = std::fs::read_to_string(hosts_file).unwrap(); + let hosts: Hosts = + serde_yaml::from_str(&hosts_file).expect("Could not parse peers file"); + let peers: HashMap = hosts + .hosts + .into_iter() + .map(|peer| { + let key = from_hex_formatted(&peer.name).expect("Could not parse peer key"); + let key = PublicKey::decode(key.as_ref()).expect("Peer key is invalid"); + (key, peer.ip) + }) + .collect(); + + let peer_keys = peers.keys().cloned().collect::>(); + let mut bootstrappers = Vec::new(); + for bootstrapper in &config.bootstrappers { + let key = + from_hex_formatted(bootstrapper).expect("Could not parse bootstrapper key"); + let key = PublicKey::decode(key.as_ref()).expect("Bootstrapper key is invalid"); + let ip = peers.get(&key).expect("Could not find bootstrapper in IPs"); + let bootstrapper_socket = format!("{}:{}", ip, config.port); + let bootstrapper_socket = SocketAddr::from_str(&bootstrapper_socket) + .expect("Could not parse bootstrapper socket"); + bootstrappers.push((key, bootstrapper_socket)); + } + let ip = peers.get(&public_key).expect("Could not find self in IPs"); + (*ip, peer_keys, bootstrappers) + } else { + let peers_file = std::fs::read_to_string(peers_file.unwrap()).unwrap(); + let peers: Peers = + serde_yaml::from_str(&peers_file).expect("Could not parse peers file"); + let peers: HashMap = peers + .addresses + .into_iter() + .map(|peer| { + let key = from_hex_formatted(&peer.0).expect("Could not parse peer key"); + let key = PublicKey::decode(key.as_ref()).expect("Peer key is invalid"); + (key, peer.1) + }) + .collect(); + + let peer_keys = peers.keys().cloned().collect::>(); + let mut bootstrappers = Vec::new(); + for bootstrapper in &config.bootstrappers { + let key = + from_hex_formatted(bootstrapper).expect("Could not parse bootstrapper key"); + let key = PublicKey::decode(key.as_ref()).expect("Bootstrapper key is invalid"); + let socket = peers.get(&key).expect("Could not find bootstrapper in IPs"); + bootstrappers.push((key, *socket)); + } + let ip = peers + .get(&public_key) + .expect("Could not find self in IPs") + .ip(); + (ip, peer_keys, bootstrappers) + }; info!(peers = peers.len(), "loaded peers"); let peers_u32 = peers.len() as u32; // Parse config - let key = from_hex_formatted(&config.private_key).expect("Could not parse private key"); - let key = PrivateKey::decode(key.as_ref()).expect("Private key is invalid"); - let signer = ::from(key).expect("Could not create signer"); let share = from_hex_formatted(&config.share).expect("Could not parse share"); let share = group::Share::decode(share.as_ref()).expect("Share is invalid"); let threshold = quorum(peers_u32); @@ -104,8 +168,6 @@ fn main() { let identity = poly::Public::decode_cfg(identity.as_ref(), &(threshold as usize)) .expect("Identity is invalid"); let identity_public = *poly::public(&identity); - let public_key = signer.public_key(); - let ip = peers.get(&public_key).expect("Could not find self in IPs"); info!( ?public_key, ?identity_public, @@ -114,26 +176,13 @@ fn main() { "loaded config" ); - // Configure peers and bootstrappers - let peer_keys = peers.keys().cloned().collect::>(); - let mut bootstrappers = Vec::new(); - for bootstrapper in &config.bootstrappers { - let key = from_hex_formatted(bootstrapper).expect("Could not parse bootstrapper key"); - let key = PublicKey::decode(key.as_ref()).expect("Bootstrapper key is invalid"); - let ip = peers.get(&key).expect("Could not find bootstrapper in IPs"); - let bootstrapper_socket = format!("{}:{}", ip, config.port); - let bootstrapper_socket = SocketAddr::from_str(&bootstrapper_socket) - .expect("Could not parse bootstrapper socket"); - bootstrappers.push((key, bootstrapper_socket)); - } - // Configure network let p2p_namespace = union_unique(NAMESPACE, b"_P2P"); let mut p2p_cfg = authenticated::Config::aggressive( signer.clone(), &p2p_namespace, SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), config.port), - SocketAddr::new(*ip, config.port), + SocketAddr::new(ip, config.port), bootstrappers, MAX_MESSAGE_SIZE, ); @@ -144,7 +193,7 @@ fn main() { authenticated::Network::new(context.with_label("network"), p2p_cfg); // Provide authorized peers - oracle.register(0, peer_keys.clone()).await; + oracle.register(0, peers.clone()).await; // Register voter channel let voter_limit = Quota::per_second(NonZeroU32::new(128).unwrap()); @@ -192,7 +241,7 @@ fn main() { signer, identity, share, - participants: peer_keys, + participants: peers, mailbox_size: config.mailbox_size, deque_size: config.deque_size, backfill_quota: backfiller_limit, diff --git a/chain/src/lib.rs b/chain/src/lib.rs index daf4331d..65146b60 100644 --- a/chain/src/lib.rs +++ b/chain/src/lib.rs @@ -2,7 +2,7 @@ use alto_types::{Finalized, Notarized}; use commonware_consensus::threshold_simplex::types::Seed; use commonware_cryptography::bls12381; use serde::{Deserialize, Serialize}; -use std::future::Future; +use std::{collections::HashMap, future::Future, net::SocketAddr}; pub mod actors; pub mod engine; @@ -56,6 +56,7 @@ impl Indexer for alto_client::Client { } } +/// Configuration for the engine. #[derive(Deserialize, Serialize)] pub struct Config { pub private_key: String, @@ -63,6 +64,7 @@ pub struct Config { pub identity: String, pub port: u16, + pub metrics_port: Option, pub directory: String, pub worker_threads: usize, pub log_level: String, @@ -77,6 +79,14 @@ pub struct Config { pub indexer: Option, } +/// A list of peers provided when a validator is run locally. +/// +/// When run remotely, [commonware_deployer::ec2::Hosts] is used instead. +#[derive(Deserialize, Serialize)] +pub struct Peers { + pub addresses: HashMap, +} + #[cfg(test)] mod tests { use super::*;