rialo-limits 0.2.0

Limits of the Rialo network.
Documentation
// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! # Oracle Data Size Overhead Tests
//!
//! This module tests that oracle data doesn't experience excessive overhead when
//! serialized through our protocol's data structures.
//!
//! ## Background
//!
//! When an oracle returns data, it goes through several layers of wrapping:
//! 1. The raw data is wrapped in `OracleResponse` (adding a timestamp)
//! 2. `OracleResponse` is wrapped in `OracleOutput` enum
//! 3. `OracleOutput` is Borsh-serialized and stored in `OracleUpdate.data`
//! 4. `OracleUpdate` is stored in `OracleReport.updates`
//! 5. Finally, `OracleReport` is Bincode-serialized for transmission
//!
//! Each layer adds metadata and serialization overhead. This test ensures that
//! the total overhead remains constant at exactly 221 bytes per oracle update.
//!
//! ## Measured Overhead
//!
//! Through empirical testing, we've determined that the overhead for each
//! oracle update is **exactly 221 bytes**. This overhead is constant
//! regardless of the actual data size or number of validators.
//!
//! ### Detailed Overhead Breakdown (221 bytes per update)
//!
//! #### 1. OracleOutput (Borsh serialized) - contributes to OracleUpdate.data
//! - **Enum discriminant**: 1 byte (0 for Success variant)
//! - **OracleResponse.response** (OracleData):
//!   - `OracleData::Raw` variant discriminant: 1 byte
//!     - Length prefix: 4 bytes (u32 in little-endian for Borsh)
//!     - Data: `size` bytes (the actual oracle data)
//!   - `OracleData::Filtered` variant discriminant: 1 byte
//!     - Length prefix: 4 bytes (u32 in little-endian for Borsh)
//!     - For each filter entry:
//!       - `FilterResult`enum discriminant: 1 byte
//!       - Length prefix: 4 bytes (u32 in little-endian for Borsh)
//!       - Data: `size` bytes (the actual oracle data)
//! - **OracleResponse.timestamp** (String):
//!   - Length prefix: 4 bytes (u32 in little-endian)
//!   - Timestamp string: 27 bytes (fixed format: "2025-08-23T17:52:43.123456Z")
//! - **Subtotal for OracleOutput**: 36 bytes + data size
//!
//! #### 2. OracleUpdate (Bincode serialized)
//! - **data field** (`Vec<u8>`):
//!   - Length prefix: 8 bytes (u64 for Bincode)
//!   - Contents: The Borsh-serialized OracleOutput (39 + data size bytes)
//! - **validator** (Authority Pubkey): 104 bytes
//! - **Subtotal for OracleUpdate**: 40 bytes + OracleOutput size
//!
//! #### 3. OracleReport (Bincode serialized)
//! - **oracle_id**: 64 bytes (based on OracleId type definition)
//! - **round**: 8 bytes (u64)
//! - **updates** (`Vec<OracleUpdate>`):
//!   - Length prefix: 8 bytes (u64 for Vec length)
//!   - Contents: The OracleUpdate entries
//! - **Subtotal for OracleReport structure**: 80 bytes + all OracleUpdate sizes
//!
//! ### Total Calculation - Raw data
//! For a single update with data of size S:
//! - OracleOutput overhead: 37 bytes
//! - OracleUpdate overhead: 104 bytes
//! - OracleReport overhead: 80 bytes
//! - **Total fixed overhead: 221 bytes**
//!
//! ### Total Calculation - Filtered data
//! For a single update with data of size S:
//! - OracleOutput overhead: 41 + (f * 5) bytes
//! - OracleUpdate overhead: 40 bytes
//! - OracleReport overhead: 80 bytes
//! - **Total fixed overhead: 157 bytes**
//!
//! ## JSON Serialization Note
//!
//! The oracle returns JSON data, which adds 2 bytes (quotes) to string values.
//! The test accounts for this by adjusting the input string size so that the
//! JSON-serialized result is exactly the desired size.

use tracing::error;

/// The minimum viable size for the oracle output buffer, ensuring the oracle can serialize and
/// distinguish success or error responses from the `OracleOutput` enum without truncation.
/// Chosen empirically to support a variety of error/success reporting formats.
pub const MIN_VIABLE_LIMIT_OF_ORACLE_OUTPUT_SIZE: usize = 100;
pub const MAX_ORACLE_OUTPUT_SIZE: usize =
    (u16::MAX as usize) - ORACLE_REPORT_BASE_OVERHEAD - ORACLE_UPDATE_BASE_OVERHEAD;
pub const ORACLE_REPORT_BASE_OVERHEAD: usize = 80; // oracle_id (64) + round (8) + updates length prefix (8)
pub const ORACLE_UPDATE_BASE_OVERHEAD: usize = 104; // data length prefix (8) + validator (96)
pub const ORACLE_OUTPUT_RAW_BASE_OVERHEAD: usize = 37; // enum discriminant for the output (1) + enum discriminant for the data (1) + response length prefix (4) + timestamp length prefix (4) + timestamp (27)

/// Calculates the maximum size of serialized OracleOutput.
///
/// This function determines the maximum size of oracle data that can be included
/// in an oracle update, given the number of validators and the protocol's size limits.
/// It accounts for all serialization overhead from the oracle report structure.
///
/// # Arguments
///
/// * `validator_count` - The number of validators that will submit oracle updates
///
/// # Returns
///
/// The maximum size in bytes that the oracle data can be for each validator's update
/// to ensure the entire oracle report stays within the u16::MAX size limit.
pub fn max_oracle_output_serialized_bytes(validator_count: u32) -> usize {
    if validator_count == 0 {
        error!(
            validator_count,
            "Got validator_count==0 for calculation of allowed oracle update size"
        );
        return 0;
    }
    (((u16::MAX as usize).saturating_sub(ORACLE_REPORT_BASE_OVERHEAD)) / validator_count as usize)
        .saturating_sub(ORACLE_UPDATE_BASE_OVERHEAD)
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Checks the definition of MAX_ORACLE_OUTPUT_SIZE.
    #[test]
    fn test_max_oracle_output_serialized_bytes_single_validator() {
        // Test that max_oracle_output_serialized_bytes(1) equals MAX_ORACLE_OUTPUT_SIZE
        let result = max_oracle_output_serialized_bytes(1);
        assert_eq!(result, MAX_ORACLE_OUTPUT_SIZE);
    }

    /// Checks that the computation doesn't overflow.
    #[test]
    fn test_max_oracle_output_huge() {
        // Test that max_oracle_output_serialized_bytes(1) equals MAX_ORACLE_OUTPUT_SIZE
        let result = max_oracle_output_serialized_bytes(1000000);
        assert_eq!(result, 0);
    }
}