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 tokio::time::sleep;
18use tracing::{info, instrument};
19use vbs::BinarySerializer;
20
21use crate::OrchestratorVersion;
22
23pub struct OrchestratorClient {
25 pub client: Client<ClientErr, OrchestratorVersion>,
27}
28
29#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default, PartialEq)]
31pub struct BenchResults {
32 pub partial_results: String,
34 pub avg_latency_in_sec: i64,
36 pub num_latency: i64,
38 pub minimum_latency_in_sec: i64,
40 pub maximum_latency_in_sec: i64,
42 pub throughput_bytes_per_sec: u64,
44 pub total_transactions_committed: u64,
46 pub transaction_size_in_bytes: u64,
48 pub total_time_elapsed_in_sec: u64,
50 pub total_num_views: usize,
52 pub failed_num_views: usize,
54 pub committee_type: String,
56}
57
58impl BenchResults {
59 pub fn printout(&self) {
61 println!("=====================");
62 println!("{0} Benchmark results:", self.partial_results);
63 println!("Committee type: {}", self.committee_type);
64 println!(
65 "Average latency: {} seconds, Minimum latency: {} seconds, Maximum latency: {} seconds",
66 self.avg_latency_in_sec, self.minimum_latency_in_sec, self.maximum_latency_in_sec
67 );
68 println!("Throughput: {} bytes/sec", self.throughput_bytes_per_sec);
69 println!(
70 "Total transactions committed: {}",
71 self.total_transactions_committed
72 );
73 println!(
74 "Total number of views: {}, Failed number of views: {}",
75 self.total_num_views, self.failed_num_views
76 );
77 println!("=====================");
78 }
79}
80
81#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default, PartialEq)]
83pub struct BenchResultsDownloadConfig {
84 pub commit_sha: String,
87 pub total_nodes: usize,
89 pub da_committee_size: usize,
91 pub fixed_leader_for_gpuvid: usize,
93 pub transactions_per_round: usize,
95 pub transaction_size: u64,
97 pub rounds: usize,
99
100 pub partial_results: String,
106 pub avg_latency_in_sec: i64,
108 pub minimum_latency_in_sec: i64,
110 pub maximum_latency_in_sec: i64,
112 pub throughput_bytes_per_sec: u64,
114 pub total_transactions_committed: u64,
116 pub total_time_elapsed_in_sec: u64,
118 pub total_num_views: usize,
120 pub failed_num_views: usize,
122 pub committee_type: String,
124}
125
126#[derive(Parser, Debug, Clone)]
129#[command(
130 name = "Multi-machine consensus",
131 about = "Simulates consensus among multiple machines"
132)]
133pub struct ValidatorArgs {
135 pub url: Url,
137 pub advertise_address: Option<String>,
139 pub builder_address: Option<SocketAddr>,
141 #[arg(short, long)]
144 pub network_config_file: Option<String>,
145}
146
147#[derive(Parser, Debug, Clone)]
149pub struct MultiValidatorArgs {
150 pub num_nodes: u16,
152 pub url: Url,
154 pub advertise_address: Option<String>,
156 #[arg(short, long)]
159 pub network_config_file: Option<String>,
160}
161
162pub async fn get_complete_config<TYPES: NodeType>(
168 client: &OrchestratorClient,
169 mut validator_config: ValidatorConfig<TYPES>,
170) -> anyhow::Result<(
171 NetworkConfig<TYPES>,
172 ValidatorConfig<TYPES>,
173 NetworkConfigSource,
174)> {
175 let run_config: NetworkConfig<TYPES> = client
177 .post_and_wait_all_public_keys::<TYPES>(&mut validator_config)
178 .await;
179
180 info!(
181 "Retrieved config; our node index is {}. DA committee member: {}",
182 run_config.node_index, validator_config.is_da
183 );
184 Ok((
185 run_config,
186 validator_config,
187 NetworkConfigSource::Orchestrator,
188 ))
189}
190
191impl ValidatorArgs {
192 #[must_use]
216 pub fn from_multi_args(multi_args: MultiValidatorArgs, node_index: u16) -> Self {
217 Self {
218 url: multi_args.url,
219 advertise_address: multi_args.advertise_address,
220 builder_address: None,
221 network_config_file: multi_args
222 .network_config_file
223 .map(|s| format!("{s}-{node_index}")),
224 }
225 }
226}
227
228impl OrchestratorClient {
229 #[must_use]
231 pub fn new(url: Url) -> Self {
232 let client = Client::<ClientErr, OrchestratorVersion>::new(url);
233 OrchestratorClient { client }
235 }
236
237 pub async fn get_config_without_peer<TYPES: NodeType>(
246 &self,
247 ) -> anyhow::Result<NetworkConfig<TYPES>> {
248 let identity = |client: Client<ClientErr, OrchestratorVersion>| {
249 async move {
250 let node_index: Result<u16, ClientErr> = client.post("api/identity").send().await;
251
252 node_index
253 }
254 .boxed()
255 };
256 let node_index = self.wait_for_fn_from_orchestrator(identity).await;
257
258 let f = |client: Client<ClientErr, OrchestratorVersion>| {
260 async move {
261 let config: Result<NetworkConfig<TYPES>, ClientErr> = client
262 .post(&format!("api/config/{node_index}"))
263 .send()
264 .await;
265 config
266 }
267 .boxed()
268 };
269
270 let mut config = self.wait_for_fn_from_orchestrator(f).await;
271 config.node_index = From::<u16>::from(node_index);
272
273 Ok(config)
274 }
275
276 #[instrument(skip_all, name = "orchestrator node index for validator config")]
281 pub async fn get_node_index_for_init_validator_config(&self) -> u16 {
282 let cur_node_index = |client: Client<ClientErr, OrchestratorVersion>| {
283 async move {
284 let cur_node_index: Result<u16, ClientErr> = client
285 .post("api/get_tmp_node_index")
286 .send()
287 .await
288 .inspect_err(|err| tracing::error!("{err}"));
289
290 cur_node_index
291 }
292 .boxed()
293 };
294 self.wait_for_fn_from_orchestrator(cur_node_index).await
295 }
296
297 #[instrument(skip_all, name = "orchestrator config")]
302 pub async fn get_config_after_collection<TYPES: NodeType>(&self) -> NetworkConfig<TYPES> {
303 let get_config_after_collection = |client: Client<ClientErr, OrchestratorVersion>| {
305 async move {
306 let result = client
307 .post("api/post_config_after_peer_collected")
308 .send()
309 .await;
310
311 if let Err(ref err) = result {
312 tracing::error!("{err}");
313 }
314
315 result
316 }
317 .boxed()
318 };
319
320 self.wait_for_fn_from_orchestrator(get_config_after_collection)
322 .await
323 }
324
325 pub async fn post_builder_addresses(&self, addresses: Vec<Url>) {
330 let send_builder_f = |client: Client<ClientErr, OrchestratorVersion>| {
331 let request_body = vbs::Serializer::<OrchestratorVersion>::serialize(&addresses)
332 .expect("Failed to serialize request");
333
334 async move {
335 let result: Result<_, ClientErr> = client
336 .post("api/builder")
337 .body_binary(&request_body)
338 .unwrap()
339 .send()
340 .await
341 .inspect_err(|err| tracing::error!("{err}"));
342 result
343 }
344 .boxed()
345 };
346 self.wait_for_fn_from_orchestrator::<_, _, ()>(send_builder_f)
347 .await;
348 }
349
350 pub async fn get_builder_addresses(&self) -> Vec<Url> {
352 let get_builder = |client: Client<ClientErr, OrchestratorVersion>| {
354 async move {
355 let result = client.get("api/builders").send().await;
356
357 if let Err(ref err) = result {
358 tracing::error!("{err}");
359 }
360
361 result
362 }
363 .boxed()
364 };
365
366 self.wait_for_fn_from_orchestrator(get_builder).await
368 }
369
370 #[instrument(skip(self), name = "orchestrator public keys")]
376 pub async fn post_and_wait_all_public_keys<TYPES: NodeType>(
377 &self,
378 validator_config: &mut ValidatorConfig<TYPES>,
379 ) -> NetworkConfig<TYPES> {
380 let pubkey: Vec<u8> =
381 PeerConfig::<TYPES>::to_bytes(&validator_config.public_config()).clone();
382 let da_requested: bool = validator_config.is_da;
383
384 let request_body = vbs::Serializer::<OrchestratorVersion>::serialize(&pubkey)
385 .expect("failed to serialize request");
386
387 let (node_index, is_da): (u64, bool) = loop {
389 let result = self
390 .client
391 .post(&format!("api/pubkey/{da_requested}"))
392 .body_binary(&request_body)
393 .expect("Failed to form request")
394 .send()
395 .await
396 .inspect_err(|err| tracing::error!("{err}"));
397
398 if let Ok((index, is_da)) = result {
399 break (index, is_da);
400 }
401
402 sleep(Duration::from_millis(250)).await;
403 };
404
405 validator_config.is_da = is_da;
406
407 let wait_for_all_nodes_pub_key = |client: Client<ClientErr, OrchestratorVersion>| {
409 async move {
410 client
411 .get("api/peer_pub_ready")
412 .send()
413 .await
414 .inspect_err(|err| tracing::error!("{err}"))
415 }
416 .boxed()
417 };
418 self.wait_for_fn_from_orchestrator::<_, _, ()>(wait_for_all_nodes_pub_key)
419 .await;
420
421 let mut network_config = self.get_config_after_collection().await;
422
423 network_config.node_index = node_index;
424
425 network_config
426 }
427
428 #[instrument(skip(self), name = "orchestrator ready signal")]
433 pub async fn wait_for_all_nodes_ready(&self, peer_config: Vec<u8>) -> bool {
434 let send_ready_f = |client: Client<ClientErr, OrchestratorVersion>| {
435 let pk = peer_config.clone();
436 async move {
437 let result: Result<_, ClientErr> = client
438 .post("api/ready")
439 .body_binary(&pk)
440 .unwrap()
441 .send()
442 .await
443 .inspect_err(|err| tracing::error!("{err}"));
444 result
445 }
446 .boxed()
447 };
448 self.wait_for_fn_from_orchestrator::<_, _, ()>(send_ready_f)
449 .await;
450
451 let wait_for_all_nodes_ready_f = |client: Client<ClientErr, OrchestratorVersion>| {
452 async move { client.get("api/start").send().await }.boxed()
453 };
454 self.wait_for_fn_from_orchestrator(wait_for_all_nodes_ready_f)
455 .await
456 }
457
458 #[instrument(skip_all, name = "orchestrator metrics")]
462 pub async fn post_bench_results(&self, bench_results: BenchResults) {
463 let _send_metrics_f: Result<(), ClientErr> = self
464 .client
465 .post("api/results")
466 .body_json(&bench_results)
467 .unwrap()
468 .send()
469 .await
470 .inspect_err(|err| tracing::warn!("{err}"));
471 }
472
473 #[instrument(skip_all, name = "waiting for orchestrator")]
476 async fn wait_for_fn_from_orchestrator<F, Fut, GEN>(&self, f: F) -> GEN
477 where
478 F: Fn(Client<ClientErr, OrchestratorVersion>) -> Fut,
479 Fut: Future<Output = Result<GEN, ClientErr>>,
480 {
481 loop {
482 let client = self.client.clone();
483 let res = f(client).await;
484 match res {
485 Ok(x) => break x,
486 Err(err) => {
487 tracing::info!("{err}");
488 sleep(Duration::from_millis(250)).await;
489 },
490 }
491 }
492 }
493}