1use std::{net::SocketAddr, time::Duration};
8
9use clap::Parser;
10use futures::{Future, FutureExt};
11use hotshot_types::{
12 PeerConfig, ValidatorConfig,
13 network::{NetworkConfig, NetworkConfigSource},
14 traits::node_implementation::NodeType,
15};
16use http_client::{Client, Url, error::ClientErr};
17use libp2p_identity::PeerId;
18use multiaddr::Multiaddr;
19use tokio::time::sleep;
20use tracing::{info, instrument};
21use vbs::BinarySerializer;
22
23use crate::OrchestratorVersion;
24
25pub struct OrchestratorClient {
27 pub client: Client<ClientErr, OrchestratorVersion>,
29}
30
31#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default, PartialEq)]
33pub struct BenchResults {
34 pub partial_results: String,
36 pub avg_latency_in_sec: i64,
38 pub num_latency: i64,
40 pub minimum_latency_in_sec: i64,
42 pub maximum_latency_in_sec: i64,
44 pub throughput_bytes_per_sec: u64,
46 pub total_transactions_committed: u64,
48 pub transaction_size_in_bytes: u64,
50 pub total_time_elapsed_in_sec: u64,
52 pub total_num_views: usize,
54 pub failed_num_views: usize,
56 pub committee_type: String,
58}
59
60impl BenchResults {
61 pub fn printout(&self) {
63 println!("=====================");
64 println!("{0} Benchmark results:", self.partial_results);
65 println!("Committee type: {}", self.committee_type);
66 println!(
67 "Average latency: {} seconds, Minimum latency: {} seconds, Maximum latency: {} seconds",
68 self.avg_latency_in_sec, self.minimum_latency_in_sec, self.maximum_latency_in_sec
69 );
70 println!("Throughput: {} bytes/sec", self.throughput_bytes_per_sec);
71 println!(
72 "Total transactions committed: {}",
73 self.total_transactions_committed
74 );
75 println!(
76 "Total number of views: {}, Failed number of views: {}",
77 self.total_num_views, self.failed_num_views
78 );
79 println!("=====================");
80 }
81}
82
83#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default, PartialEq)]
85pub struct BenchResultsDownloadConfig {
86 pub commit_sha: String,
89 pub total_nodes: usize,
91 pub da_committee_size: usize,
93 pub fixed_leader_for_gpuvid: usize,
95 pub transactions_per_round: usize,
97 pub transaction_size: u64,
99 pub rounds: usize,
101
102 pub partial_results: String,
108 pub avg_latency_in_sec: i64,
110 pub minimum_latency_in_sec: i64,
112 pub maximum_latency_in_sec: i64,
114 pub throughput_bytes_per_sec: u64,
116 pub total_transactions_committed: u64,
118 pub total_time_elapsed_in_sec: u64,
120 pub total_num_views: usize,
122 pub failed_num_views: usize,
124 pub committee_type: String,
126}
127
128#[derive(Parser, Debug, Clone)]
131#[command(
132 name = "Multi-machine consensus",
133 about = "Simulates consensus among multiple machines"
134)]
135pub struct ValidatorArgs {
137 pub url: Url,
139 pub advertise_address: Option<String>,
141 pub builder_address: Option<SocketAddr>,
143 #[arg(short, long)]
146 pub network_config_file: Option<String>,
147}
148
149#[derive(Parser, Debug, Clone)]
151pub struct MultiValidatorArgs {
152 pub num_nodes: u16,
154 pub url: Url,
156 pub advertise_address: Option<String>,
158 #[arg(short, long)]
161 pub network_config_file: Option<String>,
162}
163
164pub async fn get_complete_config<TYPES: NodeType>(
170 client: &OrchestratorClient,
171 mut validator_config: ValidatorConfig<TYPES>,
172 libp2p_advertise_address: Option<Multiaddr>,
173 libp2p_public_key: Option<PeerId>,
174) -> anyhow::Result<(
175 NetworkConfig<TYPES>,
176 ValidatorConfig<TYPES>,
177 NetworkConfigSource,
178)> {
179 let run_config: NetworkConfig<TYPES> = client
181 .post_and_wait_all_public_keys::<TYPES>(
182 &mut validator_config,
183 libp2p_advertise_address,
184 libp2p_public_key,
185 )
186 .await;
187
188 info!(
189 "Retrieved config; our node index is {}. DA committee member: {}",
190 run_config.node_index, validator_config.is_da
191 );
192 Ok((
193 run_config,
194 validator_config,
195 NetworkConfigSource::Orchestrator,
196 ))
197}
198
199impl ValidatorArgs {
200 #[must_use]
224 pub fn from_multi_args(multi_args: MultiValidatorArgs, node_index: u16) -> Self {
225 Self {
226 url: multi_args.url,
227 advertise_address: multi_args.advertise_address,
228 builder_address: None,
229 network_config_file: multi_args
230 .network_config_file
231 .map(|s| format!("{s}-{node_index}")),
232 }
233 }
234}
235
236impl OrchestratorClient {
237 #[must_use]
239 pub fn new(url: Url) -> Self {
240 let client = Client::<ClientErr, OrchestratorVersion>::new(url);
241 OrchestratorClient { client }
243 }
244
245 #[allow(clippy::type_complexity)]
256 pub async fn get_config_without_peer<TYPES: NodeType>(
257 &self,
258 libp2p_advertise_address: Option<Multiaddr>,
259 libp2p_public_key: Option<PeerId>,
260 ) -> anyhow::Result<NetworkConfig<TYPES>> {
261 let request_body = vbs::Serializer::<OrchestratorVersion>::serialize(&(
263 libp2p_advertise_address,
264 libp2p_public_key,
265 ))?;
266
267 let identity = |client: Client<ClientErr, OrchestratorVersion>| {
268 let request_body = request_body.clone();
270 async move {
271 let node_index: Result<u16, ClientErr> = client
272 .post("api/identity")
273 .body_binary(&request_body)
274 .expect("failed to set request body")
275 .send()
276 .await;
277
278 node_index
279 }
280 .boxed()
281 };
282 let node_index = self.wait_for_fn_from_orchestrator(identity).await;
283
284 let f = |client: Client<ClientErr, OrchestratorVersion>| {
286 async move {
287 let config: Result<NetworkConfig<TYPES>, ClientErr> = client
288 .post(&format!("api/config/{node_index}"))
289 .send()
290 .await;
291 config
292 }
293 .boxed()
294 };
295
296 let mut config = self.wait_for_fn_from_orchestrator(f).await;
297 config.node_index = From::<u16>::from(node_index);
298
299 Ok(config)
300 }
301
302 #[instrument(skip_all, name = "orchestrator node index for validator config")]
307 pub async fn get_node_index_for_init_validator_config(&self) -> u16 {
308 let cur_node_index = |client: Client<ClientErr, OrchestratorVersion>| {
309 async move {
310 let cur_node_index: Result<u16, ClientErr> = client
311 .post("api/get_tmp_node_index")
312 .send()
313 .await
314 .inspect_err(|err| tracing::error!("{err}"));
315
316 cur_node_index
317 }
318 .boxed()
319 };
320 self.wait_for_fn_from_orchestrator(cur_node_index).await
321 }
322
323 #[instrument(skip_all, name = "orchestrator config")]
328 pub async fn get_config_after_collection<TYPES: NodeType>(&self) -> NetworkConfig<TYPES> {
329 let get_config_after_collection = |client: Client<ClientErr, OrchestratorVersion>| {
331 async move {
332 let result = client
333 .post("api/post_config_after_peer_collected")
334 .send()
335 .await;
336
337 if let Err(ref err) = result {
338 tracing::error!("{err}");
339 }
340
341 result
342 }
343 .boxed()
344 };
345
346 self.wait_for_fn_from_orchestrator(get_config_after_collection)
348 .await
349 }
350
351 pub async fn post_builder_addresses(&self, addresses: Vec<Url>) {
356 let send_builder_f = |client: Client<ClientErr, OrchestratorVersion>| {
357 let request_body = vbs::Serializer::<OrchestratorVersion>::serialize(&addresses)
358 .expect("Failed to serialize request");
359
360 async move {
361 let result: Result<_, ClientErr> = client
362 .post("api/builder")
363 .body_binary(&request_body)
364 .unwrap()
365 .send()
366 .await
367 .inspect_err(|err| tracing::error!("{err}"));
368 result
369 }
370 .boxed()
371 };
372 self.wait_for_fn_from_orchestrator::<_, _, ()>(send_builder_f)
373 .await;
374 }
375
376 pub async fn get_builder_addresses(&self) -> Vec<Url> {
378 let get_builder = |client: Client<ClientErr, OrchestratorVersion>| {
380 async move {
381 let result = client.get("api/builders").send().await;
382
383 if let Err(ref err) = result {
384 tracing::error!("{err}");
385 }
386
387 result
388 }
389 .boxed()
390 };
391
392 self.wait_for_fn_from_orchestrator(get_builder).await
394 }
395
396 #[instrument(skip(self), name = "orchestrator public keys")]
402 pub async fn post_and_wait_all_public_keys<TYPES: NodeType>(
403 &self,
404 validator_config: &mut ValidatorConfig<TYPES>,
405 libp2p_advertise_address: Option<Multiaddr>,
406 libp2p_public_key: Option<PeerId>,
407 ) -> NetworkConfig<TYPES> {
408 let pubkey: Vec<u8> =
409 PeerConfig::<TYPES>::to_bytes(&validator_config.public_config()).clone();
410 let da_requested: bool = validator_config.is_da;
411
412 let request_body = vbs::Serializer::<OrchestratorVersion>::serialize(&(
414 pubkey,
415 libp2p_advertise_address,
416 libp2p_public_key,
417 ))
418 .expect("failed to serialize request");
419
420 let (node_index, is_da): (u64, bool) = loop {
422 let result = self
423 .client
424 .post(&format!("api/pubkey/{da_requested}"))
425 .body_binary(&request_body)
426 .expect("Failed to form request")
427 .send()
428 .await
429 .inspect_err(|err| tracing::error!("{err}"));
430
431 if let Ok((index, is_da)) = result {
432 break (index, is_da);
433 }
434
435 sleep(Duration::from_millis(250)).await;
436 };
437
438 validator_config.is_da = is_da;
439
440 let wait_for_all_nodes_pub_key = |client: Client<ClientErr, OrchestratorVersion>| {
442 async move {
443 client
444 .get("api/peer_pub_ready")
445 .send()
446 .await
447 .inspect_err(|err| tracing::error!("{err}"))
448 }
449 .boxed()
450 };
451 self.wait_for_fn_from_orchestrator::<_, _, ()>(wait_for_all_nodes_pub_key)
452 .await;
453
454 let mut network_config = self.get_config_after_collection().await;
455
456 network_config.node_index = node_index;
457
458 network_config
459 }
460
461 #[instrument(skip(self), name = "orchestrator ready signal")]
466 pub async fn wait_for_all_nodes_ready(&self, peer_config: Vec<u8>) -> bool {
467 let send_ready_f = |client: Client<ClientErr, OrchestratorVersion>| {
468 let pk = peer_config.clone();
469 async move {
470 let result: Result<_, ClientErr> = client
471 .post("api/ready")
472 .body_binary(&pk)
473 .unwrap()
474 .send()
475 .await
476 .inspect_err(|err| tracing::error!("{err}"));
477 result
478 }
479 .boxed()
480 };
481 self.wait_for_fn_from_orchestrator::<_, _, ()>(send_ready_f)
482 .await;
483
484 let wait_for_all_nodes_ready_f = |client: Client<ClientErr, OrchestratorVersion>| {
485 async move { client.get("api/start").send().await }.boxed()
486 };
487 self.wait_for_fn_from_orchestrator(wait_for_all_nodes_ready_f)
488 .await
489 }
490
491 #[instrument(skip_all, name = "orchestrator metrics")]
495 pub async fn post_bench_results(&self, bench_results: BenchResults) {
496 let _send_metrics_f: Result<(), ClientErr> = self
497 .client
498 .post("api/results")
499 .body_json(&bench_results)
500 .unwrap()
501 .send()
502 .await
503 .inspect_err(|err| tracing::warn!("{err}"));
504 }
505
506 #[instrument(skip_all, name = "waiting for orchestrator")]
509 async fn wait_for_fn_from_orchestrator<F, Fut, GEN>(&self, f: F) -> GEN
510 where
511 F: Fn(Client<ClientErr, OrchestratorVersion>) -> Fut,
512 Fut: Future<Output = Result<GEN, ClientErr>>,
513 {
514 loop {
515 let client = self.client.clone();
516 let res = f(client).await;
517 match res {
518 Ok(x) => break x,
519 Err(err) => {
520 tracing::info!("{err}");
521 sleep(Duration::from_millis(250)).await;
522 },
523 }
524 }
525 }
526}