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