Skip to main content

rialo_rex_processor_interface/
state.rs

1// Copyright (c) Subzero Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use rialo_cli_representable::Representable;
5use rialo_cli_representation::HumanReadable;
6use rialo_types::{AuthorityKeyBytes, RexData, RexId, RexOutput};
7use serde::{Deserialize, Serialize};
8use serde_big_array::BigArray;
9
10use crate::errors::RexProcessorError;
11
12/// REX update structure
13#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Representable)]
14#[representable(human_readable = "rex_update_human_readable")]
15pub struct RexUpdate {
16    /// The output that resulted from the REX Duty being performed, serialized as bytes with Borsh.
17    pub(crate) data: Vec<u8>,
18    /// The 96-byte BLS12-381 authority public key of the validator
19    #[serde(with = "BigArray")]
20    pub(crate) validator: AuthorityKeyBytes,
21}
22
23fn rex_update_human_readable(update: &RexUpdate) -> String {
24    let mut out = String::new();
25    // Format authority key as hex string for readability
26    let auth_key_hex = hex::encode(update.validator);
27    out.push_str(&format!("Validator (authority key): {}; ", auth_key_hex));
28    out.push_str("Data: ");
29    // Try to decode the rex output
30    match update.try_data_as_output() {
31        Ok(rex_output) => {
32            out.push_str(&format!("{rex_output:?}"));
33        }
34        Err(_) => {
35            out.push_str("<Unable to decode>: \n");
36        }
37    }
38    out
39}
40
41impl RexUpdate {
42    pub fn new(data: Vec<u8>, validator: AuthorityKeyBytes) -> Self {
43        Self { data, validator }
44    }
45
46    /// Returns the REX data as a Base64 encoded string
47    ///
48    /// # Returns
49    ///
50    /// The output that resulted from the REX Duty being performed, serialized as bytes with Borsh
51    pub fn data(&self) -> &[u8] {
52        &self.data
53    }
54
55    /// The 96-byte BLS12-381 authority public key of the validator.
56    pub fn validator(&self) -> AuthorityKeyBytes {
57        self.validator
58    }
59
60    /// Returns the data as [`RexOutput`]
61    ///
62    /// # Returns
63    ///
64    /// The output that resulted from the REX Duty being performed, deserialized as [`RexOutput`]
65    pub fn try_data_as_output(&self) -> Result<RexOutput, RexProcessorError> {
66        borsh::from_slice(&self.data).map_err(|_| RexProcessorError::ReportData)
67    }
68
69    /// Returns the `RexData`  payload returned by the rex if the request was a success.
70    /// Otherwise, returns None.
71    ///
72    /// # Returns
73    ///
74    /// An Option containing the `RexData`  payload if the rex output was a success, or None otherwise.
75    pub fn success_payload(&self) -> Option<RexData> {
76        match self.try_data_as_output() {
77            Ok(RexOutput::Success(response)) => Some(response.response),
78            _ => None,
79        }
80    }
81
82    pub fn get_rex_response_timestamp(&self) -> Option<String> {
83        if let Ok(RexOutput::Success(response)) = self.try_data_as_output() {
84            Some(response.timestamp)
85        } else {
86            None
87        }
88    }
89}
90
91/// Represents an rex's definition and configuration
92#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Representable)]
93#[representable(human_readable = "rex_report_human_readable")]
94pub struct RexReport {
95    pub rex_id: RexId,           // Unique identifier
96    pub round: u64,              // Round that received the report
97    pub updates: Vec<RexUpdate>, // Updates from validator for this rex
98}
99
100fn rex_report_human_readable(report: &RexReport) -> String {
101    let mut out = String::new();
102
103    out.push_str("REX Report\n");
104    out.push_str("=============\n\n");
105    out.push_str(&format!("REX ID: {}\n", report.rex_id));
106    out.push_str(&format!("Round: {}\n", report.round));
107    out.push_str(&format!("Number of Updates: {}\n", report.updates.len()));
108
109    if !report.updates.is_empty() {
110        out.push_str("\nUpdates:\n");
111        for (i, update) in report.updates.iter().enumerate() {
112            out.push_str(&format!("  Update {}:\n", i + 1));
113            let auth_key_hex = hex::encode(update.validator());
114            out.push_str(&format!("    Validator: {}\n", auth_key_hex));
115            out.push_str(&format!("    Data Size: {} bytes\n", update.data().len()));
116
117            // Show success payload if available
118            if let Ok(output) = update.try_data_as_output() {
119                match output {
120                    RexOutput::Success(response) => {
121                        out.push_str(&format!("    Success: {:?}\n", response.human_readable()));
122                    }
123                    RexOutput::RexError(err) => {
124                        out.push_str(&format!("    RexError: {err:?}\n"));
125                    }
126                    RexOutput::UnserializableResponse(err) => {
127                        out.push_str(&format!("    UnserializableResponse: {err:?}\n"));
128                    }
129                    _ => {
130                        out.push_str(&format!("    Unknown Output Type: {:?}\n", output));
131                    }
132                }
133            } else {
134                out.push_str(&format!("    Output: {}\n", update.human_readable()));
135            }
136        }
137    }
138
139    out
140}
141
142impl RexReport {
143    /// Converts the `RexReport` into a `Vec<RexUpdate>`
144    ///
145    /// This function is used to extract the updates from the `RexReport`
146    ///
147    /// # Returns
148    ///
149    /// A vector of `RexUpdate` instances
150    pub fn into_updates(self) -> Vec<RexUpdate> {
151        self.updates
152    }
153
154    /// Returns an iterator over containing all the `RexOutput`
155    ///
156    /// # Returns
157    ///
158    /// Returns an iterator over all the `RexOutput`s.
159    pub fn outputs(&self) -> impl Iterator<Item = RexOutput> + '_ {
160        self.updates
161            .iter()
162            .filter_map(|update| update.try_data_as_output().ok())
163    }
164}
165
166/// Extracts the round from the bincode data
167///
168/// # Arguments
169///
170/// * `bincode_data` - A byte slice containing the bincode data
171///
172/// # Returns
173///
174/// The extracted round value
175///
176/// # Errors
177///
178/// Returns an error if the data is invalid or if the conversion fails
179pub fn extract_round_raw(bincode_data: &[u8]) -> Result<u64, RexProcessorError> {
180    // The RexReport structure has changed, so we need to deserialize it properly
181    // to extract the round field
182    let report: RexReport =
183        bincode::deserialize(bincode_data).map_err(|_| RexProcessorError::ReportData)?;
184
185    Ok(report.round)
186}