rialo-rex-processor-interface 0.8.0-alpha.0

Instructions and constructors for the program processing REX updates from validators
Documentation
// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use rialo_cli_representable::Representable;
use rialo_cli_representation::HumanReadable;
use rialo_types::{AuthorityKeyBytes, RexData, RexId, RexOutput};
use serde::{Deserialize, Serialize};
use serde_big_array::BigArray;

use crate::errors::RexProcessorError;

/// REX update structure
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Representable)]
#[representable(human_readable = "rex_update_human_readable")]
pub struct RexUpdate {
    /// The output that resulted from the REX Duty being performed, serialized as bytes with Borsh.
    pub(crate) data: Vec<u8>,
    /// The 96-byte BLS12-381 authority public key of the validator
    #[serde(with = "BigArray")]
    pub(crate) validator: AuthorityKeyBytes,
}

fn rex_update_human_readable(update: &RexUpdate) -> String {
    let mut out = String::new();
    // Format authority key as hex string for readability
    let auth_key_hex = hex::encode(update.validator);
    out.push_str(&format!("Validator (authority key): {}; ", auth_key_hex));
    out.push_str("Data: ");
    // Try to decode the rex output
    match update.try_data_as_output() {
        Ok(rex_output) => {
            out.push_str(&format!("{rex_output:?}"));
        }
        Err(_) => {
            out.push_str("<Unable to decode>: \n");
        }
    }
    out
}

impl RexUpdate {
    pub fn new(data: Vec<u8>, validator: AuthorityKeyBytes) -> Self {
        Self { data, validator }
    }

    /// Returns the REX data as a Base64 encoded string
    ///
    /// # Returns
    ///
    /// The output that resulted from the REX Duty being performed, serialized as bytes with Borsh
    pub fn data(&self) -> &[u8] {
        &self.data
    }

    /// The 96-byte BLS12-381 authority public key of the validator.
    pub fn validator(&self) -> AuthorityKeyBytes {
        self.validator
    }

    /// Returns the data as [`RexOutput`]
    ///
    /// # Returns
    ///
    /// The output that resulted from the REX Duty being performed, deserialized as [`RexOutput`]
    pub fn try_data_as_output(&self) -> Result<RexOutput, RexProcessorError> {
        borsh::from_slice(&self.data).map_err(|_| RexProcessorError::ReportData)
    }

    /// Returns the `RexData`  payload returned by the rex if the request was a success.
    /// Otherwise, returns None.
    ///
    /// # Returns
    ///
    /// An Option containing the `RexData`  payload if the rex output was a success, or None otherwise.
    pub fn success_payload(&self) -> Option<RexData> {
        match self.try_data_as_output() {
            Ok(RexOutput::Success(response)) => Some(response.response),
            _ => None,
        }
    }

    pub fn get_rex_response_timestamp(&self) -> Option<String> {
        if let Ok(RexOutput::Success(response)) = self.try_data_as_output() {
            Some(response.timestamp)
        } else {
            None
        }
    }
}

/// Represents an rex's definition and configuration
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Representable)]
#[representable(human_readable = "rex_report_human_readable")]
pub struct RexReport {
    pub rex_id: RexId,           // Unique identifier
    pub round: u64,              // Round that received the report
    pub updates: Vec<RexUpdate>, // Updates from validator for this rex
}

fn rex_report_human_readable(report: &RexReport) -> String {
    let mut out = String::new();

    out.push_str("REX Report\n");
    out.push_str("=============\n\n");
    out.push_str(&format!("REX ID: {}\n", report.rex_id));
    out.push_str(&format!("Round: {}\n", report.round));
    out.push_str(&format!("Number of Updates: {}\n", report.updates.len()));

    if !report.updates.is_empty() {
        out.push_str("\nUpdates:\n");
        for (i, update) in report.updates.iter().enumerate() {
            out.push_str(&format!("  Update {}:\n", i + 1));
            let auth_key_hex = hex::encode(update.validator());
            out.push_str(&format!("    Validator: {}\n", auth_key_hex));
            out.push_str(&format!("    Data Size: {} bytes\n", update.data().len()));

            // Show success payload if available
            if let Ok(output) = update.try_data_as_output() {
                match output {
                    RexOutput::Success(response) => {
                        out.push_str(&format!("    Success: {:?}\n", response.human_readable()));
                    }
                    RexOutput::RexError(err) => {
                        out.push_str(&format!("    RexError: {err:?}\n"));
                    }
                    RexOutput::UnserializableResponse(err) => {
                        out.push_str(&format!("    UnserializableResponse: {err:?}\n"));
                    }
                    _ => {
                        out.push_str(&format!("    Unknown Output Type: {:?}\n", output));
                    }
                }
            } else {
                out.push_str(&format!("    Output: {}\n", update.human_readable()));
            }
        }
    }

    out
}

impl RexReport {
    /// Converts the `RexReport` into a `Vec<RexUpdate>`
    ///
    /// This function is used to extract the updates from the `RexReport`
    ///
    /// # Returns
    ///
    /// A vector of `RexUpdate` instances
    pub fn into_updates(self) -> Vec<RexUpdate> {
        self.updates
    }

    /// Returns an iterator over containing all the `RexOutput`
    ///
    /// # Returns
    ///
    /// Returns an iterator over all the `RexOutput`s.
    pub fn outputs(&self) -> impl Iterator<Item = RexOutput> + '_ {
        self.updates
            .iter()
            .filter_map(|update| update.try_data_as_output().ok())
    }
}

/// Extracts the round from the bincode data
///
/// # Arguments
///
/// * `bincode_data` - A byte slice containing the bincode data
///
/// # Returns
///
/// The extracted round value
///
/// # Errors
///
/// Returns an error if the data is invalid or if the conversion fails
pub fn extract_round_raw(bincode_data: &[u8]) -> Result<u64, RexProcessorError> {
    // The RexReport structure has changed, so we need to deserialize it properly
    // to extract the round field
    let report: RexReport =
        bincode::deserialize(bincode_data).map_err(|_| RexProcessorError::ReportData)?;

    Ok(report.round)
}