checkpointq_lib/
processor.rs1use 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 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 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 canonical = Some(passed_threshold)
91 } else {
92 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}