checkpointq_lib/
processor.rs

1use crate::client::{ResponsePayloadWithEndpointInfo, SuccessEndpointPayload};
2use crate::errors::AppError;
3use colored::*;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug)]
8struct GroupedResult {
9    success: HashMap<String, Vec<SuccessPayload>>,
10    failure: Vec<FailurePayload>,
11}
12
13#[derive(Debug, Serialize, Deserialize)]
14pub struct DisplayableResult {
15    pub canonical: Option<HashMap<String, Vec<SuccessPayload>>>,
16    pub non_canonical: Option<HashMap<String, Vec<SuccessPayload>>>,
17    pub failure: Vec<FailurePayload>,
18}
19
20#[derive(Debug, Serialize, Deserialize)]
21pub struct SuccessPayload {
22    pub payload: SuccessEndpointPayload,
23    pub endpoint: String,
24}
25
26#[derive(Debug, Serialize, Deserialize)]
27pub struct FailurePayload {
28    pub payload: AppError,
29    pub endpoint: String,
30}
31
32fn group_success_failure(response_payload: Vec<ResponsePayloadWithEndpointInfo>) -> GroupedResult {
33    let (successes, failures): (Vec<SuccessPayload>, Vec<FailurePayload>) = response_payload
34        .into_iter()
35        .fold((vec![], vec![]), |mut acc, result| {
36            match result.payload {
37                Ok(success) => acc.0.push(SuccessPayload {
38                    payload: success,
39                    endpoint: result.endpoint,
40                }),
41                Err(error) => acc.1.push(FailurePayload {
42                    payload: error,
43                    endpoint: result.endpoint,
44                }),
45            }
46            acc
47        });
48
49    let mut hash_to_successes: HashMap<String, Vec<SuccessPayload>> = HashMap::new();
50    successes.into_iter().for_each(|entry| {
51        hash_to_successes
52            .entry(entry.payload.data.finalized.root.clone())
53            .or_default()
54            .push(entry)
55    });
56
57    GroupedResult {
58        success: hash_to_successes,
59        failure: failures,
60    }
61}
62
63pub fn process_to_displayable_format(
64    response_payload: Vec<ResponsePayloadWithEndpointInfo>,
65) -> DisplayableResult {
66    // groups the results into
67    // failures
68    // success
69    //   - success grouped by their block_root hash
70    let grouped_result = group_success_failure(response_payload);
71    let mut canonical: Option<HashMap<String, Vec<SuccessPayload>>> = None;
72    let mut non_canonical: Option<HashMap<String, Vec<SuccessPayload>>> = None;
73
74    if !grouped_result.success.is_empty() {
75        if grouped_result.success.keys().len() == 1 {
76            canonical = Some(grouped_result.success);
77        } else {
78            // more than one results, pick one with values more than 2/3
79            let total_value = grouped_result.success.values().len() as f64;
80            let threshold = (2f64 / 3f64 * total_value).floor();
81            let (passed_threshold, below_threshold): (
82                HashMap<String, Vec<SuccessPayload>>,
83                HashMap<String, Vec<SuccessPayload>>,
84            ) = grouped_result
85                .success
86                .into_iter()
87                .partition(|(_, values)| values.len() as f64 > threshold);
88            if passed_threshold.keys().len() == 1 {
89                // if there is only one value they passed the threshold that is the canonical result
90                canonical = Some(passed_threshold)
91            } else {
92                // else the non_canonical will include
93                // the multiple values that passed the threshold
94                // the values that did not even pass the threshold
95                non_canonical = Some(
96                    passed_threshold
97                        .into_iter()
98                        .chain(below_threshold)
99                        .collect(),
100                )
101            }
102        }
103    };
104
105    DisplayableResult {
106        canonical,
107        non_canonical,
108        failure: grouped_result.failure,
109    }
110}
111
112pub fn print_result(result: DisplayableResult, is_verbose: bool) {
113    if let Some(canonical_result) = result.canonical {
114        println!(
115            "{}: {}",
116            "Block root".blue(),
117            canonical_result
118                .keys()
119                .next()
120                .unwrap_or(&"block root not found".to_string())
121                .green()
122                .bold()
123        );
124
125        if let Some((_, first_value)) = canonical_result.iter().next() {
126            if let Some(first_item) = first_value.iter().next() {
127                println!(
128                    "{}: \t{}",
129                    "Epoch".blue(),
130                    first_item.payload.data.finalized.epoch.green().bold()
131                );
132            }
133        }
134
135        if is_verbose {
136            println!(
137                "{}:\n \t{}",
138                "Details".blue(),
139                serde_json::to_string_pretty(&canonical_result)
140                    .unwrap_or("displaying verbose failed".to_string())
141                    .green()
142            );
143        }
144    };
145
146    if let Some(non_canonical_result) = result.non_canonical {
147        println!("{}", "Conflicting:".yellow().bold());
148        if is_verbose {
149            println!(
150                "{}:\n \t{}",
151                "Details".yellow(),
152                serde_json::to_string_pretty(&non_canonical_result)
153                    .unwrap_or("displaying verbose failed".to_string())
154                    .yellow()
155            );
156        } else {
157            for (key, values) in &non_canonical_result {
158                println!("\t Checkpoint: {}", key.yellow());
159                for value in values {
160                    println!("\t\t {}", value.endpoint.yellow());
161                }
162            }
163        }
164    }
165
166    if !result.failure.is_empty() {
167        println!("{}", "Errors:".red().bold());
168        if is_verbose {
169            println!(
170                "{}:\n \t{}",
171                "Details".red(),
172                serde_json::to_string_pretty(&result.failure)
173                    .unwrap_or("displaying error failed".to_string())
174                    .red()
175            );
176        } else {
177            result.failure.into_iter().for_each(|failure_value| {
178                println!("\t Endpoint: {}", failure_value.endpoint.red());
179                println!("\t Error: {}", failure_value.payload.to_string().red());
180            });
181        }
182    }
183}