rialo-limits 0.4.0-alpha.0

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

//! # REX Data Size Overhead Tests
//!
//! This module tests that REX data doesn't experience excessive overhead when
//! serialized through our protocol's data structures.
//!
//! ## Background
//!
//! When a REX program returns data, it goes through several layers of wrapping:
//! 1. The raw data is wrapped in `RexResponse` (adding a timestamp)
//! 2. `RexResponse` is wrapped in `RexOutput` enum
//! 3. `RexOutput` is Borsh-serialized and stored in `RexUpdate.data`
//! 4. `RexUpdate` is stored in `RexReport.updates`
//! 5. Finally, `RexReport` 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 REX update.
//!
//! ## Measured Overhead
//!
//! Through empirical testing, we've determined that the overhead for each
//! REX 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. RexOutput (Borsh serialized) - contributes to RexUpdate.data
//! - **Enum discriminant**: 1 byte (0 for Success variant)
//! - **RexResponse.response** (RexData):
//!   - `RexData::Raw` variant discriminant: 1 byte
//!     - Length prefix: 4 bytes (u32 in little-endian for Borsh)
//!     - Data: `size` bytes (the actual REX data)
//!   - `RexData::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 REX data)
//! - **RexResponse.timestamp** (String):
//!   - Length prefix: 4 bytes (u32 in little-endian)
//!   - Timestamp string: 27 bytes (fixed format: "2025-08-23T17:52:43.123456Z")
//! - **Subtotal for RexOutput**: 36 bytes + data size
//!
//! #### 2. RexUpdate (Bincode serialized)
//! - **data field** (`Vec<u8>`):
//!   - Length prefix: 8 bytes (u64 for Bincode)
//!   - Contents: The Borsh-serialized RexOutput (39 + data size bytes)
//! - **validator** (Authority Pubkey): 104 bytes
//! - **Subtotal for RexUpdate**: 40 bytes + RexOutput size
//!
//! #### 3. RexReport (Bincode serialized)
//! - **rex_id**: 64 bytes (based on RexId type definition)
//! - **round**: 8 bytes (u64)
//! - **updates** (`Vec<RexUpdate>`):
//!   - Length prefix: 8 bytes (u64 for Vec length)
//!   - Contents: The RexUpdate entries
//! - **Subtotal for RexReport structure**: 80 bytes + all RexUpdate sizes
//!
//! ### Total Calculation - Raw data
//! For a single update with data of size S:
//! - RexOutput overhead: 37 bytes
//! - RexUpdate overhead: 104 bytes
//! - RexReport overhead: 80 bytes
//! - **Total fixed overhead: 221 bytes**
//!
//! ### Total Calculation - Filtered data
//! For a single update with data of size S:
//! - RexOutput overhead: 41 + (f * 5) bytes
//! - RexUpdate overhead: 40 bytes
//! - RexReport overhead: 80 bytes
//! - **Total fixed overhead: 157 bytes**
//!
//! ## JSON Serialization Note
//!
//! The REX 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 REX output buffer, ensuring the REX program can serialize and
/// distinguish success or error responses from the `RexOutput` enum without truncation.
/// Chosen empirically to support a variety of error/success reporting formats.
pub const MIN_VIABLE_LIMIT_OF_REX_OUTPUT_SIZE: usize = 100;
pub const REX_MAX_OUTPUT_SIZE: usize =
    (u16::MAX as usize) - REX_REPORT_BASE_OVERHEAD - REX_UPDATE_BASE_OVERHEAD;
pub const REX_REPORT_BASE_OVERHEAD: usize = 80; // rex_id (64) + round (8) + updates length prefix (8)
pub const REX_UPDATE_BASE_OVERHEAD: usize = 104; // data length prefix (8) + validator (96)
pub const REX_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 RexOutput.
///
/// This function determines the maximum size of REX data that can be included
/// in an REX update, given the number of validators and the protocol's size limits.
/// It accounts for all serialization overhead from the REX report structure.
///
/// # Arguments
///
/// * `validator_count` - The number of validators that will submit REX updates
///
/// # Returns
///
/// The maximum size in bytes that the REX data can be for each validator's update
/// to ensure the entire REX report stays within the u16::MAX size limit.
pub fn max_rex_output_serialized_bytes(validator_count: u32) -> usize {
    if validator_count == 0 {
        error!(
            validator_count,
            "Got validator_count==0 for calculation of allowed REX update size"
        );
        return 0;
    }
    (((u16::MAX as usize).saturating_sub(REX_REPORT_BASE_OVERHEAD)) / validator_count as usize)
        .saturating_sub(REX_UPDATE_BASE_OVERHEAD)
}

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

    /// Checks the definition of MAX_OUTPUT_SIZE.
    #[test]
    fn test_max_output_serialized_bytes_single_validator() {
        // Test that max_output_serialized_bytes(1) equals MAX_OUTPUT_SIZE
        let result = max_rex_output_serialized_bytes(1);
        assert_eq!(result, REX_MAX_OUTPUT_SIZE);
    }

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