tangle-network / tangle

The Layer 1 blockchain for on-demand services.
https://www.tangle.tools/
GNU General Public License v3.0
51 stars 25 forks source link

[TASK] Update the service pallet to handle operator pricing. #748

Closed shekohex closed 3 months ago

shekohex commented 3 months ago

Overview

The goal of this task is to allow the providers to specify how are their base pricing for their machine would be specified on-chain. Looking at Akash way for configuring the bidding pricing it looks very clear for us to do something a bit similar:

Update OperatorPreferences

The current OperatorPreferences looks like the following:

pub struct OperatorPreferences {
    /// The operator ECDSA public key.
    pub key: ecdsa::Public,
    /// The approval prefrence of the operator.
    pub approval: ApprovalPrefrence,
}

We will update it by adding a new field for the PriceTargets

/// Represents the pricing structure for various hardware resources.
/// All prices are specified in USD/hr, calculated based on the average block time.
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct PriceTargets {
    /// Price per vCPU per hour
    pub cpu: u64,
    /// Price per MB of memory per hour
    pub mem: u64,
    /// Price per GB of HDD storage per hour
    pub storage_hd: u64,
    /// Price per GB of SSD storage per hour
    pub storage_ssd: u64,
    /// Price per GB of NVMe storage per hour
    pub storage_nvme: u64,
}

/// Represents the preferences and configuration for an operator.
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct OperatorPreferences {
    /// The operator ECDSA public key.
    pub key: ecdsa::Public,
    /// The approval preference of the operator.
    pub approval: ApprovalPreference,
    /// The pricing targets for the operator's resources.
    pub price_targets: PriceTargets,
}

An Extrinsic to Update Pricing

We will create an extrinsic in the Service Pallet to Allow for Updating the price targets with a new value

#[pallet::call]
impl<T: Config> Pallet<T> {
    /// Update the price targets for an operator.
    #[pallet::weight(T::WeightInfo::update_price_targets())]
    pub fn update_price_targets(
        origin: OriginFor<T>,
        new_price_targets: PriceTargets,
    ) -> DispatchResult {
        let who = ensure_signed(origin)?;

        // Ensure the caller is a registered operator
        ensure!(Operators::<T>::contains_key(&who), Error::<T>::NotAnOperator);

        // Update the price targets
        Operators::<T>::mutate(&who, |operator| {
            if let Some(preferences) = operator {
                preferences.price_targets = new_price_targets;
            }
        });

        // Emit an event
        Self::deposit_event(Event::PriceTargetsUpdated { operator: who, new_price_targets });

        Ok(())
    }
}

Price Calculation

To calculate the price for a specific configuration:

  1. Determine the number of blocks per hour based on the average block time:

    const SECONDS_PER_HOUR: u32 = 3600;
    const AVG_BLOCK_TIME_SECONDS: u32 = 6;
    const BLOCKS_PER_HOUR: u32 = SECONDS_PER_HOUR / AVG_BLOCK_TIME_SECONDS;
  2. Calculate the price for each resource:

    let cpu_price = (config.vcpu * price_targets.cpu * BLOCKS_PER_HOUR) / 1_000_000;
    let mem_price = (config.mem * price_targets.mem * BLOCKS_PER_HOUR) / 1_000_000;
    let storage_price = match config.storage_type {
       StorageType::HDD => (config.storage * price_targets.storage_hd * BLOCKS_PER_HOUR) / 1_000_000,
       StorageType::SSD => (config.storage * price_targets.storage_ssd * BLOCKS_PER_HOUR) / 1_000_000,
       StorageType::NVMe => (config.storage * price_targets.storage_nvme * BLOCKS_PER_HOUR) / 1_000_000,
    };
  3. Sum up the prices to get the total price per block:

    let total_price_per_block = cpu_price + mem_price + storage_price;

Additional Notes

  1. All prices are specified in USD/hr and converted to a per-block basis using the average block time of 6 seconds.
  2. The pricing calculation uses integer arithmetic to avoid floating-point operations. The results are in millionths of a USD to maintain precision.
  3. We would want to add limits or sanity checks on the price values to prevent extreme or unreasonable pricing.

Open Questions and Recommendations

  1. Pricing Changes and Running Services:

    • Recommendation: Apply pricing changes only to newly created services. This ensures price stability for running services and prevents unexpected cost changes for users.
    • Implementation: Store the agreed-upon price with each service when it's created. Use this stored price for billing throughout the service's lifecycle.
  2. Timing of Pricing Changes:

    • Recommendation: Implement a delayed pricing change mechanism.
    • Implementation:
      • When an operator updates their pricing, set a future block number for the change to take effect.
      • Store both current and future pricing in the OperatorPreferences struct.
      • At the specified block, automatically switch to the new pricing for new services.
    • This approach provides transparency and allows users to prepare for upcoming price changes.
  3. Additional Considerations:

    • Implement a minimum notice period for price increases (e.g., 24 hours or 14,400 blocks at 6-second block time).
    • Allow immediate application of price decreases to benefit users.
    • Consider adding a maximum rate of change for prices to prevent drastic fluctuations.

By addressing these open questions, we can create a fair and transparent pricing system that balances the needs of both operators and users.