psibase 0.28.0

Library and command-line tool for interacting with psibase networks
Documentation
//! # Description
//!
//! Dynamic difficulty adjustment service for adaptive rate limiting
//!
//! This service provides a self-adjusting rate limiting mechanism that dynamically modifies
//! difficulty thresholds based on observed activity patterns. Each rate limiter is represented
//! by an NFT, allowing ownership-based administration.
//!
//! # Mechanics
//!
//! The service operates on a time-windowed activity count:
//!
//! 1. **Activity Tracking**: A consumer account increments `activity_count` each time an action
//!    occurs. `activity_count` accumulates within a configurable time window.
//!
//! 2. **Difficulty Adjustment**:
//!    - **Below Target**: If `activity_count` is below `target_min` when a window elapses, the
//!      difficulty decreases by a configured percentage (subject to a floor value).
//!    - **Above Target**: If `activity_count` exceeds `target_max` at any point, the difficulty
//!      immediately increases by a configured percentage, `activity_count` resets, and a new window
//!      begins. If one increment pushes `activity_count` past `target_max` by one or more whole
//!      multiples of `target_max`, the difficulty is increased by the configured percentage once per multiple.
//!
//! 3. **Window-Based Decay**: After each window period (`window_seconds`), if activity was below
//!    the minimum target, difficulty decays proportionally for each complete window that elapsed.
//!
//! 4. **Bounded Adjustments**: Difficulty cannot fall below `floor_difficulty`, ensuring a minimum
//!    security threshold is maintained.
//!
//! # Roles
//!
//! - **Admin**: The NFT owner can configure all parameters (targets, window, percentages, floor).
//!   The NFT is initially minted to the account that creates the rate limiter, but can be
//!   transferred to change administration.
//! - **Consumer**: The account designated at creation time can increment the activity_count and query
//!   difficulty. The consumer is automatically set to the account that calls `create()` and cannot
//!   be changed after creation. Initially, the creator is both admin and consumer, but the admin
//!   role can be transferred via NFT ownership while the consumer remains fixed.

#[crate::service(name = "diff-adj", dispatch = false, psibase_mod = "crate")]
#[allow(non_snake_case, unused_variables)]
pub mod Service {
    use crate::{AccountNumber, Pack, TimePointSec, ToSchema, Unpack};

    use async_graphql::SimpleObject;
    use serde::{Deserialize, Serialize};

    #[table(name = "RateLimitTable", index = 0)]
    #[derive(Default, Pack, Unpack, ToSchema, SimpleObject, Serialize, Deserialize, Debug)]
    #[fracpack(fracpack_mod = "fracpack")]
    pub struct RateLimit {
        #[primary_key]
        pub nft_id: u32,
        pub window_seconds: u32,
        pub activity_count: u32,
        pub target_min: u32,
        pub target_max: u32,
        pub floor_difficulty: u64,
        pub active_difficulty: u64,
        pub last_update: TimePointSec,
        pub increase_ppm: u32,
        pub decrease_ppm: u32,
        pub consumer: AccountNumber,
    }

    /// Creates a new Rate limit
    ///
    /// # Arguments
    /// * `initial_difficulty` - Sets initial difficulty
    /// * `window_seconds` - Seconds duration before decay occurs
    /// * `target_min` - Minimum rate limit target
    /// * `target_max` - Maximum rate limit target
    /// * `floor_difficulty` - Minimum difficulty
    /// * `increase_ppm` - PPM to increase when over target, e.g. 50000 ppm = 5%
    /// * `decrease_ppm` - PPM to decrease when under target, e.g. 50000 ppm = 5%
    #[action]
    fn create(
        initial_difficulty: u64,
        window_seconds: u32,
        target_min: u32,
        target_max: u32,
        floor_difficulty: u64,
        increase_ppm: u32,
        decrease_ppm: u32,
    ) -> u32 {
        unimplemented!()
    }

    /// Get RateLimit record
    ///
    /// # Arguments
    /// * `nft_id` - RateLimit / NFT ID
    ///
    /// # Returns
    /// The RateLimit if it exists
    #[action]
    fn get(nft_id: u32) -> Option<RateLimit> {
        unimplemented!()
    }

    /// Get RateLimit difficulty
    ///
    /// # Arguments
    /// * `nft_id` - RateLimit / NFT ID
    ///
    /// # Returns
    /// Difficulty of RateLimit
    #[action]
    fn get_diff(nft_id: u32) -> u64 {
        unimplemented!()
    }

    /// Increment RateLimit instance, potentially increasing the difficulty.
    ///
    /// The difficulty may increase multiple times if `activity_count` exceeds `target_max`
    ///   by more than one multiple of `target_max`.
    ///
    /// Returns the difficulty before any difficulty adjustment due to the increment.
    ///
    /// * Requires sender to be consumer account.
    ///
    /// # Arguments
    /// * `nft_id` - RateLimit / NFT ID
    /// * `amount` - Amount to increment the activity_count by
    #[action]
    fn increment(nft_id: u32, amount: u32) -> u64 {
        unimplemented!()
    }

    /// Update targets
    ///
    /// * Requires holding administration NFT.
    ///
    /// # Arguments
    /// * `nft_id` - RateLimit / NFT ID
    /// * `target_min` - Minimum target activity
    /// * `target_max` - Maximum target activity
    #[action]
    fn set_targets(nft_id: u32, target_min: u32, target_max: u32) {
        unimplemented!()
    }

    /// Update window
    ///
    /// * Requires holding administration NFT.
    ///
    /// # Arguments
    /// * `nft_id` - RateLimit / NFT ID
    /// * `seconds` - Seconds
    #[action]
    fn set_window(nft_id: u32, seconds: u32) {
        unimplemented!()
    }

    /// Update floor difficulty
    ///
    /// * Requires holding administration NFT.
    ///
    /// # Arguments
    /// * `nft_id` - RateLimit / NFT ID
    /// * `difficulty` - Difficulty
    #[action]
    fn set_floor(nft_id: u32, difficulty: u64) {
        unimplemented!()
    }

    /// Update ppm change
    ///
    /// * Requires holding administration NFT.
    ///
    /// # Arguments
    /// * `nft_id` - RateLimit / NFT ID
    /// * `increase_ppm` - PPM to increase when over target, e.g. 50000 ppm = 5%
    /// * `decrease_ppm` - PPM to decrease when under target, e.g. 50000 ppm = 5%
    #[action]
    fn set_ppm(nft_id: u32, increase_ppm: u32, decrease_ppm: u32) {
        unimplemented!()
    }

    /// Delete RateLimit instance
    ///
    /// * Requires holding administration NFT.
    ///
    /// # Arguments
    /// * `nft_id` - RateLimit / NFT ID
    #[action]
    fn delete(nft_id: u32) {
        unimplemented!()
    }

    /// Get targets
    ///
    /// Gets the minimum and maximum targets for the specified DiffAdjust
    ///
    /// Returns (target_min, target_max)
    #[action]
    fn get_targets(nft_id: u32) -> (u32, u32) {
        unimplemented!()
    }
}

pub use Service::{RateLimit, RateLimitTable};

#[test]
fn verify_schema() {
    crate::assert_schema_matches_package::<Wrapper>();
}